public void SegwitWalletTransactionBuildingTest_SpendP2WPKHAndNormalUTXOs() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode node = builder.CreateStratisPosNode(KnownNetworks.StraxRegTest).WithWallet().Start(); CoreNode listener = builder.CreateStratisPosNode(KnownNetworks.StraxRegTest).WithWallet().Start(); TestHelper.Connect(listener, node); var mineAddress = node.FullNode.WalletManager().GetUnusedAddress(); int maturity = (int)node.FullNode.Network.Consensus.CoinbaseMaturity; var miner = node.FullNode.NodeService <IPowMining>() as PowMining; miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), (ulong)(maturity + 2), int.MaxValue); // Send a transaction from first node to itself so that it has a proper segwit input to spend. var destinationAddress = node.FullNode.WalletManager().GetUnusedAddress(); var witAddress = destinationAddress.Bech32Address; var p2wpkhAmount = Money.Coins(1); IActionResult transactionResult = node.FullNode.NodeController <WalletController>() .BuildTransaction(new BuildTransactionRequest { AccountName = "account 0", AllowUnconfirmed = true, Recipients = new List <RecipientModel> { new RecipientModel { DestinationAddress = witAddress, Amount = p2wpkhAmount.ToString() } }, Password = node.WalletPassword, WalletName = node.WalletName, FeeAmount = Money.Coins(0.001m).ToString() }).GetAwaiter().GetResult(); var walletBuildTransactionModel = (WalletBuildTransactionModel)(transactionResult as JsonResult)?.Value; node.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(walletBuildTransactionModel.Hex)); Transaction witFunds = node.FullNode.Network.CreateTransaction(walletBuildTransactionModel.Hex); uint witIndex = witFunds.Outputs.AsIndexedOutputs().First(o => o.TxOut.ScriptPubKey.IsScriptType(ScriptType.P2WPKH)).N; TestBase.WaitLoop(() => listener.CreateRPCClient().GetBlockCount() >= (maturity + 2), cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), 1, int.MaxValue); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); // Make sure wallet is synced. TestBase.WaitLoop(() => node.CreateRPCClient().GetBlockCount() == node.FullNode.WalletManager().LastBlockHeight(), cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); var spendable = node.FullNode.WalletManager().GetSpendableTransactionsInWallet(node.WalletName).Where(t => t.Address.Bech32Address != witAddress); // By sending more than the size of the P2WPKH UTXO, we guarantee that at least one non-P2WPKH UTXO gets included transactionResult = node.FullNode.NodeController <WalletController>() .BuildTransaction(new BuildTransactionRequest { AccountName = "account 0", AllowUnconfirmed = true, Outpoints = new List <OutpointRequest>() { new OutpointRequest() { Index = (int)witIndex, TransactionId = witFunds.GetHash().ToString() }, new OutpointRequest() { Index = spendable.First().Transaction.Index, TransactionId = spendable.First().Transaction.Id.ToString() } }, Recipients = new List <RecipientModel> { new RecipientModel { DestinationAddress = witAddress, Amount = (p2wpkhAmount + Money.Coins(0.5m)).ToString() } }, Password = node.WalletPassword, WalletName = node.WalletName, FeeAmount = Money.Coins(0.001m).ToString() }).GetAwaiter().GetResult(); walletBuildTransactionModel = (WalletBuildTransactionModel)(transactionResult as JsonResult)?.Value; node.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(walletBuildTransactionModel.Hex)); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), 1, int.MaxValue); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); } }
public void CheckSegwitP2PSerialisationForNonWitnessNode() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // We have to name the networks differently because the NBitcoin network registration won't allow two identical networks to coexist otherwise. var network = new StraxRegTest(); network.SetPrivatePropertyValue("Name", "StraxRegTestWithDeployments"); Assert.NotNull(network.Consensus.BIP9Deployments[2]); var networkNoBIP9 = new StraxRegTest(); networkNoBIP9.SetPrivatePropertyValue("Name", "StraxRegTestWithoutDeployments"); Assert.NotNull(networkNoBIP9.Consensus.BIP9Deployments[2]); // Remove BIP9 deployments (i.e. segwit). for (int i = 0; i < networkNoBIP9.Consensus.BIP9Deployments.Length; i++) { networkNoBIP9.Consensus.BIP9Deployments[i] = null; } // Ensure the workaround had the desired effect. Assert.Null(networkNoBIP9.Consensus.BIP9Deployments[2]); Assert.NotNull(network.Consensus.BIP9Deployments[2]); // Explicitly use new & separate instances of StratisRegTest because we modified the BIP9 deployments on one instance. CoreNode node = builder.CreateStratisPosNode(network).Start(); CoreNode listener = builder.CreateStratisPosNode(networkNoBIP9).Start(); // Sanity check. Assert.Null(listener.FullNode.Network.Consensus.BIP9Deployments[2]); Assert.NotNull(node.FullNode.Network.Consensus.BIP9Deployments[2]); // By disabling Segwit on the listener node we also prevent the WitnessCommitments rule from rejecting the mining node's blocks once we modify the listener's peer services. IConnectionManager listenerConnMan = listener.FullNode.NodeService <IConnectionManager>(); listenerConnMan.Parameters.TemplateBehaviors.Add(new TestBehavior()); // Override the listener node's default settings, so that it will not ask for witness data in P2P messages. listenerConnMan.Parameters.Services &= ~NetworkPeerServices.NODE_WITNESS; TestHelper.Connect(listener, node); // Mine a Segwit block on the first node. It should have commitment data as its settings have not been modified. var script = new Key().PubKey.WitHash.ScriptPubKey; var miner = node.FullNode.NodeService <IPowMining>() as PowMining; List <uint256> res = miner.GenerateBlocks(new ReserveScript(script), 1, int.MaxValue); Block block = node.FullNode.ChainIndexer.GetHeader(res.First()).Block; Script commitment = WitnessCommitmentsRule.GetWitnessCommitment(node.FullNode.Network, block); Assert.NotNull(commitment); // The listener should sync the mined block without validation failures. var cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token; TestBase.WaitLoop(() => listener.CreateRPCClient().GetBlockCount() >= 1, cancellationToken: cancellationToken); // We need to capture a message on the non-witness-enabled destination node and see that it contains a block serialised without witness data. INetworkPeer connectedPeer = listenerConnMan.ConnectedPeers.FindByEndpoint(node.Endpoint); TestBehavior testBehavior = connectedPeer.Behavior <TestBehavior>(); var blockMessages = testBehavior.receivedMessageTracker["block"]; var blockReceived = blockMessages.First(); var receivedBlock = blockReceived.Message.Payload as BlockPayload; var parsedBlock = receivedBlock.Obj; // The block mined on the mining node (witness) should be bigger than the one received by the listener (no witness). Assert.True(block.GetSerializedSize() > parsedBlock.GetSerializedSize()); // Reserialise the received block without witness data (this should have no effect on its size). var nonWitnessBlock = parsedBlock.WithOptions(listener.FullNode.Network.Consensus.ConsensusFactory, TransactionOptions.None); // We received a block without witness data in the first place. Assert.True(parsedBlock.GetSerializedSize() == nonWitnessBlock.GetSerializedSize()); } }
public void SegwitWalletTransactionBuildingAndPropagationTest() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode node = builder.CreateStratisPosNode(KnownNetworks.StraxRegTest).WithWallet().Start(); CoreNode listener = builder.CreateStratisPosNode(KnownNetworks.StraxRegTest).WithWallet().Start(); IConnectionManager listenerConnMan = listener.FullNode.NodeService <IConnectionManager>(); listenerConnMan.Parameters.TemplateBehaviors.Add(new TestBehavior()); // The listener node will have default settings, i.e. it should ask for witness data in P2P messages. Assert.True(listenerConnMan.Parameters.Services.HasFlag(NetworkPeerServices.NODE_WITNESS)); TestHelper.Connect(listener, node); var mineAddress = node.FullNode.WalletManager().GetUnusedAddress(); var miner = node.FullNode.NodeService <IPowMining>() as PowMining; miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), (ulong)(node.FullNode.Network.Consensus.CoinbaseMaturity + 1), int.MaxValue); // Wait for listener to sync to the same block height so that it won't reject the coinbase spend as being premature. TestHelper.WaitForNodeToSync(node, listener); // Send a transaction from first node to itself so that it has a proper segwit input to spend. var destinationAddress = node.FullNode.WalletManager().GetUnusedAddress(); var witAddress = destinationAddress.Bech32Address; IActionResult transactionResult = node.FullNode.NodeController <WalletController>() .BuildTransaction(new BuildTransactionRequest { AccountName = "account 0", AllowUnconfirmed = true, Recipients = new List <RecipientModel> { new RecipientModel { DestinationAddress = witAddress, Amount = Money.Coins(1).ToString() } }, Password = node.WalletPassword, WalletName = node.WalletName, FeeAmount = Money.Coins(0.001m).ToString() }).GetAwaiter().GetResult(); var walletBuildTransactionModel = (WalletBuildTransactionModel)(transactionResult as JsonResult)?.Value; node.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(walletBuildTransactionModel.Hex)); Transaction witFunds = node.FullNode.Network.CreateTransaction(walletBuildTransactionModel.Hex); uint witIndex = witFunds.Outputs.AsIndexedOutputs().First(o => o.TxOut.ScriptPubKey.IsScriptType(ScriptType.P2WPKH)).N; TestBase.WaitLoop(() => listener.CreateRPCClient().GetBlockCount() >= 1, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); INetworkPeer connectedPeer = listenerConnMan.ConnectedPeers.FindByEndpoint(node.Endpoint); TestBehavior testBehavior = connectedPeer.Behavior <TestBehavior>(); miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), 1, int.MaxValue); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); // Make sure wallet is synced. TestBase.WaitLoop(() => node.CreateRPCClient().GetBlockCount() == node.FullNode.WalletManager().LastBlockHeight(), cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); // We need to capture a message on the witness-enabled destination node and see that it contains a transaction serialised with witness data. // However, the first transaction has no witness data since it was only being sent to a segwit scriptPubKey (i.e. no witness input data). // So clear all messages for now. testBehavior.receivedMessageTracker.Clear(); // Send a transaction that has a segwit input, to a segwit address. transactionResult = node.FullNode.NodeController <WalletController>() .BuildTransaction(new BuildTransactionRequest { AccountName = "account 0", AllowUnconfirmed = true, Outpoints = new List <OutpointRequest>() { new OutpointRequest() { Index = (int)witIndex, TransactionId = witFunds.GetHash().ToString() } }, Recipients = new List <RecipientModel> { new RecipientModel { DestinationAddress = witAddress, Amount = Money.Coins(0.5m).ToString() } }, Password = node.WalletPassword, WalletName = node.WalletName, FeeAmount = Money.Coins(0.001m).ToString() }).GetAwaiter().GetResult(); walletBuildTransactionModel = (WalletBuildTransactionModel)(transactionResult as JsonResult)?.Value; node.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(walletBuildTransactionModel.Hex)); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length > 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); var txMessages = testBehavior.receivedMessageTracker["tx"]; var txMessage = txMessages.First(); var receivedTransaction = txMessage.Message.Payload as TxPayload; var parsedTransaction = receivedTransaction.Obj; var nonWitnessTransaction = parsedTransaction.WithOptions(TransactionOptions.None, listener.FullNode.Network.Consensus.ConsensusFactory); Assert.True(parsedTransaction.GetSerializedSize() > nonWitnessTransaction.GetSerializedSize()); miner.GenerateBlocks(new ReserveScript(mineAddress.ScriptPubKey), 1, int.MaxValue); TestBase.WaitLoop(() => node.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); TestBase.WaitLoop(() => listener.CreateRPCClient().GetRawMempool().Length == 0, cancellationToken: new CancellationTokenSource(TimeSpan.FromMinutes(1)).Token); } }
public void MempoolSyncTransactions() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode stratisNodeSync = builder.CreateStratisPowNode(); CoreNode stratisNode1 = builder.CreateStratisPowNode(); CoreNode stratisNode2 = builder.CreateStratisPowNode(); builder.StartAll(); stratisNodeSync.NotInIBD(); stratisNode1.NotInIBD(); stratisNode2.NotInIBD(); // generate blocks and wait for the downloader to pickup stratisNodeSync.SetDummyMinerSecret(new BitcoinSecret(new Key(), stratisNodeSync.FullNode.Network)); stratisNodeSync.GenerateStratisWithMiner(105); // coinbase maturity = 100 // wait for block repo for block sync to work TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(stratisNodeSync)); // sync both nodes stratisNode1.CreateRPCClient().AddNode(stratisNodeSync.Endpoint, true); stratisNode2.CreateRPCClient().AddNode(stratisNodeSync.Endpoint, true); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisNode1, stratisNodeSync)); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisNode2, stratisNodeSync)); // create some transactions and push them to the pool var trxs = new List <Transaction>(); foreach (int index in Enumerable.Range(1, 5)) { Block block = stratisNodeSync.FullNode.BlockStoreManager().BlockRepository.GetAsync(stratisNodeSync.FullNode.Chain.GetBlock(index).HashBlock).Result; Transaction prevTrx = block.Transactions.First(); var dest = new BitcoinSecret(new Key(), stratisNodeSync.FullNode.Network); Transaction tx = stratisNodeSync.FullNode.Network.CreateTransaction(); tx.AddInput(new TxIn(new OutPoint(prevTrx.GetHash(), 0), PayToPubkeyHashTemplate.Instance.GenerateScriptPubKey(stratisNodeSync.MinerSecret.PubKey))); tx.AddOutput(new TxOut("25", dest.PubKey.Hash)); tx.AddOutput(new TxOut("24", new Key().PubKey.Hash)); // 1 btc fee tx.Sign(stratisNodeSync.FullNode.Network, stratisNodeSync.MinerSecret, false); trxs.Add(tx); } var options = new ParallelOptions { MaxDegreeOfParallelism = 5 }; Parallel.ForEach(trxs, options, transaction => { stratisNodeSync.Broadcast(transaction); }); // wait for all nodes to have all trx TestHelper.WaitLoop(() => stratisNodeSync.CreateRPCClient().GetRawMempool().Length == 5); // the full node should be connected to both nodes Assert.True(stratisNodeSync.FullNode.ConnectionManager.ConnectedPeers.Count() >= 2); TestHelper.WaitLoop(() => stratisNode1.CreateRPCClient().GetRawMempool().Length == 5); TestHelper.WaitLoop(() => stratisNode2.CreateRPCClient().GetRawMempool().Length == 5); // mine the transactions in the mempool stratisNodeSync.GenerateStratisWithMiner(1); TestHelper.WaitLoop(() => stratisNodeSync.CreateRPCClient().GetRawMempool().Length == 0); // wait for block and mempool to change TestHelper.WaitLoop(() => stratisNode1.CreateRPCClient().GetBestBlockHash() == stratisNodeSync.CreateRPCClient().GetBestBlockHash()); TestHelper.WaitLoop(() => stratisNode2.CreateRPCClient().GetBestBlockHash() == stratisNodeSync.CreateRPCClient().GetBestBlockHash()); TestHelper.WaitLoop(() => stratisNode1.CreateRPCClient().GetRawMempool().Length == 0); TestHelper.WaitLoop(() => stratisNode2.CreateRPCClient().GetRawMempool().Length == 0); } }
public void TxMempoolMapOrphans() { var rand = new Random(); var randByte = new byte[32]; Func <uint256> randHash = () => { rand.NextBytes(randByte); return(new uint256(randByte)); }; using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode stratisNode = builder.CreateStratisPowNode(); builder.StartAll(); stratisNode.SetDummyMinerSecret(new BitcoinSecret(new Key(), stratisNode.FullNode.Network)); // 50 orphan transactions: for (ulong i = 0; i < 50; i++) { Transaction tx = stratisNode.FullNode.Network.CreateTransaction(); tx.AddInput(new TxIn(new OutPoint(randHash(), 0), new Script(OpcodeType.OP_1))); tx.AddOutput(new TxOut(new Money(1 * Money.CENT), stratisNode.MinerSecret.ScriptPubKey)); stratisNode.FullNode.NodeService <MempoolOrphans>().AddOrphanTx(i, tx); } Assert.Equal(50, stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count); // ... and 50 that depend on other orphans: for (ulong i = 0; i < 50; i++) { MempoolOrphans.OrphanTx txPrev = stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().ElementAt(rand.Next(stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count)); Transaction tx = stratisNode.FullNode.Network.CreateTransaction(); tx.AddInput(new TxIn(new OutPoint(txPrev.Tx.GetHash(), 0), new Script(OpcodeType.OP_1))); tx.AddOutput(new TxOut(new Money((1 + i + 100) * Money.CENT), stratisNode.MinerSecret.ScriptPubKey)); stratisNode.FullNode.NodeService <MempoolOrphans>().AddOrphanTx(i, tx); } Assert.Equal(100, stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count); // This really-big orphan should be ignored: for (ulong i = 0; i < 10; i++) { MempoolOrphans.OrphanTx txPrev = stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().ElementAt(rand.Next(stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count)); Transaction tx = stratisNode.FullNode.Network.CreateTransaction(); tx.AddOutput(new TxOut(new Money(1 * Money.CENT), stratisNode.MinerSecret.ScriptPubKey)); foreach (int index in Enumerable.Range(0, 2777)) { tx.AddInput(new TxIn(new OutPoint(txPrev.Tx.GetHash(), index), new Script(OpcodeType.OP_1))); } Assert.False(stratisNode.FullNode.NodeService <MempoolOrphans>().AddOrphanTx(i, tx)); } Assert.Equal(100, stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count); // Test EraseOrphansFor: for (ulong i = 0; i < 3; i++) { int sizeBefore = stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count; stratisNode.FullNode.NodeService <MempoolOrphans>().EraseOrphansFor(i); Assert.True(stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count < sizeBefore); } // Test LimitOrphanTxSize() function: stratisNode.FullNode.NodeService <MempoolOrphans>().LimitOrphanTxSize(40); Assert.True(stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count <= 40); stratisNode.FullNode.NodeService <MempoolOrphans>().LimitOrphanTxSize(10); Assert.True(stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Count <= 10); stratisNode.FullNode.NodeService <MempoolOrphans>().LimitOrphanTxSize(0); Assert.True(!stratisNode.FullNode.NodeService <MempoolOrphans>().OrphansList().Any()); } }
public void TxMempoolBlockDoublespend() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode stratisNodeSync = builder.CreateStratisPowNode(); builder.StartAll(); stratisNodeSync.NotInIBD(); stratisNodeSync.FullNode.NodeService <MempoolSettings>().RequireStandard = true; // make sure to test standard tx stratisNodeSync.SetDummyMinerSecret(new BitcoinSecret(new Key(), stratisNodeSync.FullNode.Network)); stratisNodeSync.GenerateStratisWithMiner(100); // coinbase maturity = 100 TestHelper.WaitLoop(() => stratisNodeSync.FullNode.ConsensusLoop().Tip.HashBlock == stratisNodeSync.FullNode.Chain.Tip.HashBlock); TestHelper.WaitLoop(() => stratisNodeSync.FullNode.GetBlockStoreTip().HashBlock == stratisNodeSync.FullNode.Chain.Tip.HashBlock); // Make sure skipping validation of transctions that were // validated going into the memory pool does not allow // double-spends in blocks to pass validation when they should not. Script scriptPubKey = PayToPubkeyHashTemplate.Instance.GenerateScriptPubKey(stratisNodeSync.MinerSecret.PubKey); Block genBlock = stratisNodeSync.FullNode.BlockStoreManager().BlockRepository.GetAsync(stratisNodeSync.FullNode.Chain.GetBlock(1).HashBlock).Result; // Create a double-spend of mature coinbase txn: var spends = new List <Transaction>(2); foreach (int index in Enumerable.Range(1, 2)) { Transaction trx = stratisNodeSync.FullNode.Network.CreateTransaction(); trx.AddInput(new TxIn(new OutPoint(genBlock.Transactions[0].GetHash(), 0), scriptPubKey)); trx.AddOutput(Money.Cents(11), new Key().PubKey.Hash); // Sign: trx.Sign(stratisNodeSync.FullNode.Network, stratisNodeSync.MinerSecret, false); spends.Add(trx); } // Test 1: block with both of those transactions should be rejected. Block block = stratisNodeSync.GenerateStratis(1, spends).Single(); TestHelper.WaitLoop(() => stratisNodeSync.FullNode.ConsensusLoop().Tip.HashBlock == stratisNodeSync.FullNode.Chain.Tip.HashBlock); Assert.True(stratisNodeSync.FullNode.Chain.Tip.HashBlock != block.GetHash()); // Test 2: ... and should be rejected if spend1 is in the memory pool Assert.True(stratisNodeSync.AddToStratisMempool(spends[0])); block = stratisNodeSync.GenerateStratis(1, spends).Single(); TestHelper.WaitLoop(() => stratisNodeSync.FullNode.ConsensusLoop().Tip.HashBlock == stratisNodeSync.FullNode.Chain.Tip.HashBlock); Assert.True(stratisNodeSync.FullNode.Chain.Tip.HashBlock != block.GetHash()); stratisNodeSync.FullNode.MempoolManager().Clear().Wait(); // Test 3: ... and should be rejected if spend2 is in the memory pool Assert.True(stratisNodeSync.AddToStratisMempool(spends[1])); block = stratisNodeSync.GenerateStratis(1, spends).Single(); TestHelper.WaitLoop(() => stratisNodeSync.FullNode.ConsensusLoop().Tip.HashBlock == stratisNodeSync.FullNode.Chain.Tip.HashBlock); Assert.True(stratisNodeSync.FullNode.Chain.Tip.HashBlock != block.GetHash()); stratisNodeSync.FullNode.MempoolManager().Clear().Wait(); // Final sanity test: first spend in mempool, second in block, that's OK: var oneSpend = new List <Transaction>(); oneSpend.Add(spends[0]); Assert.True(stratisNodeSync.AddToStratisMempool(spends[1])); block = stratisNodeSync.GenerateStratis(1, oneSpend).Single(); TestHelper.WaitLoop(() => stratisNodeSync.FullNode.ConsensusLoop().Tip.HashBlock == stratisNodeSync.FullNode.Chain.Tip.HashBlock); Assert.True(stratisNodeSync.FullNode.Chain.Tip.HashBlock == block.GetHash()); // spends[1] should have been removed from the mempool when the // block with spends[0] is accepted: TestHelper.WaitLoop(() => stratisNodeSync.FullNode.MempoolManager().MempoolSize().Result == 0); } }
public void MiningNodeWithOneConnectionAlwaysSynced() { const string walletName = "dummyWallet"; const string walletPassword = "******"; const string walletPassphrase = "dummyPassphrase"; const string accountName = "account 0"; string testFolderPath = Path.Combine(this.GetType().Name, nameof(MiningNodeWithOneConnectionAlwaysSynced)); using (NodeBuilder nodeBuilder = NodeBuilder.Create(testFolderPath)) { CoreNode minerNode = nodeBuilder.CreateStratisPowNode(this.powNetwork); minerNode.Start(); minerNode.NotInIBD(); minerNode.FullNode.WalletManager().CreateWallet(walletPassword, walletName, walletPassphrase); CoreNode connectorNode = nodeBuilder.CreateStratisPowNode(this.powNetwork); connectorNode.Start(); connectorNode.NotInIBD(); connectorNode.FullNode.WalletManager().CreateWallet(walletPassword, walletName, walletPassphrase); CoreNode firstNode = nodeBuilder.CreateStratisPowNode(this.powNetwork); firstNode.Start(); firstNode.NotInIBD(); firstNode.FullNode.WalletManager().CreateWallet(walletPassword, walletName, walletPassphrase); CoreNode secondNode = nodeBuilder.CreateStratisPowNode(this.powNetwork); secondNode.Start(); secondNode.NotInIBD(); secondNode.FullNode.WalletManager().CreateWallet(walletPassword, walletName, walletPassphrase); TestHelper.ConnectAndSync(minerNode, connectorNode); TestHelper.ConnectAndSync(connectorNode, firstNode); TestHelper.ConnectAndSync(connectorNode, secondNode); TestHelper.ConnectAndSync(firstNode, secondNode); List <CoreNode> nodes = new List <CoreNode> { minerNode, connectorNode, firstNode, secondNode }; TestHelper.WaitForNodeToSync(nodes.ToArray()); nodes.ForEach(n => { TestHelper.MineBlocks(n, walletName, walletPassword, accountName, 1); TestHelper.WaitForNodeToSync(nodes.ToArray()); }); int networkHeight = minerNode.FullNode.Chain.Height; Assert.Equal(networkHeight, nodes.Count); // Random node on network generates a block. firstNode.GenerateStratisWithMiner(1); // Wait until connector get the hash of network's block. while ((connectorNode.FullNode.ChainBehaviorState.ConsensusTip.HashBlock != firstNode.FullNode.ChainBehaviorState.ConsensusTip.HashBlock) || (firstNode.FullNode.ChainBehaviorState.ConsensusTip.Height == networkHeight)) { Thread.Sleep(1); } Assert.Equal(connectorNode.FullNode.Chain.Tip.HashBlock, firstNode.FullNode.Chain.Tip.HashBlock); Assert.Equal(minerNode.FullNode.Chain.Tip.Height, networkHeight); Assert.Equal(connectorNode.FullNode.Chain.Tip.Height, networkHeight + 1); // Miner mines the block. minerNode.GenerateStratisWithMiner(1); TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(minerNode)); networkHeight++; Assert.Equal(connectorNode.FullNode.Chain.Tip.HashBlock, firstNode.FullNode.Chain.Tip.HashBlock); Assert.Equal(minerNode.FullNode.Chain.Tip.Height, networkHeight); Assert.Equal(connectorNode.FullNode.Chain.Tip.Height, networkHeight); connectorNode.GenerateStratisWithMiner(1); networkHeight++; TestHelper.WaitForNodeToSync(nodes.ToArray()); nodes.All(n => n.FullNode.Chain.Height == networkHeight).Should() .BeTrue(because: "all nodes have synced to chain height"); Assert.Equal(firstNode.FullNode.Chain.Tip.HashBlock, minerNode.FullNode.Chain.Tip.HashBlock); } }
public void Given_NodesAreSynced_When_ABigReorgHappens_Then_TheReorgIsIgnored() { using (NodeBuilder builder = NodeBuilder.Create(this)) { CoreNode stratisMiner = builder.CreateStratisPosNode(this.posNetwork); CoreNode stratisSyncer = builder.CreateStratisPosNode(this.posNetwork); CoreNode stratisReorg = builder.CreateStratisPosNode(this.posNetwork); builder.StartAll(); stratisMiner.NotInIBD().WithWallet(walletPassword, walletName, walletPassphrase); stratisSyncer.NotInIBD().WithWallet(walletPassword, walletName, walletPassphrase); stratisReorg.NotInIBD().WithWallet(walletPassword, walletName, walletPassphrase); TestHelper.MineBlocks(stratisMiner, walletName, walletPassword, walletAccount, 1); // wait for block repo for block sync to work TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(stratisMiner)); stratisMiner.CreateRPCClient().AddNode(stratisReorg.Endpoint, true); stratisMiner.CreateRPCClient().AddNode(stratisSyncer.Endpoint, true); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisMiner, stratisSyncer)); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisMiner, stratisReorg)); // create a reorg by mining on two different chains // ================================================ stratisMiner.CreateRPCClient().RemoveNode(stratisReorg.Endpoint); stratisSyncer.CreateRPCClient().RemoveNode(stratisReorg.Endpoint); TestHelper.WaitLoop(() => !TestHelper.IsNodeConnected(stratisReorg)); TestHelper.MineBlocks(stratisMiner, walletName, walletPassword, walletAccount, 11); TestHelper.MineBlocks(stratisReorg, walletName, walletPassword, walletAccount, 12); TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(stratisMiner)); TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(stratisReorg)); // make sure the nodes are actually on different chains. Assert.NotEqual(stratisMiner.FullNode.Chain.GetBlock(2).HashBlock, stratisReorg.FullNode.Chain.GetBlock(2).HashBlock); TestHelper.TriggerSync(stratisSyncer); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisMiner, stratisSyncer)); // The hash before the reorg node is connected. uint256 hashBeforeReorg = stratisMiner.FullNode.Chain.Tip.HashBlock; // connect the reorg chain stratisMiner.CreateRPCClient().AddNode(stratisReorg.Endpoint, true); stratisSyncer.CreateRPCClient().AddNode(stratisReorg.Endpoint, true); // trigger nodes to sync TestHelper.TriggerSync(stratisMiner); TestHelper.TriggerSync(stratisReorg); TestHelper.TriggerSync(stratisSyncer); // wait for the synced chain to get headers updated. TestHelper.WaitLoop(() => !stratisReorg.FullNode.ConnectionManager.ConnectedPeers.Any()); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisMiner, stratisSyncer)); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisReorg, stratisMiner) == false); TestHelper.WaitLoop(() => TestHelper.AreNodesSynced(stratisReorg, stratisSyncer) == false); // check that a reorg did not happen. Assert.Equal(hashBeforeReorg, stratisSyncer.FullNode.Chain.Tip.HashBlock); } }