Example #1
0
        public async Task InputWhitelistedButBannedAsync()
        {
            using Key key = new();
            var alice      = WabiSabiFactory.CreateAlice(key);
            var bannedCoin = alice.Coin.Outpoint;

            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            round.Alices.Add(alice);
            Round blameRound = WabiSabiFactory.CreateBlameRound(round, cfg);
            var   mockRpc    = WabiSabiFactory.CreatePreconfiguredRpcClient(alice.Coin);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, mockRpc, round, blameRound);

            arena.Prison.Punish(bannedCoin, Punishment.Banned, uint256.Zero);
            await using ArenaRequestHandler handler = new(cfg, arena.Prison, arena, mockRpc.Object);

            var req = WabiSabiFactory.CreateInputRegistrationRequest(key: key, round: blameRound, prevout: bannedCoin);
            var ex  = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() => await handler.RegisterInputAsync(req, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.InputBanned, ex.ErrorCode);

            await arena.StopAsync(CancellationToken.None);
        }
        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 InputRegistrationFullAsync()
        {
            WabiSabiConfig cfg   = new() { MaxInputCountByRound = 3 };
            var            round = WabiSabiFactory.CreateRound(cfg);

            using Key key = new();
            var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, WabiSabiFactory.CreatePreconfiguredRpcClient(), round);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            round.Alices.Add(WabiSabiFactory.CreateAlice(round));
            round.Alices.Add(WabiSabiFactory.CreateAlice(round));
            round.Alices.Add(WabiSabiFactory.CreateAlice(round));

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var ex          = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(round.Id, BitcoinFactory.CreateOutPoint(), ownershipProof, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
            Assert.Equal(Phase.InputRegistration, round.Phase);

            await arena.StopAsync(CancellationToken.None);
        }
Example #5
0
        public async Task WrongPhaseAsync()
        {
            WabiSabiConfig cfg = new();

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)).ConfigureAwait(false);

            var round = arena.Rounds.First();

            using Key key = new();
            var req = WabiSabiFactory.CreateInputRegistrationRequest(round, key: key);

            await using ArenaRequestHandler handler = new(cfg, new(), arena, WabiSabiFactory.CreatePreconfiguredRpcClient().Object);

            foreach (Phase phase in Enum.GetValues(typeof(Phase)))
            {
                if (phase != Phase.InputRegistration)
                {
                    round.SetPhase(phase);
                    await RegisterAndAssertWrongPhaseAsync(req, handler);
                }
            }

            await arena.StopAsync(CancellationToken.None);
        }
