public void TestChainTipOutOfSync() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var header2 = fakeHeaders.NextChained(); var rules = Mock.Of <ICoreRules>(); var coreStorage = new Mock <ICoreStorage>(); var storageManager = new Mock <IStorageManager>(); var chainStateCursor = new Mock <IDeferredChainStateCursor>(); storageManager.Setup(x => x.OpenChainStateCursor()).Returns( new DisposeHandle <IChainStateCursor>(_ => { }, chainStateCursor.Object)); storageManager.Setup(x => x.OpenDeferredChainStateCursor(It.IsAny <IChainState>())).Returns( new DisposeHandle <IDeferredChainStateCursor>(_ => { }, chainStateCursor.Object)); chainStateCursor.Setup(x => x.TryGetHeader(header0.Hash, out header0)).Returns(true); chainStateCursor.Setup(x => x.TryGetHeader(header1.Hash, out header1)).Returns(true); chainStateCursor.Setup(x => x.TryGetHeader(header2.Hash, out header2)).Returns(true); // return header 1 as the chain tip chainStateCursor.Setup(x => x.ChainTip).Returns(header1); // init chain state builder seeing header 1 var chainStateBuilder = new ChainStateBuilder(rules, coreStorage.Object, storageManager.Object); Assert.AreEqual(header1.Hash, chainStateBuilder.Chain.LastBlock.Hash); // alter the chain tip outside of the chain state builder chainStateCursor.Setup(x => x.ChainTip).Returns(header2); // attempt to add block when out of sync ChainStateOutOfSyncException actualEx; AssertMethods.AssertAggregateThrows <ChainStateOutOfSyncException>(() => chainStateBuilder.AddBlockAsync(header2, Enumerable.Empty <BlockTx>()).Wait(), out actualEx); Assert.AreEqual(header1.Hash, actualEx.ExpectedChainTip.Hash); Assert.AreEqual(header2.Hash, actualEx.ActualChainTip.Hash); // attempt to rollback block when out of sync AssertMethods.AssertThrows <ChainStateOutOfSyncException>(() => chainStateBuilder.RollbackBlock(header2, Enumerable.Empty <BlockTx>()), out actualEx); Assert.AreEqual(header1.Hash, actualEx.ExpectedChainTip.Hash); Assert.AreEqual(header2.Hash, actualEx.ActualChainTip.Hash); }
public void TestInvalidMerkleRoot() { // prepare mocks var coreStorage = new Mock <ICoreStorage>(); var storageManager = new Mock <IStorageManager>(); var chainStateCursor = new Mock <IDeferredChainStateCursor>(); storageManager.Setup(x => x.OpenChainStateCursor()).Returns( new DisposeHandle <IChainStateCursor>(_ => { }, chainStateCursor.Object)); storageManager.Setup(x => x.OpenDeferredChainStateCursor(It.IsAny <IChainState>())).Returns( new DisposeHandle <IDeferredChainStateCursor>(_ => { }, chainStateCursor.Object)); chainStateCursor.Setup(x => x.CursorCount).Returns(1); chainStateCursor.Setup(x => x.DataFlowBlocks).Returns(new IDataflowBlock[0]); // prepare a test block var testBlocks = new TestBlocks(); var rules = testBlocks.Rules; var block = testBlocks.MineAndAddBlock(txCount: 10); var chainedHeader = testBlocks.Chain.LastBlock; // create an invalid version of the header where the merkle root is incorrect var invalidChainedHeader = ChainedHeader.CreateFromPrev(rules.ChainParams.GenesisChainedHeader, block.Header.With(MerkleRoot: UInt256.Zero), DateTimeOffset.Now); // mock genesis block & chain tip var genesisHeader = rules.ChainParams.GenesisChainedHeader; chainStateCursor.Setup(x => x.ChainTip).Returns(genesisHeader); chainStateCursor.Setup(x => x.TryGetHeader(genesisHeader.Hash, out genesisHeader)).Returns(true); // mock invalid block chainStateCursor.Setup(x => x.TryGetHeader(chainedHeader.Hash, out invalidChainedHeader)).Returns(true); // init chain state builder var chainStateBuilder = new ChainStateBuilder(rules, coreStorage.Object, storageManager.Object); Assert.AreEqual(rules.ChainParams.GenesisBlock.Hash, chainStateBuilder.Chain.LastBlock.Hash); // attempt to add block with invalid merkle root ValidationException actualEx; AssertMethods.AssertAggregateThrows <ValidationException>(() => chainStateBuilder.AddBlockAsync(invalidChainedHeader, Enumerable.Empty <BlockTx>()).Wait(), out actualEx); // verify error Assert.AreEqual($"Failing block {invalidChainedHeader.Hash} at height 1: Merkle root is invalid", actualEx.Message); }
public void TestChainTipOutOfSync() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var header2 = fakeHeaders.NextChained(); var rules = Mock.Of<ICoreRules>(); var coreStorage = new Mock<ICoreStorage>(); var storageManager = new Mock<IStorageManager>(); var chainStateCursor = new Mock<IDeferredChainStateCursor>(); storageManager.Setup(x => x.OpenChainStateCursor()).Returns( new DisposeHandle<IChainStateCursor>(_ => { }, chainStateCursor.Object)); storageManager.Setup(x => x.OpenDeferredChainStateCursor(It.IsAny<IChainState>())).Returns( new DisposeHandle<IDeferredChainStateCursor>(_ => { }, chainStateCursor.Object)); chainStateCursor.Setup(x => x.TryGetHeader(header0.Hash, out header0)).Returns(true); chainStateCursor.Setup(x => x.TryGetHeader(header1.Hash, out header1)).Returns(true); chainStateCursor.Setup(x => x.TryGetHeader(header2.Hash, out header2)).Returns(true); // return header 1 as the chain tip chainStateCursor.Setup(x => x.ChainTip).Returns(header1); // init chain state builder seeing header 1 var chainStateBuilder = new ChainStateBuilder(rules, coreStorage.Object, storageManager.Object); Assert.AreEqual(header1.Hash, chainStateBuilder.Chain.LastBlock.Hash); // alter the chain tip outside of the chain state builder chainStateCursor.Setup(x => x.ChainTip).Returns(header2); // attempt to add block when out of sync ChainStateOutOfSyncException actualEx; AssertMethods.AssertAggregateThrows<ChainStateOutOfSyncException>(() => chainStateBuilder.AddBlockAsync(header2, Enumerable.Empty<BlockTx>()).Wait(), out actualEx); Assert.AreEqual(header1.Hash, actualEx.ExpectedChainTip.Hash); Assert.AreEqual(header2.Hash, actualEx.ActualChainTip.Hash); // attempt to rollback block when out of sync AssertMethods.AssertThrows<ChainStateOutOfSyncException>(() => chainStateBuilder.RollbackBlock(header2, Enumerable.Empty<BlockTx>()), out actualEx); Assert.AreEqual(header1.Hash, actualEx.ExpectedChainTip.Hash); Assert.AreEqual(header2.Hash, actualEx.ActualChainTip.Hash); }
public void TestInvalidMerkleRoot() { // prepare mocks var coreStorage = new Mock<ICoreStorage>(); var storageManager = new Mock<IStorageManager>(); var chainStateCursor = new Mock<IDeferredChainStateCursor>(); storageManager.Setup(x => x.OpenChainStateCursor()).Returns( new DisposeHandle<IChainStateCursor>(_ => { }, chainStateCursor.Object)); storageManager.Setup(x => x.OpenDeferredChainStateCursor(It.IsAny<IChainState>())).Returns( new DisposeHandle<IDeferredChainStateCursor>(_ => { }, chainStateCursor.Object)); chainStateCursor.Setup(x => x.CursorCount).Returns(1); chainStateCursor.Setup(x => x.DataFlowBlocks).Returns(new IDataflowBlock[0]); // prepare a test block var testBlocks = new TestBlocks(); var rules = testBlocks.Rules; var block = testBlocks.MineAndAddBlock(txCount: 10); var chainedHeader = testBlocks.Chain.LastBlock; // create an invalid version of the header where the merkle root is incorrect var invalidChainedHeader = ChainedHeader.CreateFromPrev(rules.ChainParams.GenesisChainedHeader, block.Header.With(MerkleRoot: UInt256.Zero), DateTimeOffset.Now); // mock genesis block & chain tip var genesisHeader = rules.ChainParams.GenesisChainedHeader; chainStateCursor.Setup(x => x.ChainTip).Returns(genesisHeader); chainStateCursor.Setup(x => x.TryGetHeader(genesisHeader.Hash, out genesisHeader)).Returns(true); // mock invalid block chainStateCursor.Setup(x => x.TryGetHeader(chainedHeader.Hash, out invalidChainedHeader)).Returns(true); // init chain state builder var chainStateBuilder = new ChainStateBuilder(rules, coreStorage.Object, storageManager.Object); Assert.AreEqual(rules.ChainParams.GenesisBlock.Hash, chainStateBuilder.Chain.LastBlock.Hash); // attempt to add block with invalid merkle root ValidationException actualEx; AssertMethods.AssertAggregateThrows<ValidationException>(() => chainStateBuilder.AddBlockAsync(invalidChainedHeader, Enumerable.Empty<BlockTx>()).Wait(), out actualEx); // verify error Assert.AreEqual($"Failing block {invalidChainedHeader.Hash} at height 1: Merkle root is invalid", actualEx.Message); }
private void TestRollback(ITestStorageProvider provider) { ConsoleLoggingModule.Configure(); var logger = LogManager.GetCurrentClassLogger(); var blockCount = 10.THOUSAND(); var checkUtxoHashFrequencey = 1000; var blockProvider = new TestNet3BlockProvider(); var blocks = blockProvider.ReadBlocks().Take(blockCount).ToList(); var genesisBlock = blocks[0]; var genesisHeader = new ChainedHeader(genesisBlock.Header, height: 0, totalWork: 0, dateSeen: DateTimeOffset.Now); var genesisChain = Chain.CreateForGenesisBlock(genesisHeader); var chainParams = new Testnet3Params(); var rules = new CoreRules(chainParams) { IgnoreScripts = true, IgnoreSignatures = true, IgnoreScriptErrors = true }; using (var storageManager = provider.OpenStorageManager()) using (var coreStorage = new CoreStorage(storageManager)) using (var chainStateBuilder = new ChainStateBuilder(rules, coreStorage, storageManager)) { // add blocks to storage coreStorage.AddGenesisBlock(ChainedHeader.CreateForGenesisBlock(blocks[0].Header)); foreach (var block in blocks) { coreStorage.TryAddBlock(block); } // store empty utxo hash var expectedUtxoHashes = new List <UInt256>(); using (var chainState = chainStateBuilder.ToImmutable()) expectedUtxoHashes.Add(UtxoCommitment.ComputeHash(chainState)); // calculate utxo forward and store its state at each step along the way for (var blockIndex = 0; blockIndex < blocks.Count; blockIndex++) { logger.Info($"Adding: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); chainStateBuilder.AddBlockAsync(chainedHeader, block.Transactions.Select( (tx, txIndex) => BlockTx.Create(txIndex, tx))).Wait(); if (blockIndex % checkUtxoHashFrequencey == 0 || blockIndex == blocks.Count - 1) { using (var chainState = chainStateBuilder.ToImmutable()) expectedUtxoHashes.Add(UtxoCommitment.ComputeHash(chainState)); } } // verify the utxo state before rolling back var expectedLastUtxoHash = UInt256.ParseHex("5f155c7d8a5c850d5fb2566aec5110caa40e270184126d17022ae9780fd65fd9"); Assert.AreEqual(expectedLastUtxoHash, expectedUtxoHashes.Last()); expectedUtxoHashes.RemoveAt(expectedUtxoHashes.Count - 1); // roll utxo backwards and validate its state at each step along the way for (var blockIndex = blocks.Count - 1; blockIndex >= 0; blockIndex--) { logger.Info($"Rolling back: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); var blockTxes = block.Transactions.Select((tx, txIndex) => BlockTx.Create(txIndex, tx)); chainStateBuilder.RollbackBlock(chainedHeader, blockTxes); if ((blockIndex - 1) % checkUtxoHashFrequencey == 0 || blockIndex == 0) { var expectedUtxoHash = expectedUtxoHashes.Last(); expectedUtxoHashes.RemoveAt(expectedUtxoHashes.Count - 1); using (var chainState = chainStateBuilder.ToImmutable()) Assert.AreEqual(expectedUtxoHash, UtxoCommitment.ComputeHash(chainState)); } } // verify chain state was rolled all the way back Assert.AreEqual(-1, chainStateBuilder.Chain.Height); Assert.AreEqual(0, expectedUtxoHashes.Count); // calculate utxo forward again for (var blockIndex = 0; blockIndex < blocks.Count; blockIndex++) { logger.Info($"Adding: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); chainStateBuilder.AddBlockAsync(chainedHeader, block.Transactions.Select( (tx, txIndex) => BlockTx.Create(txIndex, tx))).Wait(); } // verify final utxo state again using (var chainState = chainStateBuilder.ToImmutable()) Assert.AreEqual(expectedLastUtxoHash, UtxoCommitment.ComputeHash(chainState)); } }
private void TestRollback(ITestStorageProvider provider) { ConsoleLoggingModule.Configure(); var logger = LogManager.GetCurrentClassLogger(); var blockCount = 10.THOUSAND(); var checkUtxoHashFrequencey = 1000; var blockProvider = new TestNet3BlockProvider(); var blocks = blockProvider.ReadBlocks().Take(blockCount).ToList(); var genesisBlock = blocks[0]; var genesisHeader = new ChainedHeader(genesisBlock.Header, height: 0, totalWork: 0, dateSeen: DateTimeOffset.Now); var genesisChain = Chain.CreateForGenesisBlock(genesisHeader); var chainParams = new Testnet3Params(); var rules = new CoreRules(chainParams) { IgnoreScripts = true, IgnoreSignatures = true, IgnoreScriptErrors = true }; using (var storageManager = provider.OpenStorageManager()) using (var coreStorage = new CoreStorage(storageManager)) using (var chainStateBuilder = new ChainStateBuilder(rules, coreStorage, storageManager)) { // add blocks to storage coreStorage.AddGenesisBlock(ChainedHeader.CreateForGenesisBlock(blocks[0].Header)); foreach (var block in blocks) coreStorage.TryAddBlock(block); // store empty utxo hash var expectedUtxoHashes = new List<UInt256>(); using (var chainState = chainStateBuilder.ToImmutable()) expectedUtxoHashes.Add(UtxoCommitment.ComputeHash(chainState)); // calculate utxo forward and store its state at each step along the way for (var blockIndex = 0; blockIndex < blocks.Count; blockIndex++) { logger.Info($"Adding: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); chainStateBuilder.AddBlockAsync(chainedHeader, block.Transactions.Select( (tx, txIndex) => BlockTx.Create(txIndex, tx))).Wait(); if (blockIndex % checkUtxoHashFrequencey == 0 || blockIndex == blocks.Count - 1) using (var chainState = chainStateBuilder.ToImmutable()) expectedUtxoHashes.Add(UtxoCommitment.ComputeHash(chainState)); } // verify the utxo state before rolling back var expectedLastUtxoHash = UInt256.ParseHex("5f155c7d8a5c850d5fb2566aec5110caa40e270184126d17022ae9780fd65fd9"); Assert.AreEqual(expectedLastUtxoHash, expectedUtxoHashes.Last()); expectedUtxoHashes.RemoveAt(expectedUtxoHashes.Count - 1); // roll utxo backwards and validate its state at each step along the way for (var blockIndex = blocks.Count - 1; blockIndex >= 0; blockIndex--) { logger.Info($"Rolling back: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); var blockTxes = block.Transactions.Select((tx, txIndex) => BlockTx.Create(txIndex, tx)); chainStateBuilder.RollbackBlock(chainedHeader, blockTxes); if ((blockIndex - 1) % checkUtxoHashFrequencey == 0 || blockIndex == 0) { var expectedUtxoHash = expectedUtxoHashes.Last(); expectedUtxoHashes.RemoveAt(expectedUtxoHashes.Count - 1); using (var chainState = chainStateBuilder.ToImmutable()) Assert.AreEqual(expectedUtxoHash, UtxoCommitment.ComputeHash(chainState)); } } // verify chain state was rolled all the way back Assert.AreEqual(-1, chainStateBuilder.Chain.Height); Assert.AreEqual(0, expectedUtxoHashes.Count); // calculate utxo forward again for (var blockIndex = 0; blockIndex < blocks.Count; blockIndex++) { logger.Info($"Adding: {blockIndex:N0}"); var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0, DateTimeOffset.Now); chainStateBuilder.AddBlockAsync(chainedHeader, block.Transactions.Select( (tx, txIndex) => BlockTx.Create(txIndex, tx))).Wait(); } // verify final utxo state again using (var chainState = chainStateBuilder.ToImmutable()) Assert.AreEqual(expectedLastUtxoHash, UtxoCommitment.ComputeHash(chainState)); } }