public async Task SimpleReorgAsync()
        {
            var chain = new ConcurrentChain(Network.RegTest);

            using var notifier = CreateNotifier(chain);

            var blockAwaiter = new EventsAwaiter <Block>(
                h => notifier.OnBlock += h,
                h => notifier.OnBlock -= h,
                5);

            var reorgAwaiter = new EventAwaiter <uint256>(
                h => notifier.OnReorg += h,
                h => notifier.OnReorg -= h);

            await notifier.StartAsync(CancellationToken.None);

            await AddBlockAsync(chain);

            var forkPoint        = chain.Tip;
            var blockToBeReorged = await AddBlockAsync(chain);

            chain.SetTip(forkPoint);
            await AddBlockAsync(chain, wait : false);
            await AddBlockAsync(chain, wait : false);
            await AddBlockAsync(chain);

            notifier.TriggerRound();

            // Three blocks notifications
            await blockAwaiter.WaitAsync(TimeSpan.FromSeconds(2));

            // No reorg notifications
            var reorgedkBlock = await reorgAwaiter.WaitAsync(TimeSpan.FromSeconds(1));

            Assert.Equal(blockToBeReorged.HashBlock, reorgedkBlock);
            Assert.Equal(chain.Tip.HashBlock, notifier.BestBlockHash);

            await notifier.StopAsync(CancellationToken.None);
        }
        public async Task NotifyBlocksAsync()
        {
            var chain = new ConcurrentChain(Network.RegTest);

            using var notifier = CreateNotifier(chain);
            var blockCount = 3;

            var reorgAwaiter = new EventAwaiter <uint256>(
                h => notifier.OnReorg += h,
                h => notifier.OnReorg -= h);

            await notifier.StartAsync(CancellationToken.None);

            // Assert that the blocks come in the right order
            var height = 0;

            void OnBlockInv(object s, Block b) => Assert.Equal(b.GetHash(), chain.GetBlock(height++).HashBlock);

            notifier.OnBlock += OnBlockInv;

            foreach (var n in Enumerable.Range(0, blockCount))
            {
                await AddBlockAsync(chain);
            }

            notifier.TriggerRound();
            await Task.Delay(TimeSpan.FromMilliseconds(100));             // give it time to process the blocks

            // Three blocks notifications
            Assert.Equal(chain.Height, height);

            // No reorg notifications
            await Assert.ThrowsAsync <OperationCanceledException>(() => reorgAwaiter.WaitAsync(TimeSpan.FromSeconds(1)));

            Assert.Equal(chain.Tip.HashBlock, notifier.BestBlockHash);

            notifier.OnBlock -= OnBlockInv;
            await notifier.StopAsync(CancellationToken.None);
        }
        public async Task GenesisBlockOnlyAsync()
        {
            var chain = new ConcurrentChain(Network.RegTest);

            using var notifier = CreateNotifier(chain);
            var blockAwaiter = new EventAwaiter <Block>(
                h => notifier.OnBlock += h,
                h => notifier.OnBlock -= h);
            var reorgAwaiter = new EventAwaiter <uint256>(
                h => notifier.OnReorg += h,
                h => notifier.OnReorg -= h);

            await notifier.StartAsync(CancellationToken.None);

            // No block notifications nor reorg notifications
            await Assert.ThrowsAsync <OperationCanceledException>(() => blockAwaiter.WaitAsync(TimeSpan.FromSeconds(1)));

            await Assert.ThrowsAsync <OperationCanceledException>(() => reorgAwaiter.WaitAsync(TimeSpan.FromSeconds(1)));

            Assert.Equal(Network.RegTest.GenesisHash, notifier.BestBlockHash);

            await notifier.StopAsync(CancellationToken.None);
        }
