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));
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}."); } }
public void UpdateOrDefault(CoordinatorRoundConfig config, bool toFile) { Denomination = config.Denomination ?? Denomination; var configSerialized = JsonConvert.SerializeObject(config); JsonConvert.PopulateObject(configSerialized, this); if (toFile) { ToFile(); } }
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 { })
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."); }
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));
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 { })
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); } }
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; }
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."); }
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); }
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]}\".";