Esempio n. 1
0
 public async Task SsAsync()
 {
     var rpc                = new MockRpcClient();
     var roundConfig        = new CoordinatorRoundConfig();
     var utxoReferee        = new UtxoReferee(Network.Main, "./", rpc, roundConfig);
     var confirmationTarget = 12;
     var round              = new CoordinatorRound(rpc, utxoReferee, roundConfig, adjustedConfirmationTarget: confirmationTarget, confirmationTarget, roundConfig.ConfirmationTargetReductionRate, TimeSpan.FromSeconds(roundConfig.InputRegistrationTimeout));
Esempio n. 2
0
        public UtxoReferee(Network network, string folderPath, RPCClient rpc, CoordinatorRoundConfig roundConfig)
        {
            Network     = Guard.NotNull(nameof(network), network);
            FolderPath  = Guard.NotNullOrEmptyOrWhitespace(nameof(folderPath), folderPath, trim: true);
            RpcClient   = Guard.NotNull(nameof(rpc), rpc);
            RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);

            BannedUtxos = new ConcurrentDictionary <OutPoint, BannedUtxo>();

            Directory.CreateDirectory(FolderPath);

            if (File.Exists(BannedUtxosFilePath))
            {
                try
                {
                    var      toRemove = new List <string>();               // what's been confirmed
                    string[] allLines = File.ReadAllLines(BannedUtxosFilePath);
                    foreach (string line in allLines)
                    {
                        var bannedRecord = BannedUtxo.FromString(line);

                        GetTxOutResponse getTxOutResponse = RpcClient.GetTxOut(bannedRecord.Utxo.Hash, (int)bannedRecord.Utxo.N, includeMempool: true);

                        // Check if inputs are unspent.
                        if (getTxOutResponse is null)
                        {
                            toRemove.Add(line);
                        }
                        else
                        {
                            BannedUtxos.TryAdd(bannedRecord.Utxo, bannedRecord);
                        }
                    }

                    if (toRemove.Count != 0)                     // a little performance boost, often it'll be empty
                    {
                        var newAllLines = allLines.Where(x => !toRemove.Contains(x));
                        File.WriteAllLines(BannedUtxosFilePath, newAllLines);
                    }

                    Logger.LogInfo($"{allLines.Length} banned UTXOs are loaded from {BannedUtxosFilePath}.");
                }
                catch (Exception ex)
                {
                    Logger.LogWarning($"Banned UTXO file got corrupted. Deleting {BannedUtxosFilePath}. {ex.GetType()}: {ex.Message}");
                    File.Delete(BannedUtxosFilePath);
                }
            }
            else
            {
                Logger.LogInfo($"No banned UTXOs are loaded from {BannedUtxosFilePath}.");
            }
        }
Esempio n. 3
0
    public void UpdateOrDefault(CoordinatorRoundConfig config, bool toFile)
    {
        Denomination = config.Denomination ?? Denomination;
        var configSerialized = JsonConvert.SerializeObject(config);

        JsonConvert.PopulateObject(configSerialized, this);

        if (toFile)
        {
            ToFile();
        }
    }
Esempio n. 4
0
        public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, IRPCClient rpc, CancellationToken cancel)
        {
            Config      = Guard.NotNull(nameof(config), config);
            RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
            RpcClient   = Guard.NotNull(nameof(rpc), rpc);

            // Make sure RPC works.
            await AssertRpcNodeFullyInitializedAsync();

            // Make sure P2P works.
            await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint(), cancel);

            if (roundConfig.FilePath is { })