Example #4
0
        public async Task BlockNotifierTestsAsync()
        {
            using var services = new HostedServices();
            var coreNode = await TestNodeBuilder.CreateAsync(services);

            await services.StartAllAsync(CancellationToken.None);

            try
            {
                var           rpc      = coreNode.RpcClient;
                BlockNotifier notifier = services.FirstOrDefault <BlockNotifier>();

                // Make sure we get notification for one block.
                var blockEventAwaiter = new EventAwaiter <Block>(
                    h => notifier.OnBlock += h,
                    h => notifier.OnBlock -= h);

                var hash  = (await rpc.GenerateAsync(1)).First();
                var block = await blockEventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

                Assert.Equal(hash, block.GetHash());

                // Make sure we get notifications about 10 blocks created at the same time.
                var blockNum           = 10;
                var blockEventsAwaiter = new EventsAwaiter <Block>(
                    h => notifier.OnBlock += h,
                    h => notifier.OnBlock -= h,
                    blockNum);

                var hashes = (await rpc.GenerateAsync(blockNum)).ToArray();

                var arrivedBlocks = (await blockEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();

                for (int i = 0; i < hashes.Length; i++)
                {
                    var expected = hashes[i];
                    var actual   = arrivedBlocks[i].GetHash();
                    Assert.Equal(expected, actual);
                }

                // Make sure we get reorg notifications.
                var reorgNum           = 3;
                var newBlockNum        = reorgNum + 1;
                var reorgEventsAwaiter = new EventsAwaiter <uint256>(
                    h => notifier.OnReorg += h,
                    h => notifier.OnReorg -= h,
                    reorgNum);
                blockEventsAwaiter         = new EventsAwaiter <Block>(
                    h => notifier.OnBlock += h,
                    h => notifier.OnBlock -= h,
                    newBlockNum);

                var reorgedHashes = hashes.TakeLast(reorgNum).ToArray();
                await rpc.InvalidateBlockAsync(reorgedHashes[0]);

                var newHashes = (await rpc.GenerateAsync(newBlockNum)).ToArray();

                var reorgedHeaders = (await reorgEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();
                var newBlocks      = (await blockEventsAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).ToArray();

                reorgedHashes = reorgedHashes.Reverse().ToArray();
                for (int i = 0; i < reorgedHashes.Length; i++)
                {
                    var expected = reorgedHashes[i];
                    var actual   = reorgedHeaders[i];
                    Assert.Equal(expected, actual);
                }

                for (int i = 0; i < newHashes.Length; i++)
                {
                    var expected = newHashes[i];
                    var actual   = newBlocks[i].GetHash();
                    Assert.Equal(expected, actual);
                }
            }
            finally
            {
                await services.StopAllAsync(CancellationToken.None);

                await coreNode.TryStopAsync();
            }
        }
Example #5
0
    public async Task BuildTransactionReorgsTestAsync()
    {
        (string password, IRPCClient rpc, Network network, _, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

        bitcoinStore.IndexStore.NewFilter += Common.Wallet_NewFilterProcessed;
        // Create the services.
        // 1. Create connection service.
        NodesGroup nodes = new(global.Config.Network, requirements : Constants.NodeRequirements);

        nodes.ConnectedNodes.Add(await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync());

        // 2. Create mempool service.
        Node node = await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync();

        node.Behaviors.Add(bitcoinStore.CreateUntrustedP2pBehavior());

        // 3. Create wasabi synchronizer service.
        using HttpClientFactory httpClientFactory = new(torEndPoint : null, backendUriGetter : () => new Uri(RegTestFixture.BackendEndPoint));
        WasabiSynchronizer synchronizer = new(bitcoinStore, httpClientFactory);
        HybridFeeProvider  feeProvider  = new(synchronizer, null);

        // 4. Create key manager service.
        var keyManager = KeyManager.CreateNew(out _, password, network);

        // 5. Create wallet service.
        var workDir = Helpers.Common.GetWorkDir();
        CachedBlockProvider blockProvider = new(
            new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
            bitcoinStore.BlockRepository);
        WalletManager walletManager = new(network, workDir, new WalletDirectories(network, workDir));

        walletManager.RegisterServices(bitcoinStore, synchronizer, serviceConfiguration, feeProvider, blockProvider);

        var baseTip = await rpc.GetBestBlockHashAsync();

        // Generate script
        using var k = new Key();
        var scp = k.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main);

        // Get some money, make it confirm.
        var key         = keyManager.GetNextReceiveKey("foo label", out _);
        var fundingTxId = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(0.1m));

        // Generate some coins
        await rpc.GenerateAsync(2);

        try
        {
            nodes.Connect();                                                     // Start connection service.
            node.VersionHandshake();                                             // Start mempool service.
            synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 10000); // Start wasabi synchronizer service.
            await feeProvider.StartAsync(CancellationToken.None);

            // Wait until the filter our previous transaction is present.
            var blockCount = await rpc.GetBlockCountAsync();

            await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), blockCount);

            using var wallet = await walletManager.AddAndStartWalletAsync(keyManager);

            var coin = Assert.Single(wallet.Coins);
            Assert.True(coin.Confirmed);
            TransactionBroadcaster broadcaster = new(network, bitcoinStore, httpClientFactory, walletManager);
            broadcaster.Initialize(nodes, rpc);

            // Send money before reorg.
            PaymentIntent operations = new(scp, Money.Coins(0.011m));
            var           btx1       = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy);
            await broadcaster.SendTransactionAsync(btx1.Transaction);

            var coin2 = Assert.Single(wallet.Coins);
            Assert.NotEqual(coin, coin2);
            Assert.False(coin2.Confirmed);

            operations = new PaymentIntent(scp, Money.Coins(0.012m));
            var btx2 = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            await broadcaster.SendTransactionAsync(btx2.Transaction);

            var coin3 = Assert.Single(wallet.Coins);
            Assert.NotEqual(coin2, coin3);
            Assert.False(coin3.Confirmed);

            // Test synchronization after fork.
            // Invalidate the blocks containing the funding transaction
            var tip = await rpc.GetBestBlockHashAsync();

            await rpc.InvalidateBlockAsync(tip);             // Reorg 1

            tip = await rpc.GetBestBlockHashAsync();

            await rpc.InvalidateBlockAsync(tip);             // Reorg 2

            // Generate three new blocks (replace the previous invalidated ones)
            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            await rpc.GenerateAsync(3);

            await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 3);

            await Task.Delay(100);             // Wait for tx processing.

            var coin4 = Assert.Single(wallet.Coins);
            Assert.Equal(coin3, coin4);
            Assert.True(coin.Confirmed);
            Assert.True(coin2.Confirmed);
            Assert.True(coin3.Confirmed);
            Assert.True(coin4.Confirmed);

            // Send money after reorg.
            // When we invalidate a block, the transactions set in the invalidated block
            // are reintroduced when we generate a new block through the rpc call
            operations = new PaymentIntent(scp, Money.Coins(0.013m));
            var btx3 = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy);
            await broadcaster.SendTransactionAsync(btx3.Transaction);

            var coin5 = Assert.Single(wallet.Coins);
            Assert.NotEqual(coin4, coin5);
            Assert.False(coin5.Confirmed);

            operations = new PaymentIntent(scp, Money.Coins(0.014m));
            var btx4 = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            await broadcaster.SendTransactionAsync(btx4.Transaction);

            var coin6 = Assert.Single(wallet.Coins);
            Assert.NotEqual(coin5, coin6);
            Assert.False(coin6.Confirmed);

            // Test synchronization after fork with different transactions.
            // Create a fork that invalidates the blocks containing the funding transaction
            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            await rpc.InvalidateBlockAsync(baseTip);

            try
            {
                await rpc.AbandonTransactionAsync(fundingTxId);
            }
            catch
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    throw;
                }
                return;                 // Occassionally this fails on Linux or OSX, I have no idea why.
            }
            // Spend the inputs of the tx so we know
            var success = bitcoinStore.TransactionStore.TryGetTransaction(fundingTxId, out var invalidSmartTransaction);
            Assert.True(success);
            var invalidCoin = Assert.Single(((CoinsRegistry)wallet.Coins).AsAllCoinsView().CreatedBy(invalidSmartTransaction !.GetHash()));
            Assert.NotNull(invalidCoin.SpenderTransaction);
            Assert.True(invalidCoin.Confirmed);

            var overwriteTx = Transaction.Create(network);
            overwriteTx.Inputs.AddRange(invalidSmartTransaction.Transaction.Inputs);
            var  walletAddress = keyManager.GetNextReceiveKey("foo", out _).GetP2wpkhAddress(network);
            bool onAddress     = false;
            foreach (var invalidOutput in invalidSmartTransaction.Transaction.Outputs)
            {
                if (onAddress)
                {
                    overwriteTx.Outputs.Add(new TxOut(invalidOutput.Value, new Key().GetAddress(ScriptPubKeyType.Segwit, network)));
                }
                else
                {
                    overwriteTx.Outputs.Add(new TxOut(invalidOutput.Value, walletAddress));
                    onAddress = true;
                }
            }
            var srtxwwreq = new SignRawTransactionRequest();
            srtxwwreq.Transaction = overwriteTx;
            var srtxwwres = await rpc.SignRawTransactionWithWalletAsync(srtxwwreq);

            var eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            await rpc.SendRawTransactionAsync(srtxwwres.SignedTransaction);

            await rpc.GenerateAsync(10);

            await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 10);

            var eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            var doubleSpend = Assert.Single(eventArgs.SuccessfullyDoubleSpentCoins);
            Assert.Equal(invalidCoin.TransactionId, doubleSpend.TransactionId);

            var curBlockHash = await rpc.GetBestBlockHashAsync();

            blockCount = await rpc.GetBlockCountAsync();

            Assert.Equal(bitcoinStore.SmartHeaderChain.TipHash, curBlockHash);
            Assert.Equal((int)bitcoinStore.SmartHeaderChain.TipHeight, blockCount);

            // Make sure the funding transaction is not in any block of the chain
            while (curBlockHash != rpc.Network.GenesisHash)
            {
                var block = await rpc.GetBlockAsync(curBlockHash);

                if (block.Transactions.Any(tx => tx.GetHash() == fundingTxId))
                {
                    throw new Exception($"Transaction found in block at height {blockCount} hash: {block.GetHash()}");
                }
                curBlockHash = block.Header.HashPrevBlock;
                blockCount--;
            }

            // Get some money, make it confirm.
            // this is necessary because we are in a fork now.
            eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            fundingTxId = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(1m), replaceable : true);

            eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(fundingTxId, eventArgs.NewlyReceivedCoins.Single().TransactionId);
            Assert.Contains(fundingTxId, wallet.Coins.Select(x => x.TransactionId));

            var fundingBumpTxId = await rpc.BumpFeeAsync(fundingTxId);

            await Task.Delay(2000);             // Waits for the funding transaction get to the mempool.

            Assert.Contains(fundingBumpTxId.TransactionId, wallet.Coins.Select(x => x.TransactionId));
            Assert.DoesNotContain(fundingTxId, wallet.Coins.Select(x => x.TransactionId));
            Assert.Single(wallet.Coins.Where(x => x.TransactionId == fundingBumpTxId.TransactionId));

            // Confirm the coin
            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            await rpc.GenerateAsync(1);

            await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1);

            Assert.Single(wallet.Coins.Where(x => x.Confirmed && x.TransactionId == fundingBumpTxId.TransactionId));
        }
        finally
        {
            bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
            await walletManager.RemoveAndStopAllAsync(CancellationToken.None);

            await synchronizer.StopAsync();

            await feeProvider.StopAsync(CancellationToken.None);

            nodes?.Dispose();
            node?.Disconnect();
        }
    }
        public async Task NotifyBlocksAsync()
        {
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1.5));

            const int BlockCount = 3;
            var       chain      = new ConcurrentChain(Network.RegTest);

            using var notifier = CreateNotifier(chain);

            var reorgAwaiter = new EventAwaiter <uint256>(
                h => notifier.OnReorg += h,
                h => notifier.OnReorg -= h);

            await notifier.StartAsync(CancellationToken.None);

            // Assert that the blocks come in the right order
            var    height  = 0;
            string message = string.Empty;

            void OnBlockInv(object?blockNotifier, Block b)
            {
                uint256 h1 = b.GetHash();
                uint256 h2 = chain.GetBlock(height + 1).HashBlock;

                if (h1 != h2)
                {
                    message = string.Format("height={0}, [h1] {1} != [h2] {2}", height, h1, h2);
                    cts.Cancel();
                    return;
                }

                height++;

                if (height == BlockCount)
                {
                    cts.Cancel();
                }
            }

            notifier.OnBlock += OnBlockInv;

            foreach (var n in Enumerable.Range(0, BlockCount))
            {
                await AddBlockAsync(chain);
            }

            notifier.TriggerRound();

            // Waits at most 1.5s given CancellationTokenSource definition
            await Task.WhenAny(Task.Delay(Timeout.InfiniteTimeSpan, cts.Token));

            Assert.True(string.IsNullOrEmpty(message), message);

            // Three blocks notifications
            Assert.Equal(chain.Height, height);

            // No reorg notifications
            await Assert.ThrowsAsync <OperationCanceledException>(() => reorgAwaiter.WaitAsync(TimeSpan.FromSeconds(1)));

            Assert.Equal(chain.Tip.HashBlock, notifier.BestBlockHash);

            notifier.OnBlock -= OnBlockInv;
            await notifier.StopAsync(CancellationToken.None);
        }
