Beispiel #1
0
    public async Task InputCanBeNotedAsync()
    {
        using Key key = new();
        var outpoint = BitcoinFactory.CreateOutPoint();

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

        Prison prison = new();

        using Arena arena = await ArenaBuilder.From(cfg, prison).CreateAndStartAsync(round);

        prison.Punish(outpoint, Punishment.Noted, uint256.One);

        var ownershipProof = WabiSabiFactory.CreateOwnershipProof(key);

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

        var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(
            async() => await arenaClient.RegisterInputAsync(round.Id, outpoint, ownershipProof, CancellationToken.None));

        Assert.NotEqual(WabiSabiProtocolErrorCode.InputBanned, 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 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);
    }
Beispiel #4
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);
    }
Beispiel #5
0
    public async Task InputUnconfirmedAsync()
    {
        using Key key = new();
        WabiSabiConfig cfg            = new();
        var            round          = WabiSabiFactory.CreateRound(cfg);
        var            ownershipProof = WabiSabiFactory.CreateOwnershipProof(key, round.Id);

        var mockRpc = new Mock <IRPCClient>();

        mockRpc.Setup(rpc => rpc.GetTxOutAsync(It.IsAny <uint256>(), It.IsAny <int>(), It.IsAny <bool>(), It.IsAny <CancellationToken>()))
        .ReturnsAsync(new NBitcoin.RPC.GetTxOutResponse {
            Confirmations = 0
        });

        using Arena arena = await ArenaBuilder.From(cfg).With(mockRpc).CreateAndStartAsync(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.InputUnconfirmed, ex.ErrorCode);

        await arena.StopAsync(CancellationToken.None);
    }
Beispiel #6
0
    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 ArenaBuilder.From(cfg).CreateAndStartAsync(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 <WrongPhaseException>(
            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);
    }
Beispiel #7
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);
    }
        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);
        }
        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);
        }
        CreateRoundWithTwoConfirmedConnectionsAsync(Arena arena, Key key1, Coin coin1, Key key2, Coin coin2)
        {
            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var round       = Assert.Single(arena.Rounds);

            // 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.FromSeconds(1), round.MaxVsizeAllocationPerAlice, CancellationToken.None).ConfigureAwait(false);

            await aliceClient2.ConfirmConnectionAsync(TimeSpan.FromSeconds(1), round.MaxVsizeAllocationPerAlice, CancellationToken.None).ConfigureAwait(false);

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

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            return(round,
                   arenaClient,
                   new[]
Beispiel #11
0
        public async Task RoundNotFoundAsync()
        {
            using Key key     = new();
            using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(new(), WabiSabiFactory.CreatePreconfiguredRpcClient());

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

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

            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);
    }
Beispiel #13
0
        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);
        }
Beispiel #14
0
        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);
        }
Beispiel #15
0
    public async Task RoundNotFoundAsync()
    {
        using Key key     = new();
        using Arena arena = await ArenaBuilder.Default.CreateAndStartAsync();

        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);
    }
        CreateRoundWithTwoConfirmedConnectionsAsync(Arena arena, Key key1, Coin coin1, Key key2, Coin coin2)
        {
            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var round       = Assert.Single(arena.Rounds);

            // 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.FromSeconds(1),
                new long[] { coin1.EffectiveValue(round.FeeRate) },
                new long[] { round.MaxVsizeAllocationPerAlice - coin1.ScriptPubKey.EstimateInputVsize() },
                roundStateUpdater,
                CancellationToken.None).ConfigureAwait(false);

            await aliceClient2.ConfirmConnectionAsync(
                TimeSpan.FromSeconds(1),
                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);

            return(round,
                   arenaClient,
                   new[]
            {
                aliceClient1,
                aliceClient2
            });
        }
        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 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);
        }
Beispiel #19
0
        public async Task InputCantBeNotedAsync()
        {
            using Key key = new();
            var outpoint = BitcoinFactory.CreateOutPoint();

            WabiSabiConfig cfg   = new() { AllowNotedInputRegistration = false };
            var            round = WabiSabiFactory.CreateRound(cfg);

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

            arena.Prison.Punish(outpoint, Punishment.Noted, uint256.One);

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

            var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(
                async() => await arenaClient.RegisterInputAsync(round.Id, outpoint, key, CancellationToken.None));

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

            await arena.StopAsync(CancellationToken.None);
        }
        CreateRoundWithTwoConfirmedConnectionsAsync(Arena arena, Key key1, SmartCoin coin1, Key key2, SmartCoin coin2)
        {
            // Create the round.
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
            var round       = Assert.Single(arena.Rounds);

            round.MaxVsizeAllocationPerAlice = 11 + 31 + MultipartyTransactionParameters.SharedOverhead;

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

            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.ConnectionConfirmation != round.Phase)
            {
                await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
            }
            await Task.WhenAll(task1, task2);

            var aliceClient1 = task1.Result;
            var aliceClient2 = task2.Result;

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

            Assert.Equal(Phase.OutputRegistration, round.Phase);

            await roundStateUpdater.StopAsync(CancellationToken.None);

            return(round,
                   arenaClient,
                   new[]
            {
                aliceClient1,
                aliceClient2
            });
        }
    CreateRoundWithTwoConfirmedConnectionsAsync(Arena arena, IKeyChain keyChain, SmartCoin coin1, SmartCoin coin2)
    {
        // Get the round.
        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
        var round       = Assert.Single(arena.Rounds);

        // Refresh the Arena States because of vsize manipulation.
        await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));

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

        var task1 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin1, keyChain, roundStateUpdater, CancellationToken.None);
        var task2 = AliceClient.CreateRegisterAndConfirmInputAsync(RoundState.FromRound(round), arenaClient, coin2, keyChain, roundStateUpdater, CancellationToken.None);

        while (Phase.ConnectionConfirmation != round.Phase)
        {
            await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
        }
        await Task.WhenAll(task1, task2);

        var aliceClient1 = await task1;
        var aliceClient2 = await task2;

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

        Assert.Equal(Phase.OutputRegistration, round.Phase);

        await roundStateUpdater.StopAsync(CancellationToken.None);

        return(round,
               arenaClient,
               new[]
        {
            aliceClient1,
            aliceClient2
        });
    }