Esempio n. 5
0
        public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, IRPCClient rpc, CancellationToken cancel)
        {
            Config      = Guard.NotNull(nameof(config), config);
            RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
            RpcClient   = Guard.NotNull(nameof(rpc), rpc);

            // Make sure RPC works.
            await AssertRpcNodeFullyInitializedAsync();

            // Make sure P2P works.
            await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint(), cancel);

            if (roundConfig.FilePath != null)
            {
                HostedServices.Register(
                    new ConfigWatcher(
                        TimeSpan.FromSeconds(10),                         // Every 10 seconds check the config
                        RoundConfig,
                        () =>
                {
                    try
                    {
                        Coordinator.RoundConfig.UpdateOrDefault(RoundConfig, toFile: false);

                        Coordinator.AbortAllRoundsInInputRegistration($"{nameof(RoundConfig)} has changed.");
                    }
                    catch (Exception ex)
                    {
                        Logger.LogDebug(ex);
                    }
                }),
                    "Config Watcher");
            }

            await HostedServices.StartAllAsync(cancel);

            // Initialize index building
            var indexBuilderServiceDir = Path.Combine(DataDir, "IndexBuilderService");
            var indexFilePath          = Path.Combine(indexBuilderServiceDir, $"Index{RpcClient.Network}.dat");
            var blockNotifier          = HostedServices.FirstOrDefault <BlockNotifier>();

            IndexBuilderService = new IndexBuilderService(RpcClient, blockNotifier, indexFilePath);
            Coordinator         = new Coordinator(RpcClient.Network, blockNotifier, Path.Combine(DataDir, "CcjCoordinator"), RpcClient, roundConfig);
            IndexBuilderService.Synchronize();
            Logger.LogInfo($"{nameof(IndexBuilderService)} is successfully initialized and started synchronization.");

            await Coordinator.MakeSureTwoRunningRoundsAsync();

            Logger.LogInfo("Chaumian CoinJoin Coordinator is successfully initialized and started two new rounds.");
        }
Esempio n. 6
0
        public async Task TryOptimizeFeesTestAsync()
        {
            var rpc = new MockRpcClient();

            rpc.Network = Network.Main;
            rpc.OnEstimateSmartFeeAsync = (confTarget, estMode) => Task.FromResult(new EstimateSmartFeeResponse
            {
                Blocks  = 1,
                FeeRate = new FeeRate(10m)
            });

            var roundConfig        = new CoordinatorRoundConfig();
            var utxoReferee        = new UtxoReferee(Network.Main, "./", rpc, roundConfig);
            var confirmationTarget = 12;
            var round = new CoordinatorRound(rpc, utxoReferee, roundConfig, adjustedConfirmationTarget: confirmationTarget, confirmationTarget, roundConfig.ConfirmationTargetReductionRate, TimeSpan.FromSeconds(roundConfig.InputRegistrationTimeout));
Esempio n. 7
0
    public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, IRPCClient rpc, CancellationToken cancel)
    {
        Config      = Guard.NotNull(nameof(config), config);
        RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
        RpcClient   = Guard.NotNull(nameof(rpc), rpc);

        // Make sure RPC works.
        await AssertRpcNodeFullyInitializedAsync(cancel);

        // Make sure P2P works.
        await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint(), cancel);

        HostedServices.Register <MempoolMirror>(() => new MempoolMirror(TimeSpan.FromSeconds(21), RpcClient, P2pNode), "Full Node Mempool Mirror");

        if (roundConfig.FilePath is { })
Esempio n. 8
0
        public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, IRPCClient rpc, CancellationToken cancel)
        {
            Config      = Guard.NotNull(nameof(config), config);
            RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
            RpcClient   = Guard.NotNull(nameof(rpc), rpc);

            // Make sure RPC works.
            await AssertRpcNodeFullyInitializedAsync();

            // Make sure P2P works.
            await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint(), cancel);

            CoordinatorParameters coordinatorParameters = new(DataDir);

            HostedServices.Register <WabiSabiCoordinator>(new WabiSabiCoordinator(coordinatorParameters, RpcClient), "WabiSabi Coordinator");

            if (roundConfig.FilePath is { })
        public async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            Logger.InitializeDefaults(Path.Combine(Global.DataDir, "Logs.txt"));
            Logger.LogSoftwareStarted("Wasabi Backend");

            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
            TaskScheduler.UnobservedTaskException      += TaskScheduler_UnobservedTaskException;
            var configFilePath = Path.Combine(Global.DataDir, "Config.json");
            var config         = new Config(configFilePath);

            config.LoadOrCreateDefaultFile();
            Logger.LogInfo("Config is successfully initialized.");

            var roundConfigFilePath = Path.Combine(Global.DataDir, "CcjRoundConfig.json");
            var roundConfig         = new CoordinatorRoundConfig(roundConfigFilePath);

            roundConfig.LoadOrCreateDefaultFile();
            Logger.LogInfo("RoundConfig is successfully initialized.");

            string host = config.GetBitcoinCoreRpcEndPoint().ToString(config.Network.RPCPort);
            var    rpc  = new RPCClient(
                authenticationString: config.BitcoinRpcConnectionString,
                hostOrUri: host,
                network: config.Network);

            var cachedRpc = new CachedRpcClient(rpc, Cache);
            await Global.InitializeAsync(config, roundConfig, cachedRpc, cancellationToken);

            try
            {
                await WebsiteTorifier.CloneAndUpdateOnionIndexHtmlAsync();
            }
            catch (Exception ex)
            {
                Logger.LogWarning(ex);
            }
        }
