public void TestInitWithChain() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = 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.ChainTip).Returns(header1); chainStateCursor.Setup(x => x.TryGetHeader(header0.Hash, out header0)).Returns(true); chainStateCursor.Setup(x => x.TryGetHeader(header1.Hash, out header1)).Returns(true); var chainStateBuilder = new ChainStateBuilder(rules, coreStorage.Object, storageManager.Object); CollectionAssert.AreEqual(new[] { header0, header1 }, chainStateBuilder.Chain.Blocks); }
public void TestMissingHeader() { 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 <IChainStateCursor>(); storageManager.Setup(x => x.OpenChainStateCursor()).Returns( new DisposeHandle <IChainStateCursor>(_ => { }, chainStateCursor.Object)); // don't mock header 1 so it is missing chainStateCursor.Setup(x => x.TryGetHeader(header0.Hash, out header0)).Returns(true); chainStateCursor.Setup(x => x.TryGetHeader(header2.Hash, out header2)).Returns(true); // return header 2 as the chain tip chainStateCursor.Setup(x => x.ChainTip).Returns(header2); // init chain state builder with missing header StorageCorruptException actualEx; AssertMethods.AssertThrows <StorageCorruptException>(() => { var chain = new ChainStateBuilder(rules, coreStorage.Object, storageManager.Object).Chain; }, out actualEx); Assert.AreEqual(StorageType.ChainState, actualEx.StorageType); Assert.AreEqual("ChainState is missing header.", actualEx.Message); }
public void TestInitWithChain() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = 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.ChainTip).Returns(header1); chainStateCursor.Setup(x => x.TryGetHeader(header0.Hash, out header0)).Returns(true); chainStateCursor.Setup(x => x.TryGetHeader(header1.Hash, out header1)).Returns(true); var chainStateBuilder = new ChainStateBuilder(rules, coreStorage.Object, storageManager.Object); CollectionAssert.AreEqual(new[] { header0, header1 }, chainStateBuilder.Chain.Blocks); }
public CoreDaemon(ICoreRules rules, IStorageManager storageManager) { this.rules = rules; this.storageManager = storageManager; coreStorage = new CoreStorage(storageManager); // create chain state builder chainStateBuilder = new ChainStateBuilder(this.rules, coreStorage, this.storageManager); // create unconfirmed txes builder unconfirmedTxesBuilder = new UnconfirmedTxesBuilder(this, coreStorage, this.storageManager); // create workers targetChainWorker = new TargetChainWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMilliseconds(0), maxIdleTime: TimeSpan.FromSeconds(30)), ChainParams, coreStorage); chainStateWorker = new ChainStateWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMilliseconds(0), maxIdleTime: TimeSpan.FromSeconds(5)), targetChainWorker, chainStateBuilder, this.rules, coreStorage); unconfirmedTxesWorker = new UnconfirmedTxesWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMilliseconds(0), maxIdleTime: TimeSpan.FromSeconds(5)), chainStateWorker, unconfirmedTxesBuilder, coreStorage); pruningWorker = new PruningWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromSeconds(0), maxIdleTime: TimeSpan.FromMinutes(5)), this, this.storageManager, chainStateWorker); defragWorker = new DefragWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMinutes(5), maxIdleTime: TimeSpan.FromMinutes(5)), this.storageManager); gcWorker = new WorkerMethod("GC Worker", GcWorker, initialNotify: true, minIdleTime: TimeSpan.FromMinutes(5), maxIdleTime: TimeSpan.FromMinutes(5)); utxoScanWorker = new WorkerMethod("UTXO Scan Worker", UtxoScanWorker, initialNotify: true, minIdleTime: TimeSpan.FromSeconds(60), maxIdleTime: TimeSpan.FromSeconds(60)); statsWorker = new StatsWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMinutes(0), maxIdleTime: TimeSpan.MaxValue), this); // wire events chainStateWorker.BlockMissed += HandleBlockMissed; targetChainWorker.OnTargetChainChanged += HandleTargetChainChanged; chainStateWorker.OnChainStateChanged += HandleChainStateChanged; pruningWorker.OnWorkFinished += defragWorker.NotifyWork; unconfirmedTxesBuilder.UnconfirmedTxAdded += RaiseUnconfirmedTxAdded; unconfirmedTxesBuilder.TxesConfirmed += RaiseTxesConfirmed; unconfirmedTxesBuilder.TxesUnconfirmed += RaiseTxesUnconfirmed; }
public PruningWorker(WorkerConfig workerConfig, CoreStorage coreStorage, ChainStateWorker chainStateWorker, ChainStateBuilder chainStateBuilder, Logger logger, IBlockchainRules rules) : base("PruningWorker", workerConfig.initialNotify, workerConfig.minIdleTime, workerConfig.maxIdleTime, logger) { this.logger = logger; this.coreStorage = coreStorage; this.chainStateWorker = chainStateWorker; this.chainStateBuilder = chainStateBuilder; this.rules = rules; this.lastPruneHeight = 0; this.Mode = PruningMode.RollbackAndBlocks; }
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 TestDoubleSpend() { // prepare test kernel var kernel = new StandardKernel(new MemoryStorageModule()); // prepare block var fakeHeaders = new FakeHeaders(); var chainedHeader = new ChainedHeader(fakeHeaders.Genesis(), height: 0, totalWork: 0); // prepare an unspent transaction var txHash = new UInt256(100); var unspentTx = new UnspentTx(chainedHeader.Hash, 1, OutputState.Unspent); // mock a parent utxo containing the unspent transaction var unspentTransactions = ImmutableDictionary.Create <UInt256, UnspentTx>().Add(txHash, unspentTx); var mockParentChainStateStorage = new Mock <IChainStateStorage>(); mockParentChainStateStorage.Setup(utxo => utxo.UnspentTransactions()).Returns(unspentTransactions); var parentUtxo = new Utxo(mockParentChainStateStorage.Object); // initialize memory utxo builder storage var memoryChainStateBuilderStorage = new MemoryChainStateBuilderStorage(mockParentChainStateStorage.Object); kernel.Rebind <IChainStateBuilderStorage>().ToConstant(memoryChainStateBuilderStorage); // initialize utxo builder var chainStateBuilder = new ChainStateBuilder(null, parentUtxo, LogManager.CreateNullLogger(), kernel, null, null, null, null, null); // create an input to spend the unspent transaction var input = new TxInput(new TxOutputKey(txHash, txOutputIndex: 0), ImmutableArray.Create <byte>(), 0); var tx = new Transaction(0, ImmutableArray.Create(input), ImmutableArray.Create <TxOutput>(), 0); // spend the input chainStateBuilder.Spend(0, tx, 0, input, chainedHeader); // verify utxo storage Assert.IsFalse(memoryChainStateBuilderStorage.UnspentTransactionsDictionary.ContainsKey(txHash)); // attempt to spend the input again chainStateBuilder.Spend(0, tx, 0, input, chainedHeader); // validation exception should be thrown }
public ChainStateWorker(WorkerConfig workerConfig, TargetChainWorker targetChainWorker, ChainStateBuilder chainStateBuilder, IBlockchainRules rules, CoreStorage coreStorage) : base("ChainStateWorker", workerConfig.initialNotify, workerConfig.minIdleTime, workerConfig.maxIdleTime) { this.rules = rules; this.coreStorage = coreStorage; this.blockProcessingDurationMeasure = new DurationMeasure(sampleCutoff: TimeSpan.FromMinutes(5)); this.blockMissCountMeasure = new CountMeasure(TimeSpan.FromSeconds(30)); this.targetChainWorker = targetChainWorker; this.chainStateBuilder = chainStateBuilder; this.currentChain = new Lazy<Chain>(() => this.chainStateBuilder.Chain); this.coreStorage.BlockInvalidated += HandleChanged; this.coreStorage.BlockTxesAdded += HandleChanged; this.coreStorage.BlockTxesRemoved += HandleChanged; this.coreStorage.ChainedHeaderAdded += HandleChanged; this.targetChainWorker.OnTargetChainChanged += HandleChanged; }
public ChainStateWorker(WorkerConfig workerConfig, TargetChainWorker targetChainWorker, ChainStateBuilder chainStateBuilder, ICoreRules rules, CoreStorage coreStorage) : base("ChainStateWorker", workerConfig.initialNotify, workerConfig.minIdleTime, workerConfig.maxIdleTime) { this.rules = rules; this.coreStorage = coreStorage; this.blockProcessingDurationMeasure = new DurationMeasure(sampleCutoff: TimeSpan.FromMinutes(5)); this.blockMissCountMeasure = new CountMeasure(TimeSpan.FromSeconds(30)); this.targetChainWorker = targetChainWorker; this.chainStateBuilder = chainStateBuilder; this.currentChain = new Lazy <Chain>(() => this.chainStateBuilder.Chain); this.coreStorage.BlockInvalidated += HandleChanged; this.coreStorage.BlockTxesAdded += HandleChanged; this.coreStorage.BlockTxesRemoved += HandleChanged; this.coreStorage.ChainedHeaderAdded += HandleChanged; this.coreStorage.ChainedHeaderRemoved += HandleChanged; this.targetChainWorker.OnTargetChainChanged += HandleChanged; }
public ChainStateWorker(TargetChainWorker targetChainWorker, ChainStateBuilder chainStateBuilder, Func <Chain> getTargetChain, WorkerConfig workerConfig, Logger logger, IKernel kernel, IBlockchainRules rules, BlockCache blockCache, SpentTransactionsCache spentTransactionsCache, InvalidBlockCache invalidBlockCache) : base("ChainStateWorker", workerConfig.initialNotify, workerConfig.minIdleTime, workerConfig.maxIdleTime, logger) { this.logger = logger; this.getTargetChain = getTargetChain; this.kernel = kernel; this.rules = rules; this.blockCache = blockCache; this.spentTransactionsCache = spentTransactionsCache; this.invalidBlockCache = invalidBlockCache; this.blockProcessingDurationMeasure = new DurationMeasure(); this.targetChainWorker = targetChainWorker; this.chainStateBuilder = chainStateBuilder; this.currentChain = this.chainStateBuilder.Chain.ToImmutable(); this.pruningWorker = kernel.Get <PruningWorker>( new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: false, minIdleTime: TimeSpan.FromSeconds(30), maxIdleTime: TimeSpan.FromMinutes(5))), new ConstructorArgument("getChainStateBuilder", (Func <ChainStateBuilder>)(() => this.chainStateBuilder))); }
public CoreDaemon(IBlockchainRules rules, IStorageManager storageManager) { this.rules = rules; this.storageManager = storageManager; this.coreStorage = new CoreStorage(storageManager); // create chain state builder this.chainStateBuilder = new ChainStateBuilder(this.rules, this.coreStorage, this.storageManager); // create workers this.targetChainWorker = new TargetChainWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMilliseconds(50), maxIdleTime: TimeSpan.FromSeconds(30)), this.rules, this.coreStorage); this.chainStateWorker = new ChainStateWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMilliseconds(0), maxIdleTime: TimeSpan.FromSeconds(5)), this.targetChainWorker, this.chainStateBuilder, this.rules, this.coreStorage); this.pruningWorker = new PruningWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromSeconds(0), maxIdleTime: TimeSpan.FromMinutes(5)), this, this.storageManager, this.chainStateWorker); this.defragWorker = new DefragWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMinutes(5), maxIdleTime: TimeSpan.FromMinutes(5)), this.storageManager); this.gcWorker = new WorkerMethod("GC Worker", GcWorker, initialNotify: true, minIdleTime: TimeSpan.FromMinutes(5), maxIdleTime: TimeSpan.FromMinutes(5)); this.utxoScanWorker = new WorkerMethod("UTXO Scan Worker", UtxoScanWorker, initialNotify: true, minIdleTime: TimeSpan.FromSeconds(60), maxIdleTime: TimeSpan.FromSeconds(60)); // wire events this.chainStateWorker.BlockMissed += HandleBlockMissed; this.targetChainWorker.OnTargetChainChanged += HandleTargetChainChanged; this.chainStateWorker.OnChainStateChanged += HandleChainStateChanged; this.pruningWorker.OnWorkFinished += this.defragWorker.NotifyWork; }
private TxOutput LookupPreviousOutput(TxOutputKey txOutputKey, ChainedBlock chainedBlock, Dictionary <UInt256, int> blockTxIndices, ChainStateBuilder chainStateBuilder) { TxOutput prevOutput; if (chainStateBuilder.TryGetOutput(txOutputKey, out prevOutput)) { return(prevOutput); } else { Transaction prevTx; int prevTxIndex; if (blockTxIndices.TryGetValue(txOutputKey.TxHash, out prevTxIndex)) { Debug.Assert(prevTxIndex >= 0 && prevTxIndex < chainedBlock.Transactions.Count); prevTx = chainedBlock.Transactions[prevTxIndex]; Debug.Assert(prevTx.Hash == txOutputKey.TxHash); } else { throw new ValidationException(chainedBlock.Hash); } var outputIndex = unchecked ((int)txOutputKey.TxOutputIndex); if (outputIndex < 0 || outputIndex >= prevTx.Outputs.Count) { throw new ValidationException(chainedBlock.Hash); } return(prevTx.Outputs[outputIndex]); } }
public void TestMissingHeader() { 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<IChainStateCursor>(); storageManager.Setup(x => x.OpenChainStateCursor()).Returns( new DisposeHandle<IChainStateCursor>(_ => { }, chainStateCursor.Object)); // don't mock header 1 so it is missing chainStateCursor.Setup(x => x.TryGetHeader(header0.Hash, out header0)).Returns(true); chainStateCursor.Setup(x => x.TryGetHeader(header2.Hash, out header2)).Returns(true); // return header 2 as the chain tip chainStateCursor.Setup(x => x.ChainTip).Returns(header2); // init chain state builder with missing header StorageCorruptException actualEx; AssertMethods.AssertThrows<StorageCorruptException>(() => { var chain = new ChainStateBuilder(rules, coreStorage.Object, storageManager.Object).Chain; }, out actualEx); Assert.AreEqual(StorageType.ChainState, actualEx.StorageType); Assert.AreEqual("ChainState is missing header.", actualEx.Message); }
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)); } }
public void TestSimpleSpend() { // prepare test kernel var kernel = new StandardKernel(new MemoryStorageModule()); // prepare block var fakeHeaders = new FakeHeaders(); var chainedHeader = new ChainedHeader(fakeHeaders.Genesis(), height: 0, totalWork: 0); // prepare an unspent transaction var txHash = new UInt256(100); var unspentTx = new UnspentTx(chainedHeader.Hash, 3, OutputState.Unspent); // prepare unspent output var unspentTransactions = ImmutableDictionary.Create <UInt256, UnspentTx>().Add(txHash, unspentTx); var unspentOutputs = ImmutableDictionary.Create <TxOutputKey, TxOutput>() .Add(new TxOutputKey(txHash, 0), new TxOutput(0, ImmutableArray.Create <byte>())) .Add(new TxOutputKey(txHash, 1), new TxOutput(0, ImmutableArray.Create <byte>())) .Add(new TxOutputKey(txHash, 2), new TxOutput(0, ImmutableArray.Create <byte>())); // mock a parent utxo containing the unspent transaction var mockParentChainStateStorage = new Mock <IChainStateStorage>(); mockParentChainStateStorage.Setup(utxo => utxo.UnspentTransactions()).Returns(unspentTransactions); mockParentChainStateStorage.Setup(utxo => utxo.UnspentOutputs()).Returns(unspentOutputs); var parentUtxo = new Utxo(mockParentChainStateStorage.Object); // initialize memory utxo builder storage var memoryChainStateBuilderStorage = new MemoryChainStateBuilderStorage(mockParentChainStateStorage.Object); kernel.Rebind <IChainStateBuilderStorage>().ToConstant(memoryChainStateBuilderStorage); // initialize utxo builder var chainStateBuilder = new ChainStateBuilder(null, parentUtxo, LogManager.CreateNullLogger(), kernel, null, null, null, null, null); // create an input to spend the unspent transaction's first output var input0 = new TxInput(new TxOutputKey(txHash, txOutputIndex: 0), ImmutableArray.Create <byte>(), 0); var tx0 = new Transaction(0, ImmutableArray.Create(input0), ImmutableArray.Create <TxOutput>(), 0); // spend the input chainStateBuilder.Spend(0, tx0, 0, input0, chainedHeader); // verify utxo storage Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary.ContainsKey(txHash)); Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary[txHash].OutputStates.Length == 3); Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary[txHash].OutputStates[0] == OutputState.Spent); Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary[txHash].OutputStates[1] == OutputState.Unspent); Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary[txHash].OutputStates[2] == OutputState.Unspent); // create an input to spend the unspent transaction's second output var input1 = new TxInput(new TxOutputKey(txHash, txOutputIndex: 1), ImmutableArray.Create <byte>(), 0); var tx1 = new Transaction(0, ImmutableArray.Create(input1), ImmutableArray.Create <TxOutput>(), 0); // spend the input chainStateBuilder.Spend(1, tx1, 1, input1, chainedHeader); // verify utxo storage Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary.ContainsKey(txHash)); Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary[txHash].OutputStates.Length == 3); Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary[txHash].OutputStates[0] == OutputState.Spent); Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary[txHash].OutputStates[1] == OutputState.Spent); Assert.IsTrue(memoryChainStateBuilderStorage.UnspentTransactionsDictionary[txHash].OutputStates[2] == OutputState.Unspent); // create an input to spend the unspent transaction's third output var input2 = new TxInput(new TxOutputKey(txHash, txOutputIndex: 2), ImmutableArray.Create <byte>(), 0); var tx2 = new Transaction(0, ImmutableArray.Create(input2), ImmutableArray.Create <TxOutput>(), 0); // spend the input chainStateBuilder.Spend(2, tx2, 2, input2, chainedHeader); // verify utxo storage Assert.IsFalse(memoryChainStateBuilderStorage.UnspentTransactionsDictionary.ContainsKey(txHash)); }
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)); } }
//TODO utxo needs to be as-at transaction, with regards to a transaction being fully spent and added back in in the same block public virtual void ValidateTransaction(ChainedBlock chainedBlock, Transaction tx, int txIndex, ChainStateBuilder chainStateBuilder, out long unspentValue, Dictionary <UInt256, int> blockTxIndices) { unspentValue = -1; // verify spend amounts var txInputValue = (UInt64)0; var txOutputValue = (UInt64)0; for (var inputIndex = 0; inputIndex < tx.Inputs.Count; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevOutput = LookupPreviousOutput(input.PreviousTxOutputKey, chainedBlock, blockTxIndices, chainStateBuilder); // add transactions previous value to unspent amount (used to calculate allowed coinbase reward) txInputValue += prevOutput.Value; } for (var outputIndex = 0; outputIndex < tx.Outputs.Count; outputIndex++) { // remove transactions spend value from unspent amount (used to calculate allowed coinbase reward) var output = tx.Outputs[outputIndex]; txOutputValue += output.Value; } // ensure that amount being output from transaction isn't greater than amount being input if (txOutputValue > txInputValue) { throw new ValidationException(chainedBlock.Hash, "Failing tx {0}: Transaction output value is greater than input value".Format2(tx.Hash.ToHexNumberString())); } // calculate unspent value unspentValue = (long)(txInputValue - txOutputValue); // sanity check if (unspentValue < 0) { throw new ValidationException(chainedBlock.Hash); } // all validation has passed }
public CoreDaemon(Logger logger, IKernel kernel, IBlockchainRules rules, IStorageManager storageManager) { this.logger = logger; this.shutdownToken = new CancellationTokenSource(); this.kernel = kernel; this.rules = rules; this.storageManager = storageManager; this.coreStorage = new CoreStorage(storageManager, logger); // write genesis block out to storage this.coreStorage.AddGenesisBlock(this.rules.GenesisChainedHeader); this.coreStorage.TryAddBlock(this.rules.GenesisBlock); // create chain state builder this.chainStateBuilder = new ChainStateBuilder(this.logger, this.rules, this.coreStorage); // add genesis block to chain state, if needed if (this.chainStateBuilder.Chain.Height < 0) { this.chainStateBuilder.AddBlock(this.rules.GenesisChainedHeader, this.rules.GenesisBlock.Transactions); } // create workers this.targetChainWorker = new TargetChainWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMilliseconds(50), maxIdleTime: TimeSpan.FromSeconds(30)), this.logger, this.rules, this.coreStorage); this.chainStateWorker = new ChainStateWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMilliseconds(50), maxIdleTime: TimeSpan.FromSeconds(5)), this.targetChainWorker, this.chainStateBuilder, this.logger, this.rules, this.coreStorage); this.pruningWorker = new PruningWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromSeconds(60), maxIdleTime: TimeSpan.FromMinutes(15)), this.coreStorage, this.chainStateWorker, this.chainStateBuilder, this.logger, this.rules); this.defragWorker = new DefragWorker( new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromMinutes(5), maxIdleTime: TimeSpan.FromMinutes(5)), this.coreStorage, this.logger); // notify defrag worker after pruning this.pruningWorker.OnWorkFinished += this.defragWorker.NotifyWork; this.chainStateWorker.BlockMissed += HandleBlockMissed; this.targetChainWorker.OnTargetChainChanged += () => { var handler = this.OnTargetChainChanged; if (handler != null) { handler(this, EventArgs.Empty); } }; this.chainStateWorker.OnChainStateChanged += () => { this.pruningWorker.NotifyWork(); this.utxoScanWorker.NotifyWork(); var handler = this.OnChainStateChanged; if (handler != null) { handler(this, EventArgs.Empty); } }; this.gcWorker = new WorkerMethod("GC Worker", _ => { this.logger.Info( string.Join("\n", new string('-', 80), "GC Memory: {0,10:#,##0.00} MB", "Process Memory: {1,10:#,##0.00} MB", new string('-', 80) ) .Format2 ( /*0*/ (float)GC.GetTotalMemory(false) / 1.MILLION(), /*1*/ (float)Process.GetCurrentProcess().PrivateMemorySize64 / 1.MILLION() )); }, initialNotify: true, minIdleTime: TimeSpan.FromSeconds(30), maxIdleTime: TimeSpan.FromSeconds(30), logger: this.logger); this.utxoScanWorker = new WorkerMethod("UTXO Scan Worker", _ => { // time taking chain state snapshots var stopwatch = Stopwatch.StartNew(); int chainStateHeight; using (var chainState = this.GetChainState()) { chainStateHeight = chainState.Chain.Height; } stopwatch.Stop(); this.logger.Info("GetChainState at {0:#,##0}: {1:#,##0.00}s".Format2(chainStateHeight, stopwatch.Elapsed.TotalSeconds)); // time enumerating chain state snapshots stopwatch = Stopwatch.StartNew(); using (var chainState = this.GetChainState()) { chainStateHeight = chainState.Chain.Height; chainState.ReadUnspentTransactions().Count(); } stopwatch.Stop(); this.logger.Info("Enumerate chain state at {0:#,##0}: {1:#,##0.00}s".Format2(chainStateHeight, stopwatch.Elapsed.TotalSeconds)); //using (var chainStateLocal = this.GetChainState()) //{ // new MethodTimer(this.logger).Time("UTXO Commitment: {0:#,##0}".Format2(chainStateLocal.UnspentTxCount), () => // { // using (var utxoStream = new UtxoStream(this.logger, chainStateLocal.ReadUnspentTransactions())) // { // var sha256 = new SHA256Managed(); // var utxoHash = sha256.ComputeHash(utxoStream); // this.logger.Info("UXO Commitment Hash: {0}".Format2(utxoHash.ToHexNumberString())); // } // }); // //new MethodTimer().Time("Full UTXO Scan: {0:#,##0}".Format2(chainStateLocal.Utxo.TransactionCount), () => // //{ // // var sha256 = new SHA256Managed(); // // foreach (var output in chainStateLocal.Utxo.GetUnspentTransactions()) // // { // // } // //}); //} }, initialNotify: true, minIdleTime: TimeSpan.FromSeconds(60), maxIdleTime: TimeSpan.FromSeconds(60), logger: this.logger); }
public CoreDaemon(Logger logger, IKernel kernel, IBlockchainRules rules, BlockHeaderCache blockHeaderCache, ChainedHeaderCache chainedHeaderCache, BlockTxHashesCache blockTxHashesCache, TransactionCache transactionCache, BlockCache blockCache) { this.logger = logger; this.shutdownToken = new CancellationTokenSource(); this.kernel = kernel; this.rules = rules; this.blockHeaderCache = blockHeaderCache; this.chainedHeaderCache = chainedHeaderCache; this.blockTxHashesCache = blockTxHashesCache; this.transactionCache = transactionCache; this.blockCache = blockCache; // write genesis block out to storage this.blockHeaderCache[this.rules.GenesisBlock.Hash] = this.rules.GenesisBlock.Header; this.blockCache[this.rules.GenesisBlock.Hash] = this.rules.GenesisBlock; this.chainedHeaderCache[this.rules.GenesisChainedHeader.Hash] = this.rules.GenesisChainedHeader; // wire up cache events this.blockHeaderCache.OnAddition += OnBlockHeaderAddition; this.blockHeaderCache.OnModification += OnBlockHeaderModification; this.blockCache.OnAddition += OnBlockAddition; this.blockCache.OnModification += OnBlockModification; this.blockTxHashesCache.OnAddition += OnBlockTxHashesAddition; this.blockTxHashesCache.OnModification += OnBlockTxHashesModification; this.chainedHeaderCache.OnAddition += OnChainedHeaderAddition; this.chainedHeaderCache.OnModification += OnChainedHeaderModification; // create chain state builder this.chainStateBuilder = this.kernel.Get <ChainStateBuilder>( new ConstructorArgument("chain", Chain.CreateForGenesisBlock(this.rules.GenesisChainedHeader).ToBuilder()), new ConstructorArgument("parentUtxo", Utxo.CreateForGenesisBlock(this.rules.GenesisBlock.Hash))); this.chainStateLock = new ReaderWriterLockSlim(); // create workers this.chainingWorker = kernel.Get <ChainingWorker>( new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromSeconds(0), maxIdleTime: TimeSpan.FromSeconds(30)))); this.targetChainWorker = kernel.Get <TargetChainWorker>( new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.FromSeconds(0), maxIdleTime: TimeSpan.FromSeconds(30)))); this.chainStateWorker = kernel.Get <ChainStateWorker>( new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.FromSeconds(5))), new ConstructorArgument("getTargetChain", (Func <Chain>)(() => this.targetChainWorker.TargetChain)), new ConstructorArgument("targetChainWorker", this.targetChainWorker), new ConstructorArgument("chainStateBuilder", this.chainStateBuilder)); this.targetChainWorker.OnTargetBlockChanged += () => { var handler = this.OnTargetBlockChanged; if (handler != null) { handler(this, EventArgs.Empty); } }; this.targetChainWorker.OnTargetChainChanged += () => { this.chainStateWorker.NotifyWork(); var handler = this.OnTargetChainChanged; if (handler != null) { handler(this, EventArgs.Empty); } }; this.chainStateWorker.OnChainStateChanged += () => { this.utxoScanWorker.NotifyWork(); //TODO once fully synced, this should save off the immutable snapshot immediately //TODO this will allow there to always be an active chain state once synced this.chainStateLock.DoWrite(() => this.chainState = null); var handler = this.OnChainStateChanged; if (handler != null) { handler(this, EventArgs.Empty); } }; this.gcWorker = new WorkerMethod("GC Worker", () => { this.logger.Info( string.Join("\n", new string('-', 80), "GC Memory: {0,10:#,##0.00} MB", "Process Memory: {1,10:#,##0.00} MB", new string('-', 80) ) .Format2 ( /*0*/ (float)GC.GetTotalMemory(false) / 1.MILLION(), /*1*/ (float)Process.GetCurrentProcess().PrivateMemorySize64 / 1.MILLION() )); }, initialNotify: true, minIdleTime: TimeSpan.FromSeconds(30), maxIdleTime: TimeSpan.FromSeconds(30), logger: this.logger); this.utxoScanWorker = new WorkerMethod("UTXO Scan Worker", () => { var chainStateLocal = this.GetChainState(); if (chainStateLocal == null) { return; } new MethodTimer().Time("Full UTXO Scan: {0:#,##0}".Format2(chainStateLocal.Utxo.OutputCount), () => { var sha256 = new SHA256Managed(); foreach (var output in chainStateLocal.Utxo.GetUnspentOutputs()) { if (new UInt256(sha256.ComputeDoubleHash(output.Value.ScriptPublicKey.ToArray())) == UInt256.Zero) { } } }); }, initialNotify: true, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue, logger: this.logger); }
private void TestRollback(ITestStorageProvider provider) { var logger = LogManager.CreateNullLogger(); var sha256 = new SHA256Managed(); var blockProvider = new MainnetBlockProvider(); var blocks = Enumerable.Range(0, 500).Select(x => blockProvider.GetBlock(x)).ToList(); var genesisBlock = blocks[0]; var genesisHeader = new ChainedHeader(genesisBlock.Header, height: 0, totalWork: 0); var genesisChain = Chain.CreateForGenesisBlock(genesisHeader); var rules = new MainnetRules(logger); using (var storageManager = provider.OpenStorageManager()) using (var coreStorage = new CoreStorage(storageManager, logger)) using (var chainStateBuilder = new ChainStateBuilder(logger, rules, coreStorage)) { // add blocks to storage coreStorage.AddGenesisBlock(ChainedHeader.CreateForGenesisBlock(blocks[0].Header)); foreach (var block in blocks) { coreStorage.TryAddBlock(block); } // calculate utxo forward and store its state at each step along the way var expectedUtxos = new List <List <UnspentTx> >(); for (var blockIndex = 0; blockIndex < blocks.Count; blockIndex++) { var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0); chainStateBuilder.AddBlock(chainedHeader, block.Transactions); using (var chainState = chainStateBuilder.ToImmutable()) { expectedUtxos.Add(chainState.ReadUnspentTransactions().ToList()); } } // verify the utxo state before rolling back //TODO verify the UTXO hash hard-coded here is correct var expectedUtxoHash = UInt256.Parse("609eb5882e0b71a707fb876c844fbfe6b4579e04eb27c7c0cefbb7478bac737b", NumberStyles.HexNumber); using (var utxoStream = new UtxoStream(logger, expectedUtxos.Last())) { var utxoHash = new UInt256(sha256.ComputeDoubleHash(utxoStream)); Assert.AreEqual(expectedUtxoHash, utxoHash); } expectedUtxos.RemoveAt(expectedUtxos.Count - 1); // roll utxo backwards and validate its state at each step along the way for (var blockIndex = blocks.Count - 1; blockIndex >= 1; blockIndex--) { var block = blocks[blockIndex]; var chainedHeader = new ChainedHeader(block.Header, blockIndex, 0); var blockTxes = block.Transactions.Select((tx, txIndex) => new BlockTx(txIndex, 0, tx.Hash, /*pruned:*/ false, tx)); chainStateBuilder.RollbackBlock(chainedHeader, blockTxes); var expectedUtxo = expectedUtxos.Last(); expectedUtxos.RemoveAt(expectedUtxos.Count - 1); List <UnspentTx> actualUtxo; using (var chainState = chainStateBuilder.ToImmutable()) { actualUtxo = chainState.ReadUnspentTransactions().ToList(); } CollectionAssert.AreEqual(expectedUtxo, actualUtxo, "UTXO differs at height: {0}".Format2(blockIndex)); } } }
public virtual void ValidateBlock(ChainedBlock chainedBlock, ChainStateBuilder chainStateBuilder) { //TODO if (BypassValidation) { return; } // calculate the next required target var requiredTarget = GetRequiredNextTarget(chainStateBuilder.Chain.ToImmutable()); // validate block's target against the required target var blockTarget = chainedBlock.Header.CalculateTarget(); if (blockTarget > requiredTarget) { throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Block target {2} did not match required target of {3}".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height, blockTarget.ToHexNumberString(), requiredTarget.ToHexNumberString())); } // validate block's proof of work against its stated target if (chainedBlock.Hash > blockTarget || chainedBlock.Hash > requiredTarget) { throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Block did not match its own target of {2}".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height, blockTarget.ToHexNumberString())); } // ensure there is at least 1 transaction if (chainedBlock.Transactions.Count == 0) { throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Zero transactions present".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height)); } //TODO apply real coinbase rule // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 var coinbaseTx = chainedBlock.Transactions[0]; // check that coinbase has only one input if (coinbaseTx.Inputs.Count != 1) { throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Coinbase transaction does not have exactly one input".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height)); } var blockTxIndices = new Dictionary <UInt256, int>(); for (var i = 0; i < chainedBlock.Transactions.Count; i++) { blockTxIndices.Add(chainedBlock.Transactions[i].Hash, i); } // validate transactions long blockUnspentValue = 0L; for (var txIndex = 1; txIndex < chainedBlock.Transactions.Count; txIndex++) { var tx = chainedBlock.Transactions[txIndex]; long txUnspentValue; ValidateTransaction(chainedBlock, tx, txIndex, chainStateBuilder, out txUnspentValue, blockTxIndices); blockUnspentValue += txUnspentValue; } // calculate the expected reward in coinbase var expectedReward = (long)(50 * SATOSHI_PER_BTC); if (chainedBlock.Height / 210000 <= 32) { expectedReward /= (long)Math.Pow(2, chainedBlock.Height / 210000); } expectedReward += blockUnspentValue; // calculate the actual reward in coinbase var actualReward = 0L; foreach (var txOutput in coinbaseTx.Outputs) { actualReward += (long)txOutput.Value; } // ensure coinbase has correct reward if (actualReward > expectedReward) { throw new ValidationException(chainedBlock.Hash, "Failing block {0} at height {1}: Coinbase value is greater than reward + fees".Format2(chainedBlock.Hash.ToHexNumberString(), chainedBlock.Height)); } // all validation has passed }