public async Task DummyRegistration(string originWalletName, string originWalletPassword) { // TODO: Move this functionality into the tests var token = new List <byte>(); // Server ID token.AddRange(Encoding.ASCII.GetBytes("".PadRight(34))); // IPv4 address token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); // IPv6 address token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); // Onion address token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); token.Add(0x00); // Port number byte[] portNumber = BitConverter.GetBytes(37123); token.Add(portNumber[0]); token.Add(portNumber[1]); // RSA sig length byte[] rsaLength = BitConverter.GetBytes(256); token.Add(rsaLength[0]); token.Add(rsaLength[1]); // RSA signature byte[] rsaSig = new byte[256]; token.AddRange(rsaSig); // ECDSA sig length byte[] ecdsaLength = BitConverter.GetBytes(128); token.Add(ecdsaLength[0]); token.Add(ecdsaLength[1]); // ECDSA signature byte[] ecdsaSig = new byte[128]; token.AddRange(ecdsaSig); // Configuration hash token.AddRange(Encoding.ASCII.GetBytes("aa4e984c5655a677716539acc8cbc0ce29331429")); // Finally add protocol byte and computed length to beginning of header byte[] protocolVersionByte = BitConverter.GetBytes(254); byte[] headerLength = BitConverter.GetBytes(token.Count); token.Insert(0, protocolVersionByte[0]); token.Insert(1, headerLength[0]); token.Insert(2, headerLength[1]); Money outputValue = new Money(0.0001m, MoneyUnit.BTC); Transaction sendTx = new Transaction(); // Recognisable string used to tag the transaction within the blockchain byte[] bytes = Encoding.UTF8.GetBytes("BREEZE_REGISTRATION_MARKER"); sendTx.Outputs.Add(new TxOut() { Value = outputValue, ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes) }); // Add each data-encoding PubKey as a TxOut foreach (PubKey pubKey in BlockChainDataConversions.BytesToPubKeys(token.ToArray())) { TxOut destTxOut = new TxOut() { Value = outputValue, ScriptPubKey = pubKey.ScriptPubKey }; sendTx.Outputs.Add(destTxOut); } HdAccount highestAcc = null; foreach (HdAccount account in this.walletManager.GetAccounts(originWalletName)) { if (highestAcc == null) { highestAcc = account; } if (account.GetSpendableAmount().ConfirmedAmount > highestAcc.GetSpendableAmount().ConfirmedAmount) { highestAcc = account; } } // This fee rate is primarily for regtest, testnet and mainnet have actual estimators that work FeeRate feeRate = new FeeRate(new Money(10000, MoneyUnit.Satoshi)); WalletAccountReference accountRef = new WalletAccountReference(originWalletName, highestAcc.Name); List <Recipient> recipients = new List <Recipient>(); TransactionBuildContext txBuildContext = new TransactionBuildContext(accountRef, recipients); txBuildContext.WalletPassword = originWalletPassword; txBuildContext.OverrideFeeRate = feeRate; txBuildContext.Sign = true; txBuildContext.MinConfirmations = 0; this.walletTransactionHandler.FundTransaction(txBuildContext, sendTx); this.logger.LogDebug("Trying to broadcast transaction: " + sendTx.GetHash()); await this.broadcasterManager.BroadcastTransactionAsync(sendTx).ConfigureAwait(false); var bcResult = this.broadcasterManager.GetTransaction(sendTx.GetHash()).State; switch (bcResult) { case Stratis.Bitcoin.Broadcasting.State.Broadcasted: case Stratis.Bitcoin.Broadcasting.State.Propagated: this.logger.LogDebug("Broadcasted transaction: " + sendTx.GetHash()); break; case Stratis.Bitcoin.Broadcasting.State.ToBroadcast: // Wait for propagation var waited = TimeSpan.Zero; var period = TimeSpan.FromSeconds(1); while (TimeSpan.FromSeconds(21) > waited) { // Check BroadcasterManager for broadcast success var transactionEntry = this.broadcasterManager.GetTransaction(sendTx.GetHash()); if (transactionEntry != null && transactionEntry.State == Stratis.Bitcoin.Broadcasting.State.Propagated) { // TODO: This is cluttering up the console, only need to log it once this.logger.LogDebug("Propagated transaction: " + sendTx.GetHash()); } await Task.Delay(period).ConfigureAwait(false); waited += period; } break; case Stratis.Bitcoin.Broadcasting.State.CantBroadcast: // Do nothing break; } this.logger.LogDebug("Uncertain if transaction was propagated: " + sendTx.GetHash()); }
public void TestMultiClientWithoutTor(int numClients) { // Workaround for segwit not correctly activating Network.RegTest.Consensus.BIP9Deployments[BIP9Deployments.Segwit] = new BIP9DeploymentsParameters(1, 0, DateTime.Now.AddDays(50).ToUnixTimestamp()); NodeBuilder builder = NodeBuilder.Create(version: "0.15.1"); CoreNode coreNode = GetCoreNode(builder); coreNode.Start(); // Replicate portions of BreezeServer's Program.cs. Maybe refactor it into a class/function in future var serviceProvider = new ServiceCollection() .AddLogging() .AddSingleton <Breeze.BreezeServer.Services.ITumblerService, Breeze.BreezeServer.Services.TumblerService>() .BuildServiceProvider(); serviceProvider .GetService <ILoggerFactory>() .AddConsole(LogLevel.Debug); // Skip the registration code - that can be tested separately string configPath = Path.Combine(coreNode.DataFolder, "breeze.conf"); File.WriteAllLines(configPath, this.breezeServerConfig); BreezeConfiguration config = new BreezeConfiguration(configPath); var coreRpc = coreNode.CreateRPCClient(); string ntbServerConfigPath = Path.Combine(coreNode.DataFolder, "server.config"); File.WriteAllLines(ntbServerConfigPath, GetNTBServerConfig(coreRpc)); // We need to start up the masternode prior to creating the SBFN instance so that // we have the URI available for starting the TumbleBit feature // TODO: Also need to see if NTB interactive console interferes with later parts of the test new Thread(delegate() { Thread.CurrentThread.IsBackground = true; // By instantiating the TumblerService directly the registration logic is skipped var tumbler = serviceProvider.GetService <Breeze.BreezeServer.Services.ITumblerService>(); tumbler.StartTumbler(config, false, "server.config", Path.GetFullPath(coreNode.DataFolder), false); }).Start(); // Wait for URI file to be written out by the TumblerService while (!File.Exists(Path.Combine(coreNode.DataFolder, "uri.txt"))) { Thread.Sleep(1000); } Console.WriteLine("* URI file detected *"); Thread.Sleep(5000); var serverAddress = File.ReadAllText(Path.Combine(coreNode.DataFolder, "uri.txt")); // Not used for this test ConfigurationOptionWrapper <object> registrationStoreDirectory = new ConfigurationOptionWrapper <object>("RegistrationStoreDirectory", ""); // Force SBFN to use the temporary hidden service to connect to the server ConfigurationOptionWrapper <object> masternodeUri = new ConfigurationOptionWrapper <object>("MasterNodeUri", serverAddress); ConfigurationOptionWrapper <object>[] configurationOptions = { registrationStoreDirectory, masternodeUri }; List <CoreNode> clientNodes = new List <CoreNode>(); int apiPortNum = 37229; for (int i = 0; i < numClients; i++) { var temp = builder.CreateStratisPowNode(false, fullNodeBuilder => { fullNodeBuilder .UsePowConsensus() .UseBlockStore() .UseMempool() .UseBlockNotification() .UseTransactionNotification() .AddMining() .UseWallet() .UseWatchOnlyWallet() .UseApi() .AddRPC() .UseTumbleBit(configurationOptions); }); temp.ConfigParameters.AddOrReplace("apiuri", $"http://localhost:{apiPortNum}"); clientNodes.Add(temp); apiPortNum++; } foreach (var node in clientNodes) { node.Start(); } // Create the source and destination wallets for nodes for (int i = 0; i < numClients; i++) { var wm1 = clientNodes[i].FullNode.NodeService <IWalletManager>() as WalletManager; wm1.CreateWallet("TumbleBit1", $"alice{i}"); wm1.CreateWallet("TumbleBit1", $"bob{i}"); } // Mined coins only mature after 100 blocks on regtest // Additionally, we need to force Segwit to activate in order for NTB to work correctly coreRpc.Generate(450); while (coreRpc.GetBlockCount() < 450) { Thread.Sleep(100); } for (int i = 0; i < numClients; i++) { coreRpc.AddNode(clientNodes[i].Endpoint, false); var rpc = clientNodes[i].CreateRPCClient(); rpc.AddNode(coreNode.Endpoint, false); for (int j = 0; j < numClients; j++) { if (i != j) { rpc.AddNode(clientNodes[j].Endpoint, false); } } } for (int i = 0; i < numClients; i++) { var wm1 = clientNodes[i].FullNode.NodeService <IWalletManager>() as WalletManager; var destination1 = wm1.GetUnusedAddress(new WalletAccountReference($"alice{i}", "account 0")); coreRpc.SendToAddress(BitcoinAddress.Create(destination1.Address, Network.RegTest), new Money(5.0m, MoneyUnit.BTC)); } clientNodes[0].FullNode.Settings.Logger.LogInformation("Waiting for transactions to propagate and finalise"); Thread.Sleep(5000); coreRpc.Generate(1); // Wait for SBFN clients to sync with the core node foreach (var node in clientNodes) { TestHelper.WaitLoop(() => node.CreateRPCClient().GetBestBlockHash() == coreRpc.GetBestBlockHash()); } // Test implementation note: the coins do not seem to immediately appear in the wallet. // This is possibly some sort of race condition between the wallet manager and block generation/sync. // This extra delay seems to ensure that the coins are definitely in the wallet by the time the // transaction count gets logged to the console below. // Wait instead of generating a block Thread.Sleep(5000); for (int i = 0; i < numClients; i++) { var wm1 = clientNodes[i].FullNode.NodeService <IWalletManager>() as WalletManager; //logger1.LogError($"({i}) Number of wallet transactions: " + wm1.GetSpendableTransactionsInWallet($"alice{i}").Count()); // Connect each client to server and start tumbling using (HttpClient client = new HttpClient()) { client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var apiSettings1 = clientNodes[i].FullNode.NodeService <ApiSettings>(); var connectContent = new StringContent(new ConnectRequest { OriginWalletName = $"alice{i}" }.ToString(), Encoding.UTF8, "application/json"); var connectResponse = client.PostAsync(apiSettings1.ApiUri + "api/TumbleBit/connect", connectContent).GetAwaiter().GetResult(); var tumbleContent = new StringContent(new TumbleRequest { OriginWalletName = $"alice{i}", OriginWalletPassword = "******", DestinationWalletName = $"bob{i}" }.ToString(), Encoding.UTF8, "application/json"); var tumbleResponse = client.PostAsync(apiSettings1.ApiUri + "api/TumbleBit/tumble", tumbleContent).GetAwaiter().GetResult(); // Note that the TB client takes about 30 seconds to completely start up, as it has to check the server parameters and RSA key proofs } clientNodes[i].FullNode.Settings.Logger.LogInformation($"Client ({i}) About to start tumbling loop"); } while (true) { for (int i = 0; i < numClients; i++) { clientNodes[i].FullNode.Settings.Logger.LogInformation($"Wallet {i} balance height: " + clientNodes[i].FullNode.Chain.Height); var wm1 = clientNodes[i].FullNode.NodeService <IWalletManager>() as WalletManager; HdAccount alice1 = wm1.GetWalletByName($"alice{i}").GetAccountByCoinType("account 0", (CoinType)Network.RegTest.Consensus.CoinType); clientNodes[i].FullNode.Settings.Logger.LogInformation($"(A{i}) Confirmed: " + alice1.GetSpendableAmount().ConfirmedAmount.ToString()); clientNodes[i].FullNode.Settings.Logger.LogInformation($"(A{i}) Unconfirmed: " + alice1.GetSpendableAmount().UnConfirmedAmount.ToString()); HdAccount bob1 = wm1.GetWalletByName($"bob{i}").GetAccountByCoinType("account 0", (CoinType)Network.RegTest.Consensus.CoinType); clientNodes[i].FullNode.Settings.Logger.LogInformation($"(B{i}) Confirmed: " + bob1.GetSpendableAmount().ConfirmedAmount.ToString()); clientNodes[i].FullNode.Settings.Logger.LogInformation($"(B{i}) Unconfirmed: " + bob1.GetSpendableAmount().UnConfirmedAmount.ToString()); clientNodes[i].FullNode.Settings.Logger.LogInformation("==="); } coreRpc.Generate(1); // Try to ensure the invalid phase error does not occur // (seems to occur when the server has not yet processed a new block and the client has) //TestHelper.WaitLoop(() => rpc1.GetBestBlockHash() == coreRpc.GetBestBlockHash()); //TestHelper.WaitLoop(() => rpc2.GetBestBlockHash() == coreRpc.GetBestBlockHash()); /*var mempool = node1.FullNode.NodeService<MempoolManager>(); * var mempoolTx = mempool.GetMempoolAsync().Result; * if (mempoolTx.Count > 0) * { * Console.WriteLine("--- Mempool contents ---"); * foreach (var tx in mempoolTx) * { * var hex = mempool.GetTransaction(tx).Result; * Console.WriteLine(tx + " ->"); * Console.WriteLine(hex); * Console.WriteLine("---"); * } * }*/ Thread.Sleep(20000); } if (builder != null) { builder.Dispose(); } }
public void TestWithTor() { // Workaround for segwit not correctly activating Network.RegTest.Consensus.BIP9Deployments[BIP9Deployments.Segwit] = new BIP9DeploymentsParameters(1, 0, DateTime.Now.AddDays(50).ToUnixTimestamp()); using (NodeBuilder builder = NodeBuilder.Create(version: "0.15.1")) { HttpClient client = null; var coreNode = builder.CreateNode(false); // This line has no effect currently as the changes to the config get overwritten coreNode.ConfigParameters.AddOrReplace("printtoconsole", "0"); coreNode.ConfigParameters.AddOrReplace("debug", "1"); //coreNode.ConfigParameters.AddOrReplace("prematurewitness", "1"); //coreNode.ConfigParameters.AddOrReplace("walletprematurewitness", "1"); coreNode.ConfigParameters.AddOrReplace("rpcworkqueue", "100"); coreNode.Start(); // Replicate portions of BreezeServer's Program.cs. Maybe refactor it into a class/function in future var serviceProvider = new ServiceCollection() .AddLogging() .AddSingleton <Breeze.BreezeServer.Services.ITumblerService, Breeze.BreezeServer.Services.TumblerService>() .BuildServiceProvider(); serviceProvider .GetService <ILoggerFactory>() .AddConsole(LogLevel.Debug); // Skip the registration code - that can be tested separately string configPath = Path.Combine(coreNode.DataFolder, "breeze.conf"); string[] breezeServerConfig = { "network=regtest", // Only the network setting is currently used from this file "rpc.user=dummy", "rpc.password=dummy", "rpc.url=http://127.0.0.1:26174/", "breeze.ipv4=127.0.0.1", "breeze.ipv6=2001:0db8:85a3:0000:0000:8a2e:0370:7334", "breeze.onion=0123456789ABCDEF", "breeze.port=37123", "breeze.regtxfeevalue=10000", "breeze.regtxoutputvalue=1000", "tumbler.url=http://127.0.0.1:37123/api/v1/", "tumbler.rsakeyfile=/Users/username/.ntumblebitserver/RegTest/Tumbler.pem", "tumbler.ecdsakeyaddress=TVwRFmEKRCnQAgShf3QshBjp1Tmucm1e87" }; File.WriteAllLines(configPath, breezeServerConfig); BreezeConfiguration config = new BreezeConfiguration(configPath); var coreRpc = coreNode.CreateRPCClient(); string ntbServerConfigPath = Path.Combine(coreNode.DataFolder, "server.config"); string[] ntbServerConfig = { "regtest=1", "rpc.url=http://127.0.0.1:" + coreRpc.Address.Port + "/", "rpc.user="******"rpc.password="******"cycle=kotori", "tor.enabled=true", "tor.server=127.0.0.1:9051" // We assume for now that tor has been manually started }; File.WriteAllLines(ntbServerConfigPath, ntbServerConfig); // We need to start up the masternode prior to creating the SBFN instance so that // we have the URI available for starting the TumbleBit feature // TODO: Also need to see if NTB interactive console interferes with later parts of the test new Thread(delegate() { Thread.CurrentThread.IsBackground = true; // By instantiating the TumblerService directly the registration logic is skipped var tumbler = serviceProvider.GetService <Breeze.BreezeServer.Services.ITumblerService>(); tumbler.StartTumbler(config, false, "server.config", Path.GetFullPath(coreNode.DataFolder), false); }).Start(); // Wait for URI file to be written out by the TumblerService while (!File.Exists(Path.Combine(coreNode.DataFolder, "uri.txt"))) { Thread.Sleep(1000); } Console.WriteLine("* URI file detected *"); Thread.Sleep(5000); var serverAddress = File.ReadAllText(Path.Combine(coreNode.DataFolder, "uri.txt")); // Not used for this test ConfigurationOptionWrapper <object> registrationStoreDirectory = new ConfigurationOptionWrapper <object>("RegistrationStoreDirectory", Path.Combine(coreNode.DataFolder, "registrationHistory.json")); // Force SBFN to connect to the server ConfigurationOptionWrapper <object> masternodeUri = new ConfigurationOptionWrapper <object>("MasterNodeUri", serverAddress); ConfigurationOptionWrapper <object> torOption = new ConfigurationOptionWrapper <object>("Tor", true); ConfigurationOptionWrapper <object> tumblerProtocolOption = new ConfigurationOptionWrapper <object>("TumblerProtocol", TumblerProtocolType.Tcp); ConfigurationOptionWrapper <object> useDummyAddressOption = new ConfigurationOptionWrapper <object>("UseDummyAddress", false); ConfigurationOptionWrapper <object>[] configurationOptions = { registrationStoreDirectory, masternodeUri, torOption, tumblerProtocolOption, useDummyAddressOption }; // Logging for NTB client code ConsoleLoggerProcessor loggerProcessor = new ConsoleLoggerProcessor(); Logs.Configure(new FuncLoggerFactory(i => new CustomerConsoleLogger(i, Logs.SupportDebug(true), false, loggerProcessor))); CoreNode node1 = builder.CreateStratisPowNode(true, fullNodeBuilder => { fullNodeBuilder .UsePowConsensus() .UseBlockStore() .UseMempool() .UseBlockNotification() .UseTransactionNotification() .AddMining() .UseWallet() .UseWatchOnlyWallet() .UseApi() .AddRPC() .UseTumbleBit(configurationOptions); }); var apiSettings = node1.FullNode.NodeService <ApiSettings>(); NLog.Config.LoggingConfiguration config1 = LogManager.Configuration; var folder = Path.Combine(node1.DataFolder, "Logs"); var tbTarget = new FileTarget(); tbTarget.Name = "tumblebit"; tbTarget.FileName = Path.Combine(folder, "tumblebit.txt"); tbTarget.ArchiveFileName = Path.Combine(folder, "tb-${date:universalTime=true:format=yyyy-MM-dd}.txt"); tbTarget.ArchiveNumbering = ArchiveNumberingMode.Sequence; tbTarget.ArchiveEvery = FileArchivePeriod.Day; tbTarget.MaxArchiveFiles = 7; tbTarget.Layout = "[${longdate:universalTime=true} ${threadid}${mdlc:item=id}] ${level:uppercase=true}: ${callsite} ${message}"; tbTarget.Encoding = Encoding.UTF8; var ruleTb = new LoggingRule("*", NLog.LogLevel.Debug, tbTarget); config1.LoggingRules.Add(ruleTb); config1.AddTarget(tbTarget); // Apply new rules. LogManager.ReconfigExistingLoggers(); //node1.NotInIBD(); // Create the source and destination wallets var wm1 = node1.FullNode.NodeService <IWalletManager>() as WalletManager; //var wm2 = node2.FullNode.NodeService<IWalletManager>() as WalletManager; wm1.CreateWallet("TumbleBit1", "alice"); wm1.CreateWallet("TumbleBit1", "bob"); // Mined coins only mature after 100 blocks on regtest // Additionally, we need to force Segwit to activate in order for NTB to work correctly coreRpc.Generate(450); var rpc1 = node1.CreateRPCClient(); //var rpc2 = node2.CreateRPCClient(); coreRpc.AddNode(node1.Endpoint, false); rpc1.AddNode(coreNode.Endpoint, false); var amount = new Money(5.0m, MoneyUnit.BTC); var destination = wm1.GetUnusedAddress(new WalletAccountReference("alice", "account 0")); coreRpc.SendToAddress(BitcoinAddress.Create(destination.Address, Network.RegTest), amount); Console.WriteLine("Waiting for transaction to propagate and finalise"); Thread.Sleep(5000); coreRpc.Generate(1); // Wait for SBFN to sync with the core node TestHelper.WaitLoop(() => rpc1.GetBestBlockHash() == coreRpc.GetBestBlockHash()); // Test implementation note: the coins do not seem to immediately appear in the wallet. // This is possibly some sort of race condition between the wallet manager and block generation/sync. // This extra delay seems to ensure that the coins are definitely in the wallet by the time the // transaction count gets logged to the console below. // Wait instead of generating a block Thread.Sleep(5000); //var log = node1.FullNode.NodeService<ILogger>(); Console.WriteLine("Number of wallet transactions: " + wm1.GetSpendableTransactionsInWallet("alice").Count()); // Connect to server and start tumbling using (client = new HttpClient()) { client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // Sample returned output // {"tumbler":"ctb://<onionaddress>.onion?h=<confighash>","denomination":"0.01000000","fee":"0.00010000","network":"RegTest","estimate":"22200"} var connectContent = new StringContent(new ConnectRequest { OriginWalletName = "alice" }.ToString(), Encoding.UTF8, "application/json"); var connectResponse = client.PostAsync(apiSettings.ApiUri + "api/TumbleBit/connect", connectContent).GetAwaiter().GetResult(); //Assert.StartsWith("[{\"", connectResponse); var tumbleModel = new TumbleRequest { OriginWalletName = "alice", OriginWalletPassword = "******", DestinationWalletName = "bob" }; var tumbleContent = new StringContent(tumbleModel.ToString(), Encoding.UTF8, "application/json"); var tumbleResponse = client.PostAsync(apiSettings.ApiUri + "api/TumbleBit/tumble", tumbleContent).GetAwaiter().GetResult(); // Note that the TB client takes about 30 seconds to completely start up, as it has to check the server parameters and // RSA key proofs //Assert.StartsWith("[{\"", tumbleResponse); } HdAccount alice = null; HdAccount bob = null; // TODO: Move forward specific numbers of blocks and check interim states? TB tests already do that for (int i = 0; i < 80; i++) { Console.WriteLine("Wallet balance height: " + node1.FullNode.Chain.Height); alice = wm1.GetWalletByName("alice").GetAccountByCoinType("account 0", (CoinType)Network.RegTest.Consensus.CoinType); Console.WriteLine("(A) Confirmed: " + alice.GetSpendableAmount().ConfirmedAmount.ToString()); Console.WriteLine("(A) Unconfirmed: " + alice.GetSpendableAmount().UnConfirmedAmount.ToString()); bob = wm1.GetWalletByName("bob").GetAccountByCoinType("account 0", (CoinType)Network.RegTest.Consensus.CoinType); Console.WriteLine("(B) Confirmed: " + bob.GetSpendableAmount().ConfirmedAmount.ToString()); Console.WriteLine("(B) Unconfirmed: " + bob.GetSpendableAmount().UnConfirmedAmount.ToString()); coreRpc.Generate(1); builder.SyncNodes(); // Try to ensure the invalid phase error does not occur // (seems to occur when the server has not yet processed a new block and the client has) TestHelper.WaitLoop(() => rpc1.GetBestBlockHash() == coreRpc.GetBestBlockHash()); var mempool = node1.FullNode.NodeService <MempoolManager>(); var mempoolTx = mempool.GetMempoolAsync().Result; if (mempoolTx.Count > 0) { Console.WriteLine("--- Mempool contents ---"); foreach (var tx in mempoolTx) { var hex = mempool.GetTransaction(tx).Result; Console.WriteLine(tx + " ->"); Console.WriteLine(hex); Console.WriteLine("---"); } } Thread.Sleep(20000); } // Check destination wallet for tumbled coins Assert.True(alice.GetSpendableAmount().ConfirmedAmount < new Money(5.0m, MoneyUnit.BTC)); Assert.True(bob.GetSpendableAmount().ConfirmedAmount > new Money(0.0035m, MoneyUnit.BTC)); // TODO: Need to amend TumblerService so that it can be shut down within the test if (client != null) { client.Dispose(); client = null; } } }