Esempio n. 10
0
        private volatile bool _disposedValue = false;         // To detect redundant calls

        public Coordinator(Network network, BlockNotifier blockNotifier, string folderPath, IRPCClient rpc, CoordinatorRoundConfig roundConfig)
        {
            Network       = Guard.NotNull(nameof(network), network);
            BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
            FolderPath    = Guard.NotNullOrEmptyOrWhitespace(nameof(folderPath), folderPath, trim: true);
            RpcClient     = Guard.NotNull(nameof(rpc), rpc);
            RoundConfig   = Guard.NotNull(nameof(roundConfig), roundConfig);

            Rounds         = new List <CoordinatorRound>();
            RoundsListLock = new AsyncLock();

            CoinJoins                  = new List <uint256>();
            UnconfirmedCoinJoins       = new List <uint256>();
            CoinJoinsLock              = new AsyncLock();
            LastSuccessfulCoinJoinTime = DateTimeOffset.UtcNow;

            Directory.CreateDirectory(FolderPath);

            UtxoReferee = new UtxoReferee(Network, FolderPath, RpcClient, RoundConfig);

            if (File.Exists(CoinJoinsFilePath))
            {
                try
                {
                    var getTxTasks = new List <(Task <Transaction> txTask, string line)>();
                    var batch      = RpcClient.PrepareBatch();

                    var      toRemove = new List <string>();
                    string[] allLines = File.ReadAllLines(CoinJoinsFilePath);
                    foreach (string line in allLines)
                    {
                        try
                        {
                            getTxTasks.Add((batch.GetRawTransactionAsync(uint256.Parse(line)), line));
                        }
                        catch (Exception ex)
                        {
                            toRemove.Add(line);

                            var logEntry = ex is RPCException rpce && rpce.RPCCode == RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY
                                                                ? $"CoinJoins file contains invalid transaction ID {line}"
                                                                : $"CoinJoins file got corrupted. Deleting offending line \"{line.Substring(0, 20)}\".";

                            Logger.LogWarning($"{logEntry}. {ex.GetType()}: {ex.Message}");
                        }
                    }

                    batch.SendBatchAsync().GetAwaiter().GetResult();

                    foreach (var(txTask, line) in getTxTasks)
                    {
                        try
                        {
                            var tx = txTask.GetAwaiter().GetResult();
                            CoinJoins.Add(tx.GetHash());
                        }
                        catch (Exception ex)
                        {
                            toRemove.Add(line);

                            var logEntry = ex is RPCException rpce && rpce.RPCCode == RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY
                                                                ? $"CoinJoins file contains invalid transaction ID {line}"
                                                                : $"CoinJoins file got corrupted. Deleting offending line \"{line.Substring(0, 20)}\".";

                            Logger.LogWarning($"{logEntry}. {ex.GetType()}: {ex.Message}");
                        }
                    }

                    if (toRemove.Count != 0)                     // a little performance boost, it'll be empty almost always
                    {
                        var newAllLines = allLines.Where(x => !toRemove.Contains(x));
                        File.WriteAllLines(CoinJoinsFilePath, newAllLines);
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogWarning($"CoinJoins file got corrupted. Deleting {CoinJoinsFilePath}. {ex.GetType()}: {ex.Message}");
                    File.Delete(CoinJoinsFilePath);
                }

                uint256[] mempoolHashes = RpcClient.GetRawMempoolAsync().GetAwaiter().GetResult();
                UnconfirmedCoinJoins.AddRange(CoinJoins.Intersect(mempoolHashes));
            }

            try
            {
                string roundCountFilePath = Path.Combine(folderPath, "RoundCount.txt");
                if (File.Exists(roundCountFilePath))
                {
                    string roundCount = File.ReadAllText(roundCountFilePath);
                    CoordinatorRound.RoundCount = long.Parse(roundCount);
                }
                else
                {
                    // First time initializes (so the first constructor will increment it and we'll start from 1.)
                    CoordinatorRound.RoundCount = 0;
                }
            }
            catch (Exception ex)
            {
                CoordinatorRound.RoundCount = 0;
                Logger.LogInfo($"{nameof(CoordinatorRound.RoundCount)} file was corrupt. Resetting to 0.");
                Logger.LogDebug(ex);
            }

            BlockNotifier.OnBlock += BlockNotifier_OnBlockAsync;
        }
Esempio n. 11
0
    public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, IRPCClient rpc, CancellationToken cancel)
    {
        Config      = Guard.NotNull(nameof(config), config);
        RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
        RpcClient   = Guard.NotNull(nameof(rpc), rpc);

        // Make sure RPC works.
        await AssertRpcNodeFullyInitializedAsync(cancel);

        // Make sure P2P works.
        await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint(), cancel);

        var p2pNode = Guard.NotNull(nameof(P2pNode), P2pNode);

        HostedServices.Register <MempoolMirror>(() => new MempoolMirror(TimeSpan.FromSeconds(21), RpcClient, p2pNode), "Full Node Mempool Mirror");

        // Initialize index building
        var indexBuilderServiceDir = Path.Combine(DataDir, "IndexBuilderService");
        var indexFilePath          = Path.Combine(indexBuilderServiceDir, $"Index{RpcClient.Network}.dat");
        var blockNotifier          = HostedServices.Get <BlockNotifier>();

        CoordinatorParameters coordinatorParameters = new(DataDir);

        Coordinator = new(RpcClient.Network, blockNotifier, Path.Combine(DataDir, "CcjCoordinator"), RpcClient, roundConfig);
        Coordinator.CoinJoinBroadcasted += Coordinator_CoinJoinBroadcasted;

        var coordinator = Guard.NotNull(nameof(Coordinator), Coordinator);

        if (!string.IsNullOrWhiteSpace(roundConfig.FilePath))
        {
            HostedServices.Register <ConfigWatcher>(() =>
                                                    new ConfigWatcher(
                                                        TimeSpan.FromSeconds(10), // Every 10 seconds check the config
                                                        RoundConfig,
                                                        () =>
            {
                try
                {
                    coordinator.RoundConfig.UpdateOrDefault(RoundConfig, toFile: false);

                    coordinator.AbortAllRoundsInInputRegistration($"{nameof(RoundConfig)} has changed.");
                }
                catch (Exception ex)
                {
                    Logger.LogDebug(ex);
                }
            }),
                                                    "Config Watcher");
        }

        CoinJoinIdStore = CoinJoinIdStore.Create(Coordinator.CoinJoinsFilePath, coordinatorParameters.CoinJoinIdStoreFilePath);
        var coinJoinScriptStore = CoinJoinScriptStore.LoadFromFile(coordinatorParameters.CoinJoinScriptStoreFilePath);

        WabiSabiCoordinator = new WabiSabiCoordinator(coordinatorParameters, RpcClient, CoinJoinIdStore, coinJoinScriptStore);
        HostedServices.Register <WabiSabiCoordinator>(() => WabiSabiCoordinator, "WabiSabi Coordinator");
        HostedServices.Register <RoundBootstrapper>(() => new RoundBootstrapper(TimeSpan.FromMilliseconds(100), Coordinator), "Round Bootstrapper");

        await HostedServices.StartAllAsync(cancel);

        IndexBuilderService = new(RpcClient, blockNotifier, indexFilePath);
        IndexBuilderService.Synchronize();
        Logger.LogInfo($"{nameof(IndexBuilderService)} is successfully initialized and started synchronization.");
    }
