public WabiSabiCoordinator(CoordinatorParameters parameters, IRPCClient rpc, ICoinJoinIdStore coinJoinIdStore) { Parameters = parameters; Warden = new(parameters.UtxoWardenPeriod, parameters.PrisonFilePath, Config); ConfigWatcher = new(parameters.ConfigChangeMonitoringPeriod, Config, () => Logger.LogInfo("WabiSabi configuration has changed.")); CoinJoinIdStore = coinJoinIdStore; CoinJoinTransactionArchiver transactionArchiver = new(Path.Combine(parameters.CoordinatorDataDir, "CoinJoinTransactions")); CoinJoinFeeRateStatStore = CoinJoinFeeRateStatStore.LoadFromFile(parameters.CoinJoinFeeRateStatStoreFilePath, Config, rpc); IoHelpers.EnsureContainingDirectoryExists(Parameters.CoinJoinFeeRateStatStoreFilePath); CoinJoinFeeRateStatStore.NewStat += FeeRateStatStore_NewStat; var coinJoinScriptStore = CoinJoinScriptStore.LoadFromFile(parameters.CoinJoinScriptStoreFilePath); IoHelpers.EnsureContainingDirectoryExists(Parameters.CoinJoinScriptStoreFilePath); RoundParameterFactory roundParameterFactory = new RoundParameterFactory(Config, rpc.Network); Arena = new( parameters.RoundProgressSteppingPeriod, rpc.Network, Config, rpc, Warden.Prison, coinJoinIdStore, roundParameterFactory, transactionArchiver, coinJoinScriptStore); IoHelpers.EnsureContainingDirectoryExists(Parameters.CoinJoinIdStoreFilePath); Arena.CoinJoinBroadcast += Arena_CoinJoinBroadcast; }
public async Task <EmptyResponse> RegisterOutputCoreAsync(OutputRegistrationRequest request, CancellationToken cancellationToken) { using (await AsyncLock.LockAsync(cancellationToken).ConfigureAwait(false)) { var round = GetRound(request.RoundId, Phase.OutputRegistration); var credentialAmount = -request.AmountCredentialRequests.Delta; if (CoinJoinScriptStore?.Contains(request.Script) is true) { Logger.LogWarning($"Round ({request.RoundId}): Already registered script."); throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AlreadyRegisteredScript, $"Round ({request.RoundId}): Already registered script."); } var inputScripts = round.Alices.Select(a => a.Coin.ScriptPubKey).ToHashSet(); if (inputScripts.Contains(request.Script)) { Logger.LogWarning($"Round ({request.RoundId}): Already registered script in the round."); throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.AlreadyRegisteredScript, $"Round ({request.RoundId}): Already registered script in round."); } Bob bob = new(request.Script, credentialAmount); var outputValue = bob.CalculateOutputAmount(round.Parameters.MiningFeeRate); var vsizeCredentialRequests = request.VsizeCredentialRequests; if (-vsizeCredentialRequests.Delta != bob.OutputVsize) { throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.IncorrectRequestedVsizeCredentials, $"Round ({request.RoundId}): Incorrect requested vsize credentials."); } // Update the current round state with the additional output to ensure it's valid. var newState = round.AddOutput(new TxOut(outputValue, bob.Script)); // Verify the credential requests and prepare their responses. await round.AmountCredentialIssuer.HandleRequestAsync(request.AmountCredentialRequests, cancellationToken).ConfigureAwait(false); await round.VsizeCredentialIssuer.HandleRequestAsync(vsizeCredentialRequests, cancellationToken).ConfigureAwait(false); // Update round state. round.Bobs.Add(bob); round.CoinjoinState = newState; } return(EmptyResponse.Instance); }
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."); }