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);
    }
예제 #3
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);
    }
예제 #4
0
    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);
    }
예제 #5
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);
    }
    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);
        }
예제 #9
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);
    }
        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);
        }