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); }
public async Task FullCoinjoinAsyncTest() { var config = new WabiSabiConfig { MaxInputCountByRound = 1 }; var round = WabiSabiFactory.CreateRound(config); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, round); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); 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)), }); await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object); CredentialPool amountCredentials = new(); CredentialPool vsizeCredentials = new(); var aliceArenaClient = new ArenaClient(round.AmountCredentialIssuerParameters, round.VsizeCredentialIssuerParameters, amountCredentials, vsizeCredentials, coordinator, new InsecureRandom()); var aliceId = await aliceArenaClient.RegisterInputAsync(Money.Coins(1m), outpoint, key, round.Id, round.Hash); Assert.NotEqual(Guid.Empty, aliceId); Assert.Empty(amountCredentials.Valuable); var reissuanceAmounts = new[] { Money.Coins(.75m) - round.FeeRate.GetFee(Constants.P2wpkhInputVirtualSize), Money.Coins(.25m) }; var inputVsize = Constants.P2wpkhInputVirtualSize; var inputRemainingVsizes = new[] { ProtocolConstants.MaxVsizePerAlice - inputVsize }; // Phase: Input Registration Assert.Equal(Phase.InputRegistration, round.Phase); await aliceArenaClient.ConfirmConnectionAsync( round.Id, aliceId, inputRemainingVsizes, amountCredentials.ZeroValue.Take(ProtocolConstants.CredentialNumber), reissuanceAmounts); Assert.Empty(amountCredentials.Valuable); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.ConnectionConfirmation, round.Phase); // Phase: Connection Confirmation await aliceArenaClient.ConfirmConnectionAsync( round.Id, aliceId, inputRemainingVsizes, amountCredentials.ZeroValue.Take(ProtocolConstants.CredentialNumber), reissuanceAmounts); Assert.Single(amountCredentials.Valuable, x => x.Amount.ToMoney() == reissuanceAmounts.First()); Assert.Single(amountCredentials.Valuable, x => x.Amount.ToMoney() == reissuanceAmounts.Last()); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.OutputRegistration, round.Phase); var bobArenaClient = new ArenaClient(round.AmountCredentialIssuerParameters, round.VsizeCredentialIssuerParameters, amountCredentials, vsizeCredentials, coordinator, new InsecureRandom()); Assert.Equal(4, amountCredentials.ZeroValue.Count()); // Phase: Output Registration using var destinationKey1 = new Key(); using var destinationKey2 = new Key(); var result = await bobArenaClient.ReissueCredentialAsync( round.Id, reissuanceAmounts[0], destinationKey1.PubKey.WitHash.ScriptPubKey, reissuanceAmounts[1], destinationKey2.PubKey.WitHash.ScriptPubKey, amountCredentials.Valuable, vsizeCredentials.Valuable); Assert.Equal(6, amountCredentials.ZeroValue.Count()); Assert.Equal(6, vsizeCredentials.ZeroValue.Count()); Credential amountCred1 = result.RealAmountCredentials.ElementAt(0); Credential amountCred2 = result.RealAmountCredentials.ElementAt(1); Credential vsizeCred1 = result.RealVsizeCredentials.ElementAt(0); Credential vsizeCred2 = result.RealVsizeCredentials.ElementAt(1); await bobArenaClient.RegisterOutputAsync( round.Id, reissuanceAmounts[0], destinationKey1.PubKey.WitHash.ScriptPubKey, new[] { amountCred1 }, new[] { vsizeCred1 }); await bobArenaClient.RegisterOutputAsync( round.Id, reissuanceAmounts[1], destinationKey2.PubKey.WitHash.ScriptPubKey, new[] { amountCred2 }, new[] { vsizeCred2 }); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.TransactionSigning, round.Phase); Assert.Equal(1, round.Coinjoin.Inputs.Count); Assert.Equal(2, round.Coinjoin.Outputs.Count); }