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);
            }
        }
Beispiel #4
0
        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()); });
            }
        }
Beispiel #5
0
        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));
            }
        }
Beispiel #7
0
        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));
            }
        }