public async Task ReorgChain_FailsFullValidation_Reconnect_OldChain_FromSecondMiner_DisconnectedAsync() { using (var builder = NodeBuilder.Create(this)) { var noValidationRulesNetwork = new BitcoinRegTestNoValidationRules(); var minerA = builder.CreateStratisPowNode(this.powNetwork, "cmfr-3-minerA").WithDummyWallet().Start(); var syncer = builder.CreateStratisPowNode(this.powNetwork, "cmfr-3-syncer").Start(); var minerB = builder.CreateStratisPowNode(noValidationRulesNetwork, "cmfr-3-minerB").NoValidation().WithDummyWallet().Start(); // MinerA mines 5 blocks TestHelper.MineBlocks(minerA, 5); // MinerB and Syncer syncs with MinerA TestHelper.ConnectAndSync(minerB, minerA); TestHelper.ConnectAndSync(syncer, minerA); // Disconnect minerB from miner A TestHelper.Disconnect(minerB, minerA); TestHelper.WaitLoop(() => !TestHelper.IsNodeConnected(minerB)); // Miner A continues to mine to height 9 TestHelper.MineBlocks(minerA, 4); TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 9); TestHelper.WaitLoop(() => minerB.FullNode.ConsensusManager().Tip.Height == 5); TestHelper.WaitLoop(() => syncer.FullNode.ConsensusManager().Tip.Height == 9); // Disconnect syncer from minerA TestHelper.Disconnect(syncer, minerA); TestHelper.WaitLoop(() => !TestHelper.IsNodeConnected(minerA)); // MinerB mines 5 more blocks: // Block 6,7,9,10 = valid // Block 8 = invalid await TestHelper.BuildBlocks.OnNode(minerB).Amount(5).Invalid(8, (coreNode, block) => BlockBuilder.InvalidCoinbaseReward(coreNode, block)).BuildAsync(); // Reconnect syncer to minerB causing the following to happen: // Reorg from blocks 9 to 5. // Connect blocks 5 to 10 // Block 8 fails. // Reorg from 7 to 5 // Reconnect blocks 6 to 9 TestHelper.ConnectNoCheck(syncer, minerB); TestHelper.AreNodesSynced(minerA, syncer); TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 9); TestHelper.WaitLoop(() => minerB.FullNode.ConsensusManager().Tip.Height == 10); TestHelper.WaitLoop(() => syncer.FullNode.ConsensusManager().Tip.Height == 9); } }
public async Task ReorgChain_FailsFullValidation_ChainHasBlocksAndHeadersOnly_NodesDisconnectedAsync() { using (var builder = NodeBuilder.Create(this)) { var noValidationRulesNetwork = new BitcoinRegTestNoValidationRules(); var minerA = builder.CreateStratisPowNode(this.powNetwork, "cmfr-6-minerA").WithDummyWallet().Start(); var minerB = builder.CreateStratisPowNode(this.powNetwork, "cmfr-6-minerB").WithDummyWallet().Start(); var minerC = builder.CreateStratisPowNode(noValidationRulesNetwork, "cmfr-6-minerC").NoValidation().WithDummyWallet().Start(); // Mine 10 blocks with minerA. We cannot use a premade chain as it adversely affects the max tip age calculation, causing sporadic sync errors. TestHelper.MineBlocks(minerA, 10); TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 10); // MinerB and MinerC syncs with MinerA TestHelper.ConnectAndSync(minerA, minerB, minerC); // Disconnect MinerC from MinerA TestHelper.Disconnect(minerA, minerC); // MinerA continues to mine to height 14 TestHelper.MineBlocks(minerA, 4); TestHelper.WaitLoopMessage(() => { return(minerA.FullNode.ConsensusManager().Tip.Height == 14, minerA.FullNode.ConsensusManager().Tip.Height.ToString()); }); TestHelper.WaitLoopMessage(() => { return(minerB.FullNode.ConsensusManager().Tip.Height == 14, minerB.FullNode.ConsensusManager().Tip.Height.ToString()); }); TestHelper.WaitLoopMessage(() => { return(minerC.FullNode.ConsensusManager().Tip.Height == 10, minerC.FullNode.ConsensusManager().Tip.Height.ToString()); }); // MinerB continues to mine to block 13 without block propogation TestHelper.DisableBlockPropagation(minerB, minerA); TestHelper.MineBlocks(minerB, 4); TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerA, 14)); TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerB, 18)); TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerC, 10)); // MinerB mines 5 more blocks: // Block 6,7,9,10 = valid // Block 8 = invalid await TestHelper.BuildBlocks.OnNode(minerC).Amount(5).Invalid(13, (coreNode, block) => BlockBuilder.InvalidCoinbaseReward(coreNode, block)).BuildAsync(); // Reconnect MinerA to MinerC. TestHelper.ConnectNoCheck(minerA, minerC); // MinerC should be disconnected from MinerA TestHelper.WaitLoop(() => !TestHelper.IsNodeConnectedTo(minerA, minerC)); // This will cause the reorg chain to fail at block 8 and roll back any changes. TestHelper.WaitLoopMessage(() => { return(minerA.FullNode.ConsensusManager().Tip.Height == 14, minerA.FullNode.ConsensusManager().Tip.Height.ToString()); }); TestHelper.WaitLoopMessage(() => { return(minerB.FullNode.ConsensusManager().Tip.Height == 18, minerB.FullNode.ConsensusManager().Tip.Height.ToString()); }); TestHelper.WaitLoopMessage(() => { return(minerC.FullNode.ConsensusManager().Tip.Height == 15, minerC.FullNode.ConsensusManager().Tip.Height.ToString()); }); } }
public async Task ReorgChain_FailsFullValidation_Reconnect_OldChain_Nodes_ConnectedAsync() { using (var builder = NodeBuilder.Create(this)) { var bitcoinNoValidationRulesNetwork = new BitcoinRegTestNoValidationRules(); var minerA = builder.CreateStratisPowNode(this.powNetwork, "cmfr-1-minerA").WithDummyWallet().WithReadyBlockchainData(ReadyBlockchain.BitcoinRegTest10Miner); var minerB = builder.CreateStratisPowNode(bitcoinNoValidationRulesNetwork, "cmfr-1-minerB").NoValidation().WithDummyWallet().Start(); ChainedHeader minerBChainTip = null; bool interceptorsEnabled = false; bool minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain = false; bool minerA_IsConnecting_To_MinerBChain = false; bool minerA_Disconnected_MinerBsChain = false; bool minerA_Reconnected_Its_OwnChain = false; // Configure the interceptor to intercept when Miner A connects Miner B's chain. void interceptorConnect(ChainedHeaderBlock chainedHeaderBlock) { if (!interceptorsEnabled) { return; } if (!minerA_IsConnecting_To_MinerBChain) { if (chainedHeaderBlock.ChainedHeader.Height == 12) { minerA_IsConnecting_To_MinerBChain = minerA.FullNode.ConsensusManager().Tip.HashBlock == minerBChainTip.GetAncestor(12).HashBlock; } return; } if (!minerA_Reconnected_Its_OwnChain) { if (chainedHeaderBlock.ChainedHeader.Height == 14) { minerA_Reconnected_Its_OwnChain = true; } return; } } // Configure the interceptor to intercept when Miner A disconnects Miner B's chain after the reorg. void interceptorDisconnect(ChainedHeaderBlock chainedHeaderBlock) { if (!interceptorsEnabled) { return; } if (!minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain) { if (minerA.FullNode.ConsensusManager().Tip.Height == 10) { minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain = true; } return; } if (!minerA_Disconnected_MinerBsChain) { if (minerA.FullNode.ConsensusManager().Tip.Height == 10) { minerA_Disconnected_MinerBsChain = true; } return; } } minerA.Start(); minerA.SetConnectInterceptor(interceptorConnect); minerA.SetDisconnectInterceptor(interceptorDisconnect); // Miner B syncs with Miner A TestHelper.ConnectAndSync(minerB, minerA); // Disable Miner A from sending blocks to Miner B TestHelper.DisableBlockPropagation(minerA, minerB); // Miner A continues to mine to height 14 TestHelper.MineBlocks(minerA, 4); TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 14); Assert.Equal(10, minerB.FullNode.ConsensusManager().Tip.Height); // Enable the interceptors so that they are active during the reorg. interceptorsEnabled = true; // Miner B mines 5 more blocks: // Block 6,7,9,10 = valid // Block 8 = invalid minerBChainTip = await TestHelper.BuildBlocks.OnNode(minerB).Amount(5).Invalid(13, (coreNode, block) => BlockBuilder.InvalidCoinbaseReward(coreNode, block)).BuildAsync(); Assert.Equal(15, minerBChainTip.Height); Assert.Equal(15, minerB.FullNode.ConsensusManager().Tip.Height); // Wait until Miner A disconnected its own chain so that it can connect to // Miner B's longer chain. TestHelper.WaitLoop(() => minerA_Disconnected_ItsOwnChain_ToConnectTo_MinerBs_LongerChain); // Wait until Miner A has connected Miner B's chain (but failed) TestHelper.WaitLoop(() => minerA_IsConnecting_To_MinerBChain); // Wait until Miner A has disconnected Miner B's invalid chain. TestHelper.WaitLoop(() => minerA_Disconnected_MinerBsChain); // Wait until Miner A has reconnected its own chain. TestHelper.WaitLoop(() => minerA_Reconnected_Its_OwnChain); } }
public void ReorgChain_FailsFullValidation_ChainHasBlocksAndHeadersOnly_NodesDisconnected() { using (var builder = NodeBuilder.Create(this)) { var noValidationRulesNetwork = new BitcoinRegTestNoValidationRules(); var minerA = builder.CreateStratisPowNode(this.powNetwork).WithDummyWallet().Start(); var minerB = builder.CreateStratisPowNode(this.powNetwork).WithDummyWallet().Start(); var minerC = builder.CreateStratisPowNode(noValidationRulesNetwork).NoValidation().WithDummyWallet().Start(); // MinerA mines 5 blocks TestHelper.MineBlocks(minerA, 5); // MinerB and MinerC syncs with MinerA TestHelper.ConnectAndSync(minerA, minerB, minerC); // Disconnect MinerC from MinerA TestHelper.Disconnect(minerA, minerC); // MinerA continues to mine to height 9 TestHelper.MineBlocks(minerA, 4); TestHelper.WaitLoopMessage(() => { return(minerA.FullNode.ConsensusManager().Tip.Height == 9, minerA.FullNode.ConsensusManager().Tip.Height.ToString()); }); TestHelper.WaitLoopMessage(() => { return(minerB.FullNode.ConsensusManager().Tip.Height == 9, minerB.FullNode.ConsensusManager().Tip.Height.ToString()); }); TestHelper.WaitLoopMessage(() => { return(minerC.FullNode.ConsensusManager().Tip.Height == 5, minerC.FullNode.ConsensusManager().Tip.Height.ToString()); }); // MinerB continues to mine to block 13 without block propogation TestHelper.DisableBlockPropagation(minerB, minerA); TestHelper.MineBlocks(minerB, 4); TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerA, 9)); TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerB, 13)); TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerC, 5)); // MinerB mines 5 more blocks: // Block 6,7,9,10 = valid // Block 8 = invalid TestHelper.BuildBlocks.OnNode(minerC).Amount(5).Invalid(8, (coreNode, block) => BlockBuilder.InvalidCoinbaseReward(coreNode, block)).BuildAsync(); // Reconnect MinerA to MinerC. TestHelper.Connect(minerA, minerC); // MinerC should be disconnected from MinerA TestHelper.WaitLoop(() => !TestHelper.IsNodeConnectedTo(minerA, minerC)); // This will cause the reorg chain to fail at block 8 and roll back any changes. TestHelper.WaitLoopMessage(() => { return(minerA.FullNode.ConsensusManager().Tip.Height == 9, minerA.FullNode.ConsensusManager().Tip.Height.ToString()); }); TestHelper.WaitLoopMessage(() => { return(minerB.FullNode.ConsensusManager().Tip.Height == 13, minerB.FullNode.ConsensusManager().Tip.Height.ToString()); }); TestHelper.WaitLoopMessage(() => { return(minerC.FullNode.ConsensusManager().Tip.Height == 10, minerC.FullNode.ConsensusManager().Tip.Height.ToString()); }); } }
public void ReorgChain_FailsFullValidation_Reconnect_OldChain_Nodes_Connected() { using (var builder = NodeBuilder.Create(this)) { var bitcoinNoValidationRulesNetwork = new BitcoinRegTestNoValidationRules(); var minerA = builder.CreateStratisPowNode(this.powNetwork).WithDummyWallet().Start(); var minerB = builder.CreateStratisPowNode(bitcoinNoValidationRulesNetwork).NoValidation().WithDummyWallet().Start(); // MinerA mines 5 blocks TestHelper.MineBlocks(minerA, 5); // MinerB syncs with MinerA TestHelper.ConnectAndSync(minerB, minerA); // Disable syncer from sending blocks to miner B TestHelper.DisableBlockPropagation(minerA, minerB); // Miner A continues to mine to height 9 TestHelper.MineBlocks(minerA, 4); TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 9); // MinerB mines 5 more blocks: // Block 6,7,9,10 = valid // Block 8 = invalid TestHelper.BuildBlocks.OnNode(minerB).Amount(5).Invalid(8, (coreNode, block) => BlockBuilder.InvalidCoinbaseReward(coreNode, block)).BuildAsync(); // On mining the following will happen: // Reorg from blocks 9 to 5. // Connect blocks 5 to 10 // Block 8 fails. // Reorg from 7 to 5 // Reconnect blocks 6 to 9 TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 9); TestHelper.WaitLoop(() => minerB.FullNode.ConsensusManager().Tip.Height == 10); } }
public async Task ReorgChain_AfterInitialRewind_ChainA_Extension_MinerC_DisconnectsAsync() { using (var builder = NodeBuilder.Create(this)) { var minerA = builder.CreateStratisPowNode(new BitcoinRegTest(), "cmi-1-minerA").WithDummyWallet(); var minerB = builder.CreateStratisPowNode(new BitcoinRegTestNoValidationRules(), "cmi-1-minerB").NoValidation().WithDummyWallet(); var syncer = builder.CreateStratisPowNode(new BitcoinRegTest(), "cmi-1-syncer").WithDummyWallet(); bool minerADisconnectedFromSyncer = false; // Configure the interceptor to disconnect a node after a certain block has been disconnected (rewound). void interceptor(ChainedHeaderBlock chainedHeaderBlock) { if (minerADisconnectedFromSyncer) { return; } if (chainedHeaderBlock.ChainedHeader.Previous.Height == 10) { // Ensure that minerA's tip has rewound to 10. TestBase.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerA, 10)); TestHelper.Disconnect(minerA, syncer); minerADisconnectedFromSyncer = true; return; } } // Start minerA and mine 10 blocks. We cannot use a premade chain as it adversely affects the max tip age calculation, causing sporadic sync errors. minerA.Start(); TestHelper.MineBlocks(minerA, 10); TestBase.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 10); // Start the nodes. minerB.Start(); syncer.Start(); minerA.SetDisconnectInterceptor(interceptor); // minerB and syncer syncs with minerA. TestHelper.ConnectAndSync(minerA, minerB, syncer); // Disconnect minerB from miner so that it can mine on its own and create a fork. TestHelper.Disconnect(minerA, minerB); // MinerA continues to mine to height 14. TestHelper.MineBlocks(minerA, 4); TestBase.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 14); TestBase.WaitLoop(() => minerB.FullNode.ConsensusManager().Tip.Height == 10); TestBase.WaitLoop(() => syncer.FullNode.ConsensusManager().Tip.Height == 14); // minerB mines 5 more blocks: // Block 6,7,9,10 = valid // Block 8 = invalid Assert.False(TestHelper.IsNodeConnected(minerB)); await TestHelper.BuildBlocks.OnNode(minerB).Amount(5).Invalid(13, (node, block) => BlockBuilder.InvalidCoinbaseReward(node, block)).BuildAsync(); // Reconnect minerA to minerB. TestHelper.ConnectNoCheck(minerA, minerB); // minerB should be disconnected from minerA. TestBase.WaitLoop(() => !TestHelper.IsNodeConnectedTo(minerA, minerB)); // syncer should be disconnected from minerA (via interceptor). TestBase.WaitLoop(() => !TestHelper.IsNodeConnectedTo(minerA, syncer)); // The reorg will fail at block 8 and roll back any changes. TestBase.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerA, 14)); TestBase.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerB, 15)); TestBase.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(syncer, 14)); } }
public void ReorgChain_AfterInitialRewind_ChainA_Extension_MinerC_Disconnects() { using (var builder = NodeBuilder.Create(this)) { var network = new BitcoinRegTest(); var minerA = builder.CreateStratisPowNode(network).WithDummyWallet(); var minerB = builder.CreateStratisPowNode(network).NoValidation().WithDummyWallet(); var syncer = builder.CreateStratisPowNode(network).WithDummyWallet(); // Configure the interceptor to disconnect a node after a certain block has been disconnected (rewound). bool interceptor(ChainedHeaderBlock chainedHeaderBlock) { if (chainedHeaderBlock.ChainedHeader.Previous.Height == 5) { // Ensure that minerA's tip has rewound to 5. TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerA, 5)); TestHelper.Disconnect(minerA, syncer); return(true); } return(false); } minerA.BlockDisconnectInterceptor(interceptor); // Start the nodes. minerA.Start(); minerB.Start(); syncer.Start(); // MinerA mines 5 blocks. TestHelper.MineBlocks(minerA, 5); // minerB and syncer syncs with minerA. TestHelper.ConnectAndSync(minerA, minerB, syncer); // Disconnect minerB from miner so that it can mine on its own and create a fork. TestHelper.Disconnect(minerA, minerB); // MinerA continues to mine to height 9. TestHelper.MineBlocks(minerA, 4); TestHelper.WaitLoop(() => minerA.FullNode.ConsensusManager().Tip.Height == 9); TestHelper.WaitLoop(() => minerB.FullNode.ConsensusManager().Tip.Height == 5); TestHelper.WaitLoop(() => syncer.FullNode.ConsensusManager().Tip.Height == 9); // minerB mines 5 more blocks: // Block 6,7,9,10 = valid // Block 8 = invalid Assert.False(TestHelper.IsNodeConnected(minerB)); TestHelper.BuildBlocks.OnNode(minerB).Amount(5).Invalid(8, (node, block) => BlockBuilder.InvalidCoinbaseReward(node, block)).BuildAsync(); // Reconnect minerA to minerB. TestHelper.Connect(minerA, minerB); // minerB should be disconnected from minerA. TestHelper.WaitLoop(() => !TestHelper.IsNodeConnectedTo(minerA, minerB)); // syncer should be disconnected from minerA (via interceptor). TestHelper.WaitLoop(() => !TestHelper.IsNodeConnectedTo(minerA, syncer)); // The reorg will fail at block 8 and roll back any changes. TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerA, 9)); TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(minerB, 10)); TestHelper.WaitLoop(() => TestHelper.IsNodeSyncedAtHeight(syncer, 9)); } }