Example #7
0
    public async Task SpendUnconfirmedTxTestAsync()
    {
        (string password, IRPCClient rpc, Network network, _, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

        bitcoinStore.IndexStore.NewFilter += Common.Wallet_NewFilterProcessed;
        // Create the services.
        // 1. Create connection service.
        NodesGroup nodes = new(global.Config.Network, requirements : Constants.NodeRequirements);

        nodes.ConnectedNodes.Add(await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync());

        // 2. Create mempool service.

        Node node = await RegTestFixture.BackendRegTestNode.CreateNewP2pNodeAsync();

        node.Behaviors.Add(bitcoinStore.CreateUntrustedP2pBehavior());

        // 3. Create wasabi synchronizer service.
        using HttpClientFactory httpClientFactory = new(torEndPoint : null, backendUriGetter : () => new Uri(RegTestFixture.BackendEndPoint));
        WasabiSynchronizer synchronizer = new(bitcoinStore, httpClientFactory);
        HybridFeeProvider  feeProvider  = new(synchronizer, null);

        // 4. Create key manager service.
        var keyManager = KeyManager.CreateNew(out _, password, network);

        // 5. Create wallet service.
        var workDir = Helpers.Common.GetWorkDir();

        CachedBlockProvider blockProvider = new(
            new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
            bitcoinStore.BlockRepository);

        WalletManager walletManager = new(network, workDir, new WalletDirectories(network, workDir));

        walletManager.RegisterServices(bitcoinStore, synchronizer, serviceConfiguration, feeProvider, blockProvider);

        // Get some money, make it confirm.
        var key = keyManager.GetNextReceiveKey("foo label", out _);

        try
        {
            nodes.Connect();                                                     // Start connection service.
            node.VersionHandshake();                                             // Start mempool service.
            synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 10000); // Start wasabi synchronizer service.
            await feeProvider.StartAsync(CancellationToken.None);

            // Wait until the filter our previous transaction is present.
            var blockCount = await rpc.GetBlockCountAsync();

            await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), blockCount);

            var wallet = await walletManager.AddAndStartWalletAsync(keyManager);

            Assert.Empty(wallet.Coins);

            // Get some money, make it confirm.
            // this is necessary because we are in a fork now.
            var eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            var tx0Id = await rpc.SendToAddressAsync(
                key.GetP2wpkhAddress(network),
                Money.Coins(1m),
                replaceable : true);

            var eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(tx0Id, eventArgs.NewlyReceivedCoins.Single().TransactionId);
            Assert.Single(wallet.Coins);

            TransactionBroadcaster broadcaster = new(network, bitcoinStore, httpClientFactory, walletManager);
            broadcaster.Initialize(nodes, rpc);

            var destination1 = key.PubKey.GetAddress(ScriptPubKeyType.Segwit, Network.Main);
            var destination2 = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main);
            var destination3 = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main);

            PaymentIntent operations = new(new DestinationRequest(destination1, Money.Coins(0.01m)), new DestinationRequest(destination2, Money.Coins(0.01m)), new DestinationRequest(destination3, Money.Coins(0.01m)));

            var tx1Res = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            Assert.Equal(2, tx1Res.InnerWalletOutputs.Count());
            Assert.Equal(2, tx1Res.OuterWalletOutputs.Count());

            // Spend the unconfirmed coin (send it to ourself)
            operations   = new PaymentIntent(key.PubKey.WitHash.ScriptPubKey, Money.Coins(0.5m));
            tx1Res       = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            await broadcaster.SendTransactionAsync(tx1Res.Transaction);

            eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            Assert.Equal(tx0Id, eventArgs.NewlySpentCoins.Single().TransactionId);
            Assert.Equal(tx1Res.Transaction.GetHash(), eventArgs.NewlyReceivedCoins.First().TransactionId);

            // There is a coin created by the latest spending transaction
            Assert.Contains(wallet.Coins, x => x.TransactionId == tx1Res.Transaction.GetHash());

            // There is a coin destroyed
            var allCoins = wallet.TransactionProcessor.Coins.AsAllCoinsView();
            Assert.Equal(1, allCoins.Count(x => !x.IsAvailable() && x.SpenderTransaction?.GetHash() == tx1Res.Transaction.GetHash()));

            // There is at least one coin created from the destruction of the first coin
            Assert.Contains(wallet.Coins, x => x.Transaction.Transaction.Inputs.Any(o => o.PrevOut.Hash == tx0Id));

            var totalWallet = wallet.Coins.Where(c => c.IsAvailable()).Sum(c => c.Amount);
            Assert.Equal((1 * Money.COIN) - tx1Res.Fee.Satoshi, totalWallet);

            // Spend the unconfirmed and unspent coin (send it to ourself)
            operations = new PaymentIntent(key.PubKey.WitHash.ScriptPubKey, Money.Coins(0.6m), subtractFee: true);
            var tx2Res = wallet.BuildTransaction(password, operations, FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);

            eventAwaiter = new EventAwaiter <ProcessedResult>(
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
                h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
            await broadcaster.SendTransactionAsync(tx2Res.Transaction);

            eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));

            var spentCoins = eventArgs.NewlySpentCoins.ToArray();
            Assert.Equal(tx1Res.Transaction.GetHash(), spentCoins.First().TransactionId);
            uint256 tx2Hash       = tx2Res.Transaction.GetHash();
            var     receivedCoins = eventArgs.NewlyReceivedCoins.ToArray();
            Assert.Equal(tx2Hash, receivedCoins[0].TransactionId);
            Assert.Equal(tx2Hash, receivedCoins[1].TransactionId);

            // There is a coin created by the latest spending transaction
            Assert.Contains(wallet.Coins, x => x.TransactionId == tx2Res.Transaction.GetHash());

            // There is a coin destroyed
            allCoins = wallet.TransactionProcessor.Coins.AsAllCoinsView();
            Assert.Equal(2, allCoins.Count(x => !x.IsAvailable() && x.SpenderTransaction?.GetHash() == tx2Hash));

            // There is at least one coin created from the destruction of the first coin
            Assert.Contains(wallet.Coins, x => x.Transaction.Transaction.Inputs.Any(o => o.PrevOut.Hash == tx1Res.Transaction.GetHash()));

            totalWallet = wallet.Coins.Where(c => c.IsAvailable()).Sum(c => c.Amount);
            Assert.Equal((1 * Money.COIN) - tx1Res.Fee.Satoshi - tx2Res.Fee.Satoshi, totalWallet);

            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            var blockId = (await rpc.GenerateAsync(1)).Single();
            try
            {
                await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1);
            }
            catch (TimeoutException)
            {
                Logger.LogInfo("Index was not processed.");
                return;                 // Very rarely this test fails. I have no clue why. Probably because all these RegTests are interconnected, anyway let's not bother the CI with it.
            }

            // Verify transactions are confirmed in the blockchain
            var block = await rpc.GetBlockAsync(blockId);

            Assert.Contains(block.Transactions, x => x.GetHash() == tx2Res.Transaction.GetHash());
            Assert.Contains(block.Transactions, x => x.GetHash() == tx1Res.Transaction.GetHash());
            Assert.Contains(block.Transactions, x => x.GetHash() == tx0Id);

            Assert.True(wallet.Coins.All(x => x.Confirmed));

            // Test coin basic count.
            ICoinsView GetAllCoins() => wallet.TransactionProcessor.Coins.AsAllCoinsView();

            var coinCount = GetAllCoins().Count();
            var to        = keyManager.GetNextReceiveKey("foo", out _);
            var res       = wallet.BuildTransaction(password, new PaymentIntent(to.P2wpkhScript, Money.Coins(0.2345m), label: "bar"), FeeStrategy.TwentyMinutesConfirmationTargetStrategy, allowUnconfirmed: true);
            await broadcaster.SendTransactionAsync(res.Transaction);

            Assert.Equal(coinCount + 2, GetAllCoins().Count());
            Assert.Equal(2, GetAllCoins().Count(x => !x.Confirmed));
            Interlocked.Exchange(ref Common.FiltersProcessedByWalletCount, 0);
            await rpc.GenerateAsync(1);

            await Common.WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 1);

            Assert.Equal(coinCount + 2, GetAllCoins().Count());
            Assert.Equal(0, GetAllCoins().Count(x => !x.Confirmed));
        }
        finally
        {
            bitcoinStore.IndexStore.NewFilter -= Common.Wallet_NewFilterProcessed;
            await walletManager.RemoveAndStopAllAsync(CancellationToken.None);

            await synchronizer.StopAsync();

            await feeProvider.StopAsync(CancellationToken.None);

            nodes?.Dispose();
            node?.Disconnect();
        }
    }