Beispiel #22
0
        public async Task InputSpentAsync()
        {
            using Key key = new();
            WabiSabiConfig cfg   = new();
            var            round = WabiSabiFactory.CreateRound(cfg);

            var mockRpc = new Mock <IRPCClient>();

            mockRpc.Setup(rpc => rpc.GetTxOutAsync(It.IsAny <uint256>(), It.IsAny <int>(), It.IsAny <bool>()))
            .ReturnsAsync((NBitcoin.RPC.GetTxOutResponse?)null);

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

            var arenaClient = WabiSabiFactory.CreateArenaClient(arena);

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

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

            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 ReissueExactDeltaAmountAsync()
    {
        WabiSabiConfig cfg   = new();
        var            round = WabiSabiFactory.CreateRound(cfg);

        round.SetPhase(Phase.OutputRegistration);
        var alice = WabiSabiFactory.CreateAlice(round);

        round.Alices.Add(alice);
        using Arena arena = await ArenaBuilder.From(cfg).CreateAndStartAsync(round);

        // Step 1. Create credentials
        var(amClient, vsClient, amIssuer, vsIssuer, amZeroCredentials, vsZeroCredentials) = WabiSabiFactory.CreateWabiSabiClientsAndIssuers(round);

        var amountsToRequest = new[] { alice.CalculateRemainingAmountCredentials(round.Parameters.MiningFeeRate, round.Parameters.CoordinationFeeRate).Satoshi };

        var(amCredentialRequest, amValid) = amClient.CreateRequest(
            amountsToRequest,
            amZeroCredentials,                    // FIXME doesn't make much sense
            CancellationToken.None);
        var startingVsizeCredentialAmount = 100L; // any number is okay here for this test

        var(vsCredentialRequest, weValid) = vsClient.CreateRequest(
            new[] { startingVsizeCredentialAmount },
            vsZeroCredentials,             // FIXME doesn't make much sense
            CancellationToken.None);

        var amResp = amIssuer.HandleRequest(amCredentialRequest);
        var weResp = vsIssuer.HandleRequest(vsCredentialRequest);
        var amountCredentialsToPresent = amClient.HandleResponse(amResp, amValid).ToArray();
        var vsizeCredentialsToPresent  = vsClient.HandleResponse(weResp, weValid).ToArray();

        // Step 2.
        var invalidVsizesToRequest = vsizeCredentialsToPresent.Select(x => 2 * x.Value);         // we request the double than what we have.

        var(realVsizeCredentialRequest, realVsizeCredentialResponseValidation) = vsClient.CreateRequest(
            invalidVsizesToRequest,
            vsizeCredentialsToPresent,
            CancellationToken.None);

        var(realAmountCredentialRequest, realAmountCredentialResponseValidation) = amClient.CreateRequest(
            amountsToRequest,
            amountCredentialsToPresent,
            CancellationToken.None);

        var zeroAmountCredentialRequestData = amClient.CreateRequestForZeroAmount();
        var zeroVsizeCredentialRequestData  = vsClient.CreateRequestForZeroAmount();

        // hit Arena directly to verify it prevents requesting more vsize credentials than what are presented.
        // we have to bypass the ArenaClient because it also prevents this invalid requests and breaks the circuit
        // early, not allowing to hit Arena.
        var ex = await Assert.ThrowsAsync <WabiSabiProtocolException>(async() =>
                                                                      await arena.ReissuanceAsync(
                                                                          new ReissueCredentialRequest(
                                                                              round.Id,
                                                                              realAmountCredentialRequest,
                                                                              realVsizeCredentialRequest,
                                                                              zeroAmountCredentialRequestData.CredentialsRequest,
                                                                              zeroVsizeCredentialRequestData.CredentialsRequest),
                                                                          CancellationToken.None).ConfigureAwait(false));

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

        // Step 2a. Now we verify the client also implements the same verifications.
        var arenaClient = WabiSabiFactory.CreateArenaClient(arena);
        await Assert.ThrowsAsync <InvalidOperationException>(async() =>
                                                             await arenaClient.ReissueCredentialAsync(
                                                                 round.Id,
                                                                 amountsToRequest,
                                                                 invalidVsizesToRequest, // we request the double than what we can
                                                                 amountCredentialsToPresent,
                                                                 vsizeCredentialsToPresent,
                                                                 CancellationToken.None));

        await arena.StopAsync(CancellationToken.None);
    }