Example #6
0
    public async Task InputImmatureAsync()
    {
        using Key key = new();
        WabiSabiConfig cfg            = new();
        var            round          = WabiSabiFactory.CreateRound(cfg);
        var            ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

        var rpc    = WabiSabiFactory.CreatePreconfiguredRpcClient();
        var rpcCfg = rpc.SetupSequence(rpc => rpc.GetTxOutAsync(It.IsAny <uint256>(), It.IsAny <int>(), It.IsAny <bool>(), It.IsAny <CancellationToken>()));

        foreach (var i in Enumerable.Range(1, 100))
        {
            rpcCfg = rpcCfg.ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse {
                Confirmations = i, IsCoinBase = true
            });
        }
        using Arena arena = await ArenaBuilder.From(cfg).With(rpc).CreateAndStartAsync(round);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

        var req = WabiSabiFactory.CreateInputRegistrationRequest(round: round);

        foreach (var i in Enumerable.Range(1, 100))
        {
            var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(round.Id, BitcoinFactory.CreateOutPoint(), ownershipProof, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.InputImmature, ex.ErrorCode);
        }

        await arena.StopAsync(CancellationToken.None);
    }
    public async Task SuccessFromPreviousCoinJoinAsync()
    {
        WabiSabiConfig cfg   = new();
        var            round = WabiSabiFactory.CreateRound(cfg);

        using Key key = new();
        var coin             = WabiSabiFactory.CreateCoin(key);
        var rpc              = WabiSabiFactory.CreatePreconfiguredRpcClient(coin);
        var coinJoinIdsStore = new InMemoryCoinJoinIdStore();

        coinJoinIdsStore.Add(coin.Outpoint.Hash);
        using Arena arena = await ArenaBuilder.From(cfg).With(rpc).With(coinJoinIdsStore).CreateAndStartAsync(round);

        var minAliceDeadline = DateTimeOffset.UtcNow + cfg.ConnectionConfirmationTimeout * 0.9;
        var arenaClient      = WabiSabiFactory.CreateArenaClient(arena);
        var ownershipProof   = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

        var(resp, _) = await arenaClient.RegisterInputAsync(round.Id, coin.Outpoint, ownershipProof, CancellationToken.None);

        AssertSingleAliceSuccessfullyRegistered(round, minAliceDeadline, resp);

        var myAlice = Assert.Single(round.Alices);

        Assert.True(myAlice.IsPayingZeroCoordinationFee);

        await arena.StopAsync(CancellationToken.None);
    }
    public async Task DetectSpentTxoBeforeSteppingIntoConnectionConfirmationAsync()
    {
        WabiSabiConfig cfg            = new() { MaxInputCountByRound = 3 };
        var            round          = WabiSabiFactory.CreateRound(cfg);
        var            offendingAlice = WabiSabiFactory.CreateAlice(round); // this Alice spent the coin after registration

        var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient();

        mockRpc.Setup(rpc => rpc.GetTxOutAsync(offendingAlice.Coin.Outpoint.Hash, (int)offendingAlice.Coin.Outpoint.N, true, It.IsAny <CancellationToken>()))
        .ReturnsAsync((GetTxOutResponse?)null);

        using Arena arena = await ArenaBuilder.From(cfg).With(mockRpc).CreateAndStartAsync(round);

        round.Alices.Add(WabiSabiFactory.CreateAlice(round));
        round.Alices.Add(offendingAlice);
        round.Alices.Add(WabiSabiFactory.CreateAlice(round));
        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

        Assert.Equal(Phase.InputRegistration, round.Phase);
        Assert.Equal(2, round.Alices.Count);         // the offending alice was removed

        round.Alices.Add(WabiSabiFactory.CreateAlice(round));
        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

        Assert.Equal(Phase.ConnectionConfirmation, round.Phase);
        Assert.Equal(3, round.Alices.Count);

        await arena.StopAsync(CancellationToken.None);
    }
    public async Task SuccessWithAliceUpdateIntraRoundAsync()
    {
        WabiSabiConfig cfg   = new();
        var            round = WabiSabiFactory.CreateRound(cfg);

        using Key key = new();
        var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);
        var coin           = WabiSabiFactory.CreateCoin(key);

        // Make sure an Alice have already been registered with the same input.
        var preAlice = WabiSabiFactory.CreateAlice(coin, WabiSabiFactory.CreateOwnershipProof(key), round);

        round.Alices.Add(preAlice);

        var rpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin);

        using Arena arena = await ArenaBuilder.From(cfg).With(rpc).CreateAndStartAsync(round);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
        var ex          = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() => await arenaClient.RegisterInputAsync(round.Id, coin.Outpoint, ownershipProof, CancellationToken.None).ConfigureAwait(false));

        Assert.Equal(WabiSabiProtocolErrorCode.AliceAlreadyRegistered, ex.ErrorCode);

        await arena.StopAsync(CancellationToken.None);
    }
Example #10
0
    public async Task InputRegistrationTimedoutAsync()
    {
        WabiSabiConfig cfg   = new() { StandardInputRegistrationTimeout = TimeSpan.Zero };
        var            round = WabiSabiFactory.CreateRound(cfg);

        using Key key = new();
        var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

        var coin = WabiSabiFactory.CreateCoin(key);
        var rpc  = WabiSabiFactory.CreatePreconfiguredRpcClient(coin);

        using Arena arena = await ArenaBuilder.From(cfg).With(rpc).CreateAndStartAsync();

        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

        arena.Rounds.Add(round);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
        var ex          = await Assert.ThrowsAsync <WrongPhaseException>(
            async() => await arenaClient.RegisterInputAsync(round.Id, coin.Outpoint, ownershipProof, CancellationToken.None));

        Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
        Assert.Equal(Phase.InputRegistration, round.Phase);

        await arena.StopAsync(CancellationToken.None);
    }
Example #11
0
        public async Task AliceTimesoutAsync()
        {
            // Alice times out when its deadline is reached.
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            using Key key = new();
            var coin = WabiSabiFactory.CreateCoin(key);
            var rpc  = WabiSabiFactory.CreatePreconfiguredRpcClient(coin);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, rpc, round);

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

            // Register Alices.
            var minAliceDeadline = DateTimeOffset.UtcNow + cfg.ConnectionConfirmationTimeout * 0.9;
            var aliceClient      = new AliceClient(round.Id, arenaClient, coin, round.FeeRate, key.GetBitcoinSecret(round.Network));
            await aliceClient.RegisterInputAsync(CancellationToken.None).ConfigureAwait(false);

            var alice = Assert.Single(round.Alices);

            alice.Deadline = DateTimeOffset.UtcNow - TimeSpan.FromMilliseconds(1);
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Empty(round.Alices);

            await arena.StopAsync(CancellationToken.None);
        }
Example #12
0
        public async Task EveryoneSignedAsync()
        {
            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, aliceClient1, aliceClient2) = await CreateRoundWithOutputsReadyToSignAsync(arena, key1, coin1, key2, coin2).ConfigureAwait(false);

            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.Equal(Phase.TransactionBroadcasting, round.Phase);

            await arena.StopAsync(CancellationToken.None);
        }
        public async Task InputRegistrationTimeoutCanBeModifiedRuntimeAsync()
        {
            WabiSabiConfig cfg = new() { StandardInputRegistrationTimeout = TimeSpan.FromHours(1) };

            using Key key = new();
            var coin = WabiSabiFactory.CreateCoin(key);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, WabiSabiFactory.CreatePreconfiguredRpcClient(coin));

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var round          = arena.Rounds.First();
            var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

            round.InputRegistrationTimeFrame = round.InputRegistrationTimeFrame with {
                Duration = TimeSpan.Zero
            };

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var ex          = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(round.Id, coin.Outpoint, ownershipProof, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
            Assert.Equal(Phase.InputRegistration, round.Phase);

            await arena.StopAsync(CancellationToken.None);
        }
Example #14
0
    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);
        }
    public async Task AliceTimesoutAsync()
    {
        // Alice times out when its deadline is reached.
        WabiSabiConfig cfg       = new();
        var            round     = WabiSabiFactory.CreateRound(cfg);
        var            km        = ServiceFactory.CreateKeyManager("");
        var            key       = BitcoinFactory.CreateHdPubKey(km);
        var            smartCoin = BitcoinFactory.CreateSmartCoin(key, 10m);
        var            rpc       = WabiSabiFactory.CreatePreconfiguredRpcClient(smartCoin.Coin);

        using Arena arena = await ArenaBuilder.From(cfg).With(rpc).CreateAndStartAsync(round);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

        using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena);
        await roundStateUpdater.StartAsync(CancellationToken.None);

        // Register Alices.
        var keyChain = new KeyChain(km, new Kitchen(""));

        using CancellationTokenSource cancellationTokenSource = new();
        var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, smartCoin, keyChain, roundStateUpdater, cancellationTokenSource.Token);

        while (round.Alices.Count == 0)
        {
            await Task.Delay(10);
        }

        var alice = Assert.Single(round.Alices);

        alice.Deadline = DateTimeOffset.UtcNow - TimeSpan.FromMilliseconds(1);
        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

        Assert.Empty(round.Alices);

        cancellationTokenSource.Cancel();

        try
        {
            await task;
            throw new InvalidOperationException("The operation should throw!");
        }
        catch (Exception exc)
        {
            Assert.True(exc is OperationCanceledException or WabiSabiProtocolException);
        }

        await roundStateUpdater.StopAsync(CancellationToken.None);

        await arena.StopAsync(CancellationToken.None);
    }
        public async Task AlicesSpentAsync()
        {
            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);

            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 aliceClient2.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);

            // 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(arena.Prison.GetInmates());

            await arena.StopAsync(CancellationToken.None);
        }
Example #18
0
    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);
    }
Example #19
0
        public async Task CreateNewAsync()
        {
            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(1m));

            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 wabiSabiApi = new WabiSabiController(coordinator);

            var insecureRandom = new InsecureRandom();
            var roundState     = RoundState.FromRound(round);
            var arenaClient    = new ArenaClient(
                roundState.CreateAmountCredentialClient(insecureRandom),
                roundState.CreateVsizeCredentialClient(insecureRandom),
                wabiSabiApi);

            Assert.Equal(Phase.InputRegistration, arena.Rounds.First().Phase);

            var bitcoinSecret = km.GetSecrets("", coin1.ScriptPubKey).Single().PrivateKey.GetBitcoinSecret(Network.Main);

            var aliceClient = new AliceClient(round.Id, arenaClient, coin1.Coin, round.FeeRate, bitcoinSecret);
            await aliceClient.RegisterInputAsync(CancellationToken.None);

            using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), wabiSabiApi);
            Task confirmationTask = aliceClient.ConfirmConnectionAsync(
                TimeSpan.FromSeconds(1),
                new long[] { coin1.EffectiveValue(round.FeeRate) },
                new long[] { roundState.MaxVsizeAllocationPerAlice - coin1.ScriptPubKey.EstimateInputVsize() },
                roundStateUpdater,
                CancellationToken.None);

            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));

            await confirmationTask;

            Assert.Equal(Phase.ConnectionConfirmation, arena.Rounds.First().Phase);
        }
        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 SuccessAsync()
        {
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            using Key key = new();
            var coin = WabiSabiFactory.CreateCoin(key);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, WabiSabiFactory.CreatePreconfiguredRpcClient(coin), round);

            var minAliceDeadline = DateTimeOffset.UtcNow + cfg.ConnectionConfirmationTimeout * 0.9;
            var arenaClient      = WabiSabiFactory.CreateArenaClient(arena);
            var resp             = await arenaClient.RegisterInputAsync(round.Id, coin.Outpoint, key, CancellationToken.None);

            AssertSingleAliceSuccessfullyRegistered(round, minAliceDeadline, resp);

            await arena.StopAsync(CancellationToken.None);
        }
        public async Task TimeoutInsufficientPeersAsync()
        {
            WabiSabiConfig cfg = new()
            {
                MaxInputCountByRound           = 2,
                MinInputCountByRoundMultiplier = 1,
                TransactionSigningTimeout      = 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);

            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.Coin.Outpoint, arena.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,
            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 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 AliceTimesoutAsync()
        {
            // Alice times out when its deadline is reached.
            WabiSabiConfig cfg       = new();
            var            round     = WabiSabiFactory.CreateRound(cfg);
            var            km        = ServiceFactory.CreateKeyManager("");
            var            key       = BitcoinFactory.CreateHdPubKey(km);
            var            smartCoin = BitcoinFactory.CreateSmartCoin(key, 10m);
            var            rpc       = WabiSabiFactory.CreatePreconfiguredRpcClient(smartCoin.Coin);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, rpc, round);

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

            using RoundStateUpdater roundStateUpdater = new(TimeSpan.FromSeconds(2), arena);
            await roundStateUpdater.StartAsync(CancellationToken.None);

            // Register Alices.
            using var identificationKey = new Key();
            var esk = km.GetSecrets("", smartCoin.ScriptPubKey).Single();

            using CancellationTokenSource cancellationTokenSource = new();
            var task = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, smartCoin, esk.PrivateKey.GetBitcoinSecret(round.Network), identificationKey, roundStateUpdater, cancellationTokenSource.Token);

            while (round.Alices.Count == 0)
            {
                await Task.Delay(10);
            }

            var alice = Assert.Single(round.Alices);

            alice.Deadline = DateTimeOffset.UtcNow - TimeSpan.FromMilliseconds(1);
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            Assert.Empty(round.Alices);

            cancellationTokenSource.Cancel();
            await Assert.ThrowsAsync <OperationCanceledException>(async() => await task);

            await roundStateUpdater.StopAsync(CancellationToken.None);

            await arena.StopAsync(CancellationToken.None);
        }
Example #26
0
    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);
    }
Example #27
0
        public async Task InputNotWhitelistedAsync()
        {
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            round.Alices.Add(WabiSabiFactory.CreateAlice(round));
            Round blameRound = WabiSabiFactory.CreateBlameRound(round, cfg);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round, blameRound);

            using Key key = new();
            var mockRpc = WabiSabiFactory.CreatePreconfiguredRpcClient();

            var req = WabiSabiFactory.CreateInputRegistrationRequest(round: blameRound);
            var ex  = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() => await arena.RegisterInputAsync(req, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.InputNotWhitelisted, ex.ErrorCode);

            await arena.StopAsync(CancellationToken.None);
        }
Example #28
0
    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);
    }
Example #29
0
        public async Task InputWhitelistedAsync()
        {
            var            alice = WabiSabiFactory.CreateAlice();
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            round.Alices.Add(alice);
            Round blameRound = WabiSabiFactory.CreateBlameRound(round, cfg);

            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round, blameRound);

            await using ArenaRequestHandler handler = new(cfg, new(), arena, WabiSabiFactory.CreatePreconfiguredRpcClient().Object);
            var req = WabiSabiFactory.CreateInputRegistrationRequest(prevout: alice.Coin.Outpoint, round: blameRound);

            var ex = await Assert.ThrowsAnyAsync <Exception>(async() => await handler.RegisterInputAsync(req, CancellationToken.None));

            if (ex is WabiSabiProtocolException wspex)
            {
                Assert.NotEqual(WabiSabiProtocolErrorCode.InputNotWhitelisted, wspex.ErrorCode);
            }

            await arena.StopAsync(CancellationToken.None);
        }
        public async Task RoundNotFoundAsync()
        {
            using Key key     = new();
            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(new(), WabiSabiFactory.CreatePreconfiguredRpcClient());

            var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key);

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var ex          = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(uint256.Zero, BitcoinFactory.CreateOutPoint(), ownershipProof, CancellationToken.None));

            Assert.Equal(WabiSabiProtocolErrorCode.RoundNotFound, ex.ErrorCode);

            await arena.StopAsync(CancellationToken.None);
        }