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); }
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(); } }
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); }
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(); } }