Esempio n. 12
0
        public async Task NotingTestsAsync()
        {
            (_, IRPCClient rpc, Network network, Coordinator coordinator, _, _, _) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            Money   denomination                  = Money.Coins(1m);
            decimal coordinatorFeePercent         = 0.1m;
            int     anonymitySet                  = 2;
            int     connectionConfirmationTimeout = 1;
            bool    doesNoteBeforeBan             = true;
            CoordinatorRoundConfig roundConfig    = RegTestFixture.CreateRoundConfig(denomination, 140, 0.7, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 1, 1, 1, 24, doesNoteBeforeBan, 11);

            coordinator.RoundConfig.UpdateOrDefault(roundConfig, toFile: true);
            coordinator.AbortAllRoundsInInputRegistration("");

            Uri baseUri = new Uri(RegTestFixture.BackendEndPoint);

            var              registerRequests  = new List <(BitcoinWitPubKeyAddress changeOutputAddress, BlindedOutputWithNonceIndex blindedData, InputProofModel[] inputsProofs)>();
            AliceClient4     aliceClientBackup = null;
            CoordinatorRound round             = coordinator.GetCurrentInputRegisterableRoundOrDefault();

            for (int i = 0; i < roundConfig.AnonymitySet; i++)
            {
                BitcoinWitPubKeyAddress activeOutputAddress = new Key().PubKey.GetSegwitAddress(network);
                BitcoinWitPubKeyAddress changeOutputAddress = new Key().PubKey.GetSegwitAddress(network);
                Key inputKey = new Key();
                BitcoinWitPubKeyAddress inputAddress = inputKey.PubKey.GetSegwitAddress(network);

                var requester = new Requester();
                var nonce     = round.NonceProvider.GetNextNonce();

                var     blinded = new BlindedOutputWithNonceIndex(nonce.N, requester.BlindScript(round.MixingLevels.GetBaseLevel().SignerKey.PubKey, nonce.R, activeOutputAddress.ScriptPubKey));
                uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blinded.BlindedOutput.ToBytes()));

                uint256 txHash = await rpc.SendToAddressAsync(inputAddress, Money.Coins(2));

                await rpc.GenerateAsync(1);

                Transaction transaction = await rpc.GetRawTransactionAsync(txHash);

                Coin     coin  = transaction.Outputs.GetCoins(inputAddress.ScriptPubKey).Single();
                OutPoint input = coin.Outpoint;

                InputProofModel inputProof = new InputProofModel {
                    Input = input, Proof = inputKey.SignCompact(blindedOutputScriptsHash)
                };
                InputProofModel[] inputsProofs = new InputProofModel[] { inputProof };
                registerRequests.Add((changeOutputAddress, blinded, inputsProofs));
                aliceClientBackup = await AliceClientBase.CreateNewAsync(round.RoundId, new[] { activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SignerKey.PubKey }, new[] { requester }, network, changeOutputAddress, new[] { blinded }, inputsProofs, BackendHttpClient);
            }

            await WaitForTimeoutAsync();

            int bannedCount = coordinator.UtxoReferee.CountBanned(false);

            Assert.Equal(0, bannedCount);
            int notedCount = coordinator.UtxoReferee.CountBanned(true);

            Assert.Equal(anonymitySet, notedCount);

            round = coordinator.GetCurrentInputRegisterableRoundOrDefault();

            foreach (var registerRequest in registerRequests)
            {
                await AliceClientBase.CreateNewAsync(round.RoundId, aliceClientBackup.RegisteredAddresses, round.MixingLevels.GetAllLevels().Select(x => x.SignerKey.PubKey), aliceClientBackup.Requesters, network, registerRequest.changeOutputAddress, new[] { registerRequest.blindedData }, registerRequest.inputsProofs, BackendHttpClient);
            }

            await WaitForTimeoutAsync();

            bannedCount = coordinator.UtxoReferee.CountBanned(false);
            Assert.Equal(anonymitySet, bannedCount);
            notedCount = coordinator.UtxoReferee.CountBanned(true);
            Assert.Equal(anonymitySet, notedCount);
        }
Esempio n. 13
0
        private volatile bool _disposedValue = false;         // To detect redundant calls

        public Coordinator(Network network, BlockNotifier blockNotifier, string folderPath, IRPCClient rpc, CoordinatorRoundConfig roundConfig)
        {
            Network       = Guard.NotNull(nameof(network), network);
            BlockNotifier = Guard.NotNull(nameof(blockNotifier), blockNotifier);
            FolderPath    = Guard.NotNullOrEmptyOrWhitespace(nameof(folderPath), folderPath, trim: true);
            RpcClient     = Guard.NotNull(nameof(rpc), rpc);
            RoundConfig   = Guard.NotNull(nameof(roundConfig), roundConfig);

            Rounds = ImmutableList <CoordinatorRound> .Empty;

            LastSuccessfulCoinJoinTime = DateTimeOffset.UtcNow;

            Directory.CreateDirectory(FolderPath);

            UtxoReferee = new UtxoReferee(Network, FolderPath, RpcClient, RoundConfig);

            if (File.Exists(CoinJoinsFilePath))
            {
                try
                {
                    var getTxTasks = new List <(Task <Transaction> txTask, string line)>();
                    var batch      = RpcClient.PrepareBatch();

                    var      toRemove = new List <string>();
                    string[] allLines = File.ReadAllLines(CoinJoinsFilePath);
                    foreach (string line in allLines)
                    {
                        try
                        {
                            getTxTasks.Add((batch.GetRawTransactionAsync(uint256.Parse(line)), line));
                        }
                        catch (Exception ex)
                        {
                            toRemove.Add(line);

                            var logEntry = ex is RPCException rpce && rpce.RPCCode == RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY
                                                                ? $"CoinJoins file contains invalid transaction ID {line}"
                                                                : $"CoinJoins file got corrupted. Deleting offending line \"{line[..20]}\".";