public async Task FullCoinjoinAsyncTestAsync() { var config = new WabiSabiConfig { MaxInputCountByRound = 1 }; var round = WabiSabiFactory.CreateRound(config); round.MaxVsizeAllocationPerAlice = 255; using var key = new Key(); var outpoint = BitcoinFactory.CreateOutPoint(); var mockRpc = new Mock <IRPCClient>(); mockRpc.Setup(rpc => rpc.GetTxOutAsync(outpoint.Hash, (int)outpoint.N, true)) .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse { IsCoinBase = false, Confirmations = 200, TxOut = new TxOut(Money.Coins(1m), key.PubKey.WitHash.GetAddress(Network.Main)), }); mockRpc.Setup(rpc => rpc.EstimateSmartFeeAsync(It.IsAny <int>(), It.IsAny <EstimateSmartFeeMode>())) .ReturnsAsync(new EstimateSmartFeeResponse { Blocks = 1000, FeeRate = new FeeRate(10m) }); mockRpc.Setup(rpc => rpc.GetMempoolInfoAsync(It.IsAny <CancellationToken>())) .ReturnsAsync(new MemPoolInfo { MinRelayTxFee = 1 }); mockRpc.Setup(rpc => rpc.PrepareBatch()).Returns(mockRpc.Object); mockRpc.Setup(rpc => rpc.SendBatchAsync()).Returns(Task.CompletedTask); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, mockRpc, round); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object); var wabiSabiApi = new WabiSabiController(coordinator); var insecureRandom = new InsecureRandom(); var roundState = RoundState.FromRound(round); var aliceArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); var inputRegistrationResponse = await aliceArenaClient.RegisterInputAsync(round.Id, outpoint, key, CancellationToken.None); var aliceId = inputRegistrationResponse.Value; var inputVsize = Constants.P2wpkhInputVirtualSize; var amountsToRequest = new[] { Money.Coins(.75m) - round.FeeRate.GetFee(inputVsize), Money.Coins(.25m), }.Select(x => x.Satoshi).ToArray(); using var destinationKey1 = new Key(); using var destinationKey2 = new Key(); var p2wpkhScriptSize = (long)destinationKey1.PubKey.WitHash.ScriptPubKey.EstimateOutputVsize(); var vsizesToRequest = new[] { roundState.MaxVsizeAllocationPerAlice - (inputVsize + 2 * p2wpkhScriptSize), 2 * p2wpkhScriptSize }; // Phase: Input Registration Assert.Equal(Phase.InputRegistration, round.Phase); var connectionConfirmationResponse1 = await aliceArenaClient.ConfirmConnectionAsync( round.Id, aliceId, amountsToRequest, vsizesToRequest, inputRegistrationResponse.IssuedAmountCredentials, inputRegistrationResponse.IssuedVsizeCredentials, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.ConnectionConfirmation, round.Phase); // Phase: Connection Confirmation var connectionConfirmationResponse2 = await aliceArenaClient.ConfirmConnectionAsync( round.Id, aliceId, amountsToRequest, vsizesToRequest, connectionConfirmationResponse1.IssuedAmountCredentials, connectionConfirmationResponse1.IssuedVsizeCredentials, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(1)); // Phase: Output Registration Assert.Equal(Phase.OutputRegistration, round.Phase); var bobArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); var reissuanceResponse = await bobArenaClient.ReissueCredentialAsync( round.Id, amountsToRequest, Enumerable.Repeat(p2wpkhScriptSize, 2), connectionConfirmationResponse2.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), connectionConfirmationResponse2.IssuedVsizeCredentials.Skip(1).Take(ProtocolConstants.CredentialNumber), // first amount is the leftover value CancellationToken.None); Credential amountCred1 = reissuanceResponse.IssuedAmountCredentials.ElementAt(0); Credential amountCred2 = reissuanceResponse.IssuedAmountCredentials.ElementAt(1); Credential zeroAmountCred1 = reissuanceResponse.IssuedAmountCredentials.ElementAt(2); Credential zeroAmountCred2 = reissuanceResponse.IssuedAmountCredentials.ElementAt(3); Credential vsizeCred1 = reissuanceResponse.IssuedVsizeCredentials.ElementAt(0); Credential vsizeCred2 = reissuanceResponse.IssuedVsizeCredentials.ElementAt(1); Credential zeroVsizeCred1 = reissuanceResponse.IssuedVsizeCredentials.ElementAt(2); Credential zeroVsizeCred2 = reissuanceResponse.IssuedVsizeCredentials.ElementAt(3); await bobArenaClient.RegisterOutputAsync( round.Id, destinationKey1.PubKey.WitHash.ScriptPubKey, new[] { amountCred1, zeroAmountCred1 }, new[] { vsizeCred1, zeroVsizeCred1 }, CancellationToken.None); await bobArenaClient.RegisterOutputAsync( round.Id, destinationKey2.PubKey.WitHash.ScriptPubKey, new[] { amountCred2, zeroAmountCred2 }, new[] { vsizeCred2, zeroVsizeCred2 }, CancellationToken.None); await aliceArenaClient.ReadyToSignAsync(round.Id, aliceId, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.TransactionSigning, round.Phase); var tx = round.Assert <SigningState>().CreateTransaction(); Assert.Single(tx.Inputs); Assert.Equal(2, tx.Outputs.Count); }