public async Task DiffTooSmallToBlameAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5, OutputRegistrationTimeout = TimeSpan.Zero, CoordinationFeeRate = CoordinationFeeRate.Zero }; var(keyChain, coin1, coin2) = WabiSabiFactory.CreateCoinKeyPairs(); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin, coin2.Coin); using Arena arena = await ArenaBuilder.From(cfg).With(mockRpc).CreateAndStartAsync(); var(round, arenaClient, alices) = await CreateRoundWithTwoConfirmedConnectionsAsync(arena, keyChain, coin1, coin2); var(amountCredentials1, vsizeCredentials1) = (alices[0].IssuedAmountCredentials, alices[0].IssuedVsizeCredentials); var(amountCredentials2, vsizeCredentials2) = (alices[1].IssuedAmountCredentials, alices[1].IssuedVsizeCredentials); // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey1 = new Key(); using var destKey2 = new Key(); await bobClient.RegisterOutputAsync( destKey1.PubKey.WitHash.ScriptPubKey, amountCredentials1.Take(ProtocolConstants.CredentialNumber), vsizeCredentials1.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); await bobClient.RegisterOutputAsync( destKey2.PubKey.WitHash.ScriptPubKey, amountCredentials2.Take(ProtocolConstants.CredentialNumber), vsizeCredentials2.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); // Add another input. The input must be able to pay for itself, but // the remaining amount after deducting the fees needs to be less // than the minimum. var txParams = round.Assert <ConstructionState>().Parameters; var extraAlice = WabiSabiFactory.CreateAlice(txParams.FeeRate.GetFee(Constants.P2wpkhInputVirtualSize) + txParams.AllowedOutputAmounts.Min - new Money(1L), round); round.Alices.Add(extraAlice); round.CoinjoinState = round.Assert <ConstructionState>().AddInput(extraAlice.Coin); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var tx = round.Assert <SigningState>().CreateTransaction(); Assert.Equal(3, tx.Inputs.Count); Assert.Equal(2, tx.Outputs.Count); Assert.DoesNotContain(round.CoordinatorScript, tx.Outputs.Select(x => x.ScriptPubKey)); await arena.StopAsync(CancellationToken.None); }
public async Task AllBobsRegisteredAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5 }; using Key key1 = new(); using Key key2 = new(); var coin1 = WabiSabiFactory.CreateCoin(key1); var coin2 = WabiSabiFactory.CreateCoin(key2); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1, coin2); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc); var(round, arenaClient, alices) = await CreateRoundWithTwoConfirmedConnectionsAsync(arena, key1, coin1, key2, coin2); var(amountCredentials1, vsizeCredentials1) = (alices[0].IssuedAmountCredentials, alices[0].IssuedVsizeCredentials); var(amountCredentials2, vsizeCredentials2) = (alices[1].IssuedAmountCredentials, alices[1].IssuedVsizeCredentials); // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey1 = new Key(); await bobClient.RegisterOutputAsync( coin1.Amount - round.FeeRate.GetFee(coin1.ScriptPubKey.EstimateInputVsize()), destKey1.PubKey.WitHash.ScriptPubKey, amountCredentials1.Take(ProtocolConstants.CredentialNumber), vsizeCredentials1.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); using var destKey2 = new Key(); await bobClient.RegisterOutputAsync( coin2.Amount - round.FeeRate.GetFee(coin2.ScriptPubKey.EstimateInputVsize()), destKey2.PubKey.WitHash.ScriptPubKey, amountCredentials2.Take(ProtocolConstants.CredentialNumber), vsizeCredentials2.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); foreach (var alice in alices) { await alice.ReadyToSignAsync(CancellationToken.None); } await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var tx = round.Assert <SigningState>().CreateTransaction(); Assert.Equal(2, tx.Inputs.Count); Assert.Equal(2, tx.Outputs.Count); await arena.StopAsync(CancellationToken.None); }
CreateRoundWithOutputsReadyToSignAsync(Arena arena, Key key1, Coin coin1, Key key2, Coin coin2) { // Create the round. await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); var round = Assert.Single(arena.Rounds); var arenaClient = WabiSabiFactory.CreateArenaClient(arena); // Register Alices. var aliceClient1 = new AliceClient(round.Id, arenaClient, coin1, round.FeeRate, key1.GetBitcoinSecret(round.Network)); var aliceClient2 = new AliceClient(round.Id, arenaClient, coin2, round.FeeRate, key2.GetBitcoinSecret(round.Network)); await aliceClient1.RegisterInputAsync(CancellationToken.None).ConfigureAwait(false); await aliceClient2.RegisterInputAsync(CancellationToken.None).ConfigureAwait(false); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.ConnectionConfirmation, round.Phase); // Confirm connections. await aliceClient1.ConfirmConnectionAsync(TimeSpan.FromMilliseconds(100), round.MaxVsizeAllocationPerAlice, CancellationToken.None).ConfigureAwait(false); await aliceClient2.ConfirmConnectionAsync(TimeSpan.FromMilliseconds(100), round.MaxVsizeAllocationPerAlice, CancellationToken.None).ConfigureAwait(false); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.OutputRegistration, round.Phase); // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey1 = new Key(); using var destKey2 = new Key(); await bobClient.RegisterOutputAsync( coin1.Amount - round.FeeRate.GetFee(coin1.ScriptPubKey.EstimateInputVsize()), destKey1.PubKey.WitHash.ScriptPubKey, aliceClient1.RealAmountCredentials, aliceClient1.RealVsizeCredentials, CancellationToken.None).ConfigureAwait(false); await bobClient.RegisterOutputAsync( coin2.Amount - round.FeeRate.GetFee(coin2.ScriptPubKey.EstimateInputVsize()), destKey1.PubKey.WitHash.ScriptPubKey, aliceClient2.RealAmountCredentials, aliceClient2.RealVsizeCredentials, CancellationToken.None).ConfigureAwait(false); return(round, aliceClient1, aliceClient2); }
CreateRoundWithOutputsReadyToSignAsync(Arena arena, Key key1, SmartCoin coin1, Key key2, SmartCoin coin2) { // Create the round. await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); var round = Assert.Single(arena.Rounds); round.MaxVsizeAllocationPerAlice = 11 + 31 + MultipartyTransactionParameters.SharedOverhead; var arenaClient = WabiSabiFactory.CreateArenaClient(arena); // Register Alices. using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena); await roundStateUpdater.StartAsync(CancellationToken.None); using var identificationKey = new Key(); var task1 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, key1.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, CancellationToken.None); var task2 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, key2.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, CancellationToken.None); while (Phase.OutputRegistration != round.Phase) { await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); } await Task.WhenAll(task1, task2); var aliceClient1 = task1.Result; var aliceClient2 = task2.Result; // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey1 = new Key(); using var destKey2 = new Key(); await bobClient.RegisterOutputAsync( destKey1.PubKey.WitHash.ScriptPubKey, aliceClient1.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), aliceClient1.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber), CancellationToken.None).ConfigureAwait(false); await bobClient.RegisterOutputAsync( destKey1.PubKey.WitHash.ScriptPubKey, aliceClient2.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), aliceClient2.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber), CancellationToken.None).ConfigureAwait(false); await roundStateUpdater.StopAsync(CancellationToken.None); return(round, aliceClient1, aliceClient2); }
public async Task DoesntSwitchImmaturelyAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5 }; var(key1, coin1, key2, coin2) = WabiSabiFactory.CreateCoinKeyPairs(); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin, coin2.Coin); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc); var(round, arenaClient, alices) = await CreateRoundWithTwoConfirmedConnectionsAsync(arena, key1, coin1, key2, coin2); var(amountCredentials1, vsizeCredentials1) = (alices[0].IssuedAmountCredentials, alices[0].IssuedVsizeCredentials); var(amountCredentials2, vsizeCredentials2) = (alices[1].IssuedAmountCredentials, alices[1].IssuedVsizeCredentials); // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey = new Key(); await bobClient.RegisterOutputAsync( destKey.PubKey.WitHash.ScriptPubKey, amountCredentials1.Take(ProtocolConstants.CredentialNumber), vsizeCredentials1.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.OutputRegistration, round.Phase); await arena.StopAsync(CancellationToken.None); }
public async Task StartOutputRegistrationAsync( BobClient bobClient, Script scriptPubKey, CancellationToken cancellationToken) { await Task.WhenAll(AmountCredentialToPresentTasks.Concat(VsizeCredentialToPresentTasks)).ConfigureAwait(false); IEnumerable <Credential> inputAmountCredentials = AmountCredentialToPresentTasks.Select(x => x.Result); IEnumerable <Credential> inputVsizeCredentials = VsizeCredentialToPresentTasks.Select(x => x.Result); await bobClient.RegisterOutputAsync( scriptPubKey, inputAmountCredentials, inputVsizeCredentials, cancellationToken).ConfigureAwait(false); }
public async Task SomeBobsRegisteredTimeoutAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5, OutputRegistrationTimeout = TimeSpan.Zero }; using Key key1 = new(); using Key key2 = new(); var coin1 = WabiSabiFactory.CreateCoin(key1); var coin2 = WabiSabiFactory.CreateCoin(key2); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1, coin2); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc).ConfigureAwait(false); var(round, arenaClient, alices) = await CreateRoundWithTwoConfirmedConnectionsAsync(arena, key1, coin1, key2, coin2).ConfigureAwait(false); var(realAmountCredentials1, realVsizeCredentials1) = alices[0]; var(realAmountCredentials2, realVsizeCredentials2) = alices[1]; // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey = new Key(); await bobClient.RegisterOutputAsync( coin1.Amount - round.FeeRate.GetFee(coin1.ScriptPubKey.EstimateInputVsize()), destKey.PubKey.WitHash.ScriptPubKey, realAmountCredentials1, realVsizeCredentials1, CancellationToken.None).ConfigureAwait(false); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var tx = round.Assert <SigningState>().CreateTransaction(); Assert.Equal(2, tx.Inputs.Count); Assert.Equal(2, tx.Outputs.Count); Assert.Contains(cfg.BlameScript, tx.Outputs.Select(x => x.ScriptPubKey)); await arena.StopAsync(CancellationToken.None); }
public async Task SomeBobsRegisteredTimeoutAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5, OutputRegistrationTimeout = TimeSpan.Zero, CoordinationFeeRate = CoordinationFeeRate.Zero }; var(keyChain, coin1, coin2) = WabiSabiFactory.CreateCoinKeyPairs(); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin, coin2.Coin); using Arena arena = await ArenaBuilder.From(cfg).With(mockRpc).CreateAndStartAsync(); var(round, arenaClient, alices) = await CreateRoundWithTwoConfirmedConnectionsAsync(arena, keyChain, coin1, coin2); var(amountCredentials1, vsizeCredentials1) = (alices[0].IssuedAmountCredentials, alices[0].IssuedVsizeCredentials); var(amountCredentials2, vsizeCredentials2) = (alices[1].IssuedAmountCredentials, alices[1].IssuedVsizeCredentials); // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey = new Key(); await bobClient.RegisterOutputAsync( destKey.PubKey.WitHash.ScriptPubKey, amountCredentials1.Take(ProtocolConstants.CredentialNumber), vsizeCredentials1.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var tx = round.Assert <SigningState>().CreateTransaction(); Assert.Equal(2, tx.Inputs.Count); Assert.Equal(2, tx.Outputs.Count); Assert.Contains(round.CoordinatorScript, tx.Outputs.Select(x => x.ScriptPubKey)); await arena.StopAsync(CancellationToken.None); }
CreateRoundWithOutputsReadyToSignAsync(Arena arena, Key key1, Coin coin1, Key key2, Coin coin2) { // Create the round. await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); var round = Assert.Single(arena.Rounds); var arenaClient = WabiSabiFactory.CreateArenaClient(arena); // Register Alices. var aliceClient1 = new AliceClient(round.Id, arenaClient, coin1, round.FeeRate, key1.GetBitcoinSecret(round.Network)); var aliceClient2 = new AliceClient(round.Id, arenaClient, coin2, round.FeeRate, key2.GetBitcoinSecret(round.Network)); await aliceClient1.RegisterInputAsync(CancellationToken.None).ConfigureAwait(false); await aliceClient2.RegisterInputAsync(CancellationToken.None).ConfigureAwait(false); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.ConnectionConfirmation, round.Phase); // Confirm connections. using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), new ArenaRequestHandlerAdapter(arena)); await aliceClient1.ConfirmConnectionAsync( TimeSpan.FromMilliseconds(100), new long[] { coin1.EffectiveValue(round.FeeRate) }, new long[] { round.MaxVsizeAllocationPerAlice - coin1.ScriptPubKey.EstimateInputVsize() }, roundStateUpdater, CancellationToken.None).ConfigureAwait(false); await aliceClient2.ConfirmConnectionAsync( TimeSpan.FromMilliseconds(100), new long[] { coin2.EffectiveValue(round.FeeRate) }, new long[] { round.MaxVsizeAllocationPerAlice - coin2.ScriptPubKey.EstimateInputVsize() }, roundStateUpdater, CancellationToken.None).ConfigureAwait(false); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.OutputRegistration, round.Phase); // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey1 = new Key(); using var destKey2 = new Key(); await bobClient.RegisterOutputAsync( coin1.Amount - round.FeeRate.GetFee(coin1.ScriptPubKey.EstimateInputVsize()), destKey1.PubKey.WitHash.ScriptPubKey, aliceClient1.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), aliceClient1.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber), CancellationToken.None).ConfigureAwait(false); await bobClient.RegisterOutputAsync( coin2.Amount - round.FeeRate.GetFee(coin2.ScriptPubKey.EstimateInputVsize()), destKey1.PubKey.WitHash.ScriptPubKey, aliceClient2.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), aliceClient2.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber), CancellationToken.None).ConfigureAwait(false); return(round, aliceClient1, aliceClient2); }
public async Task RegisterOutputTestAsync() { var config = new WabiSabiConfig { MaxInputCountByRound = 1 }; var round = WabiSabiFactory.CreateRound(config); var km = ServiceFactory.CreateKeyManager(""); var key = BitcoinFactory.CreateHdPubKey(km); SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m)); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin); 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 insecureRandom = new InsecureRandom(); var wabiSabiApi = new WabiSabiController(coordinator); var roundState = RoundState.FromRound(round); var aliceArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); var bobArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); Assert.Equal(Phase.InputRegistration, round.Phase); var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main); using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi); await roundStateUpdater.StartAsync(CancellationToken.None); var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), aliceArenaClient, coin1, bitcoinSecret, roundStateUpdater, CancellationToken.None); do { await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); }while (round.Phase != Phase.ConnectionConfirmation); var aliceClient = await task; await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.OutputRegistration, round.Phase); using var destinationKey = new Key(); var destination = destinationKey.PubKey.WitHash.ScriptPubKey; var bobClient = new BobClient(round.Id, bobArenaClient); await bobClient.RegisterOutputAsync( destination, aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), aliceClient.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); var bob = Assert.Single(round.Bobs); Assert.Equal(destination, bob.Script); var credentialAmountSum = aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber).Sum(x => x.Value); Assert.Equal(credentialAmountSum, bob.CredentialAmount); }
public async Task RegisterOutputTestAsync() { var config = new WabiSabiConfig { MaxInputCountByRound = 1 }; var round = WabiSabiFactory.CreateRound(config); var km = ServiceFactory.CreateKeyManager(""); var key = BitcoinFactory.CreateHdPubKey(km); SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m)); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin); using Arena arena = await ArenaBuilder.From(config).With(mockRpc).CreateAndStartAsync(round); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); using var memoryCache = new MemoryCache(new MemoryCacheOptions()); var idempotencyRequestCache = new IdempotencyRequestCache(memoryCache); using CoinJoinFeeRateStatStore coinJoinFeeRateStatStore = new(config, arena.Rpc); var wabiSabiApi = new WabiSabiController(idempotencyRequestCache, arena, coinJoinFeeRateStatStore); InsecureRandom insecureRandom = InsecureRandom.Instance; var roundState = RoundState.FromRound(round); var aliceArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); var bobArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(insecureRandom), roundState.CreateVsizeCredentialClient(insecureRandom), wabiSabiApi); Assert.Equal(Phase.InputRegistration, round.Phase); using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi); await roundStateUpdater.StartAsync(CancellationToken.None); var keyChain = new KeyChain(km, new Kitchen("")); var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), aliceArenaClient, coin1, keyChain, roundStateUpdater, CancellationToken.None); do { await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); }while (round.Phase != Phase.ConnectionConfirmation); var aliceClient = await task; await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.OutputRegistration, round.Phase); using var destinationKey = new Key(); var destination = destinationKey.PubKey.WitHash.ScriptPubKey; var bobClient = new BobClient(round.Id, bobArenaClient); await bobClient.RegisterOutputAsync( destination, aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber), aliceClient.IssuedVsizeCredentials.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); var bob = Assert.Single(round.Bobs); Assert.Equal(destination, bob.Script); var credentialAmountSum = aliceClient.IssuedAmountCredentials.Take(ProtocolConstants.CredentialNumber).Sum(x => x.Value); Assert.Equal(credentialAmountSum, bob.CredentialAmount); }
public async Task RegisterOutputTestAsync() { 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)); var km = ServiceFactory.CreateKeyManager(""); var key = BitcoinFactory.CreateHdPubKey(km); SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m)); var outpoint = coin1.OutPoint; var mockRpc = new Mock <IRPCClient>(); mockRpc.Setup(rpc => rpc.GetTxOutAsync(outpoint.Hash, (int)outpoint.N, true)) .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse { IsCoinBase = false, Confirmations = coin1.Height, TxOut = coin1.TxOut, }); CredentialPool amountCredential = new(); CredentialPool vsizeCredential = new(); await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object); var aliceArenaClient = new ArenaClient(round.AmountCredentialIssuerParameters, round.VsizeCredentialIssuerParameters, amountCredential, vsizeCredential, coordinator, new InsecureRandom()); var bobArenaClient = new ArenaClient(round.AmountCredentialIssuerParameters, round.VsizeCredentialIssuerParameters, amountCredential, vsizeCredential, coordinator, new InsecureRandom()); Assert.Equal(Phase.InputRegistration, round.Phase); var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main); var aliceClient = await AliceClient.CreateNewAsync(aliceArenaClient, new[] { coin1.Coin }, bitcoinSecret, round.Id, round.Hash, round.FeeRate); Task confirmationTask = aliceClient.ConfirmConnectionAsync(TimeSpan.FromSeconds(3), CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); await confirmationTask; Assert.Equal(Phase.ConnectionConfirmation, round.Phase); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.OutputRegistration, round.Phase); using var destinationKey1 = new Key(); using var destinationKey2 = new Key(); using var destinationKey3 = new Key(); using var destinationKey4 = new Key(); var bobClient = new BobClient(round.Id, bobArenaClient); await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destinationKey1.PubKey.WitHash.ScriptPubKey); await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destinationKey2.PubKey.WitHash.ScriptPubKey); await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destinationKey3.PubKey.WitHash.ScriptPubKey); await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destinationKey4.PubKey.WitHash.ScriptPubKey); Assert.Equal(4, round.Bobs.Count); }
public async Task RegisterOutputTestAsync() { var config = new WabiSabiConfig { MaxInputCountByRound = 1 }; var round = WabiSabiFactory.CreateRound(config); var km = ServiceFactory.CreateKeyManager(""); var key = BitcoinFactory.CreateHdPubKey(km); SmartCoin coin1 = BitcoinFactory.CreateSmartCoin(key, Money.Coins(2m)); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(config, mockRpc, round); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); ZeroCredentialPool zeroAmountCredential = new(); ZeroCredentialPool zeroVsizeCredential = new(); await using var coordinator = new ArenaRequestHandler(config, new Prison(), arena, mockRpc.Object); var insecureRandom = new InsecureRandom(); var wabiSabiApi = new WabiSabiController(coordinator); var roundState = RoundState.FromRound(round); var aliceArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(zeroAmountCredential, insecureRandom), roundState.CreateVsizeCredentialClient(zeroVsizeCredential, insecureRandom), wabiSabiApi); var bobArenaClient = new ArenaClient( roundState.CreateAmountCredentialClient(zeroAmountCredential, insecureRandom), roundState.CreateVsizeCredentialClient(zeroVsizeCredential, insecureRandom), wabiSabiApi); Assert.Equal(Phase.InputRegistration, round.Phase); var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main); var aliceClient = new AliceClient(round.Id, aliceArenaClient, coin1.Coin, round.FeeRate, bitcoinSecret); await aliceClient.RegisterInputAsync(CancellationToken.None); Task confirmationTask = aliceClient.ConfirmConnectionAsync(TimeSpan.FromSeconds(1), roundState.MaxVsizeAllocationPerAlice, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); await confirmationTask; Assert.Equal(Phase.ConnectionConfirmation, round.Phase); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1)); Assert.Equal(Phase.OutputRegistration, round.Phase); using var destinationKey = new Key(); var destination = destinationKey.PubKey.WitHash.ScriptPubKey; var bobClient = new BobClient(round.Id, bobArenaClient); await bobClient.RegisterOutputAsync(Money.Coins(0.25m), destination, aliceClient.RealAmountCredentials, aliceClient.RealVsizeCredentials, CancellationToken.None); var bob = Assert.Single(round.Bobs); Assert.Equal(destination, bob.Script); Assert.Equal(25_000_000, bob.CredentialAmount); }
public async Task DiffTooSmallToBlameAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5, OutputRegistrationTimeout = TimeSpan.Zero }; using Key key1 = new(); using Key key2 = new(); var coin1 = WabiSabiFactory.CreateCoin(key1); var coin2 = WabiSabiFactory.CreateCoin(key2); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1, coin2); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc); var(round, arenaClient, alices) = await CreateRoundWithTwoConfirmedConnectionsAsync(arena, key1, coin1, key2, coin2).ConfigureAwait(false); var(realAmountCredentials1, realVsizeCredentials1) = alices[0]; var(realAmountCredentials2, realVsizeCredentials2) = alices[1]; // Register outputs. var bobClient = new BobClient(round.Id, arenaClient); using var destKey1 = new Key(); using var destKey2 = new Key(); await bobClient.RegisterOutputAsync( coin1.Amount - round.FeeRate.GetFee(coin1.ScriptPubKey.EstimateInputVsize()), destKey1.PubKey.WitHash.ScriptPubKey, realAmountCredentials1, realVsizeCredentials1, CancellationToken.None).ConfigureAwait(false); await bobClient.RegisterOutputAsync( coin2.Amount - round.FeeRate.GetFee(coin2.ScriptPubKey.EstimateInputVsize()), destKey2.PubKey.WitHash.ScriptPubKey, realAmountCredentials2, realVsizeCredentials2, CancellationToken.None).ConfigureAwait(false); // Add another input. The input must be able to pay for itself, but // the remaining amount after deducting the fees needs to be less // than the minimum. var txParams = round.Assert <ConstructionState>().Parameters; var extraAlice = WabiSabiFactory.CreateAlice(txParams.FeeRate.GetFee(Constants.P2wpkhInputVirtualSize) + txParams.AllowedOutputAmounts.Min - new Money(1L)); round.Alices.Add(extraAlice); round.CoinjoinState = round.Assert <ConstructionState>().AddInput(extraAlice.Coin); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var tx = round.Assert <SigningState>().CreateTransaction(); Assert.Equal(3, tx.Inputs.Count); Assert.Equal(2, tx.Outputs.Count); Assert.DoesNotContain(cfg.BlameScript, tx.Outputs.Select(x => x.ScriptPubKey)); await arena.StopAsync(CancellationToken.None); }