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 TimeoutSufficientPeersAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 1, TransactionSigningTimeout = TimeSpan.Zero, OutputRegistrationTimeout = TimeSpan.Zero }; var(keyChain, coin1, coin2) = WabiSabiFactory.CreateCoinKeyPairs(); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin, coin2.Coin); mockRpc.Setup(rpc => rpc.SendRawTransactionAsync(It.IsAny <Transaction>(), It.IsAny <CancellationToken>())) .ThrowsAsync(new RPCException(RPCErrorCode.RPC_TRANSACTION_REJECTED, "", null)); Prison prison = new(); using Arena arena = await ArenaBuilder.From(cfg, mockRpc, prison).CreateAndStartAsync(); var(round, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, keyChain, coin1, coin2); // Make sure not all alices signed. var alice3 = WabiSabiFactory.CreateAlice(round); alice3.ConfirmedConnection = true; round.Alices.Add(alice3); round.CoinjoinState = round.Assert <ConstructionState>().AddInput(alice3.Coin); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var signedCoinJoin = round.Assert <SigningState>().CreateTransaction(); await aliceClient1.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await aliceClient2.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase != Phase.Ended)); Assert.Single(arena.Rounds.Where(x => x is BlameRound)); var badOutpoint = alice3.Coin.Outpoint; Assert.Contains(badOutpoint, prison.GetInmates().Select(x => x.Utxo)); var onlyRound = arena.Rounds.Single(x => x is BlameRound); var blameRound = Assert.IsType <BlameRound>(onlyRound); Assert.NotNull(blameRound.BlameOf); Assert.Equal(round.Id, blameRound.BlameOf.Id); var whitelist = blameRound.BlameWhitelist; Assert.Contains(aliceClient1.SmartCoin.OutPoint, whitelist); Assert.Contains(aliceClient2.SmartCoin.OutPoint, whitelist); Assert.DoesNotContain(badOutpoint, whitelist); await arena.StopAsync(CancellationToken.None); }
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, }; 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(); await bobClient.RegisterOutputAsync( destKey1.PubKey.WitHash.ScriptPubKey, amountCredentials1.Take(ProtocolConstants.CredentialNumber), vsizeCredentials1.Take(ProtocolConstants.CredentialNumber), CancellationToken.None); using var destKey2 = new Key(); await bobClient.RegisterOutputAsync( 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 + 1, tx.Outputs.Count); // +1 for the coordinator fee await arena.StopAsync(CancellationToken.None); }
public async Task AlicesSpentAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5, MaxSuggestedAmountBase = Money.Satoshis(ProtocolConstants.MaxAmountPerAlice) }; var(keyChain, coin1, coin2) = WabiSabiFactory.CreateCoinKeyPairs(); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin, coin2.Coin); mockRpc.Setup(rpc => rpc.SendRawTransactionAsync(It.IsAny <Transaction>(), It.IsAny <CancellationToken>())) .ThrowsAsync(new RPCException(RPCErrorCode.RPC_TRANSACTION_REJECTED, "", null)); Prison prison = new(); using Arena arena = await ArenaBuilder.From(cfg, mockRpc, prison).CreateAndStartAsync(); var(round, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, keyChain, coin1, coin2); await aliceClient1.ReadyToSignAsync(CancellationToken.None); await aliceClient2.ReadyToSignAsync(CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var signedCoinJoin = round.Assert <SigningState>().CreateTransaction(); await aliceClient1.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await aliceClient2.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase != Phase.Ended)); Assert.Equal(Phase.Ended, round.Phase); Assert.False(round.WasTransactionBroadcast); // There should be no inmate, because we aren't punishing spenders with banning // as there's no reason to ban already spent UTXOs, // the cost of spending the UTXO is the punishment instead. Assert.Empty(prison.GetInmates()); await arena.StopAsync(CancellationToken.None); }
public async Task FailsBroadcastAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5 }; var(keyChain, coin1, coin2) = WabiSabiFactory.CreateCoinKeyPairs(); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin, coin2.Coin); mockRpc.Setup(rpc => rpc.SendRawTransactionAsync(It.IsAny <Transaction>(), It.IsAny <CancellationToken>())) .ThrowsAsync(new RPCException(RPCErrorCode.RPC_TRANSACTION_REJECTED, "", null)); Prison prison = new(); using Arena arena = await ArenaBuilder.From(cfg, mockRpc, prison).CreateAndStartAsync(); var(round, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, keyChain, coin1, coin2); await aliceClient1.ReadyToSignAsync(CancellationToken.None); await aliceClient2.ReadyToSignAsync(CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var signedCoinJoin = round.Assert <SigningState>().CreateTransaction(); await aliceClient1.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await aliceClient2.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase != Phase.Ended)); Assert.Equal(Phase.Ended, round.Phase); Assert.False(round.WasTransactionBroadcast); Assert.Empty(prison.GetInmates()); await arena.StopAsync(CancellationToken.None); }
public async Task TimeoutInsufficientPeersAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 1, TransactionSigningTimeout = TimeSpan.Zero, MaxSuggestedAmountBase = Money.Satoshis(ProtocolConstants.MaxAmountPerAlice) }; var(keyChain, coin1, coin2) = WabiSabiFactory.CreateCoinKeyPairs(); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin, coin2.Coin); mockRpc.Setup(rpc => rpc.SendRawTransactionAsync(It.IsAny <Transaction>(), It.IsAny <CancellationToken>())) .ThrowsAsync(new RPCException(RPCErrorCode.RPC_TRANSACTION_REJECTED, "", null)); Prison prison = new(); using Arena arena = await ArenaBuilder.From(cfg, mockRpc, prison).CreateAndStartAsync(); var(round, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, keyChain, coin1, coin2); await aliceClient1.ReadyToSignAsync(CancellationToken.None); await aliceClient2.ReadyToSignAsync(CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var signedCoinJoin = round.Assert <SigningState>().CreateTransaction(); await aliceClient1.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.DoesNotContain(round, arena.Rounds.Where(x => x.Phase != Phase.Ended)); Assert.Equal(Phase.Ended, round.Phase); Assert.Equal(EndRoundState.AbortedNotEnoughAlicesSigned, round.EndRoundState); Assert.Empty(arena.Rounds.Where(x => x is BlameRound)); Assert.Contains(aliceClient2.SmartCoin.OutPoint, prison.GetInmates().Select(x => x.Utxo)); await arena.StopAsync(CancellationToken.None); }
public async Task TimeoutInsufficientPeersAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 1, TransactionSigningTimeout = TimeSpan.Zero }; var(key1, coin1, key2, coin2) = WabiSabiFactory.CreateCoinKeyPairs(); var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin1.Coin, coin2.Coin); mockRpc.Setup(rpc => rpc.SendRawTransactionAsync(It.IsAny <Transaction>())) .ThrowsAsync(new RPCException(RPCErrorCode.RPC_TRANSACTION_REJECTED, "", null)); using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc); var(round, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, key1, coin1, key2, coin2); await aliceClient1.ReadyToSignAsync(CancellationToken.None); await aliceClient2.ReadyToSignAsync(CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var signedCoinJoin = round.Assert <SigningState>().CreateTransaction(); await aliceClient1.SignTransactionAsync(signedCoinJoin, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.DoesNotContain(round, arena.ActiveRounds); Assert.Equal(Phase.Ended, round.Phase); Assert.False(round.WasTransactionBroadcast); Assert.Empty(arena.Rounds.Where(x => x.IsBlameRound)); Assert.Contains(aliceClient2.SmartCoin.OutPoint, arena.Prison.GetInmates().Select(x => x.Utxo)); await arena.StopAsync(CancellationToken.None); }
public async Task EveryoneSignedAsync() { WabiSabiConfig cfg = new() { MaxInputCountByRound = 2, MinInputCountByRoundMultiplier = 0.5, MaxSuggestedAmountBase = Money.Satoshis(ProtocolConstants.MaxAmountPerAlice) }; 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, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, keyChain, coin1, coin2); await aliceClient1.ReadyToSignAsync(CancellationToken.None); await aliceClient2.ReadyToSignAsync(CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var signedCoinJoin = round.Assert <SigningState>().CreateTransaction(); await aliceClient1.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await aliceClient2.SignTransactionAsync(signedCoinJoin, keyChain, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.DoesNotContain(round, arena.GetActiveRounds()); Assert.Equal(Phase.Ended, round.Phase); await arena.StopAsync(CancellationToken.None); }
public async Task EveryoneSignedAsync() { 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, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, key1, coin1, key2, coin2); await aliceClient1.ReadyToSignAsync(CancellationToken.None); await aliceClient2.ReadyToSignAsync(CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.Equal(Phase.TransactionSigning, round.Phase); var signedCoinJoin = round.Assert <SigningState>().CreateTransaction(); await aliceClient1.SignTransactionAsync(signedCoinJoin, CancellationToken.None); await aliceClient2.SignTransactionAsync(signedCoinJoin, CancellationToken.None); await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)); Assert.DoesNotContain(round, arena.ActiveRounds); Assert.Equal(Phase.Ended, round.Phase); await arena.StopAsync(CancellationToken.None); }