public PruningWorker(WorkerConfig workerConfig, ICoreDaemon coreDaemon, IStorageManager storageManager, ChainStateWorker chainStateWorker) : base("PruningWorker", workerConfig.initialNotify, workerConfig.minIdleTime, workerConfig.maxIdleTime) { this.coreDaemon = coreDaemon; this.storageManager = storageManager; this.chainStateWorker = chainStateWorker; this.prunedChain = new ChainBuilder(); this.Mode = PruningMode.None; }
private void RollbackTransaction() { if (!this.inTransaction) { throw new InvalidOperationException(); } this.chainStateBuilderStorage.RollbackTransaction(); this.chain = this.savedChain.ToBuilder(); this.inTransaction = false; }
public TestBlocks(TestBlocks parent) { this.random = parent.random; this.txManager = parent.txManager; this.coinbasePrivateKey = parent.coinbasePrivateKey; this.coinbasePublicKey = parent.coinbasePublicKey; this.miner = parent.miner; this.rules = parent.rules; this.blocks = parent.blocks.ToImmutable().ToBuilder(); this.chain = parent.chain.ToImmutable().ToBuilder(); }
public void TestCreateFromEnumerable() { // create a chain var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); // create builder from enumerable var chainBuilder = new ChainBuilder(new[] { header0, header1 }); // verify CollectionAssert.AreEqual(new[] { header0, header1 }, chainBuilder.Blocks); CollectionAssert.AreEquivalent(new Dictionary<UInt256, ChainedHeader> { { header0.Hash, header0 }, { header1.Hash, header1 } }, chainBuilder.BlocksByHash); }
public ChainStateBuilder(Logger logger, IBlockchainRules rules, CoreStorage coreStorage) { this.logger = logger; this.sha256 = new SHA256Managed(); this.rules = rules; this.coreStorage = coreStorage; this.blockValidator = new BlockValidator(this.coreStorage, this.rules, this.logger); this.chainStateCursorHandle = coreStorage.OpenChainStateCursor(); this.chainStateCursor = this.chainStateCursorHandle.Item; this.chain = new ChainBuilder(chainStateCursor.ReadChain()); this.utxoBuilder = new UtxoBuilder(chainStateCursor, logger); this.commitLock = new ReaderWriterLockSlim(); this.stats = new BuilderStats(); }
public void TestGenesisBlock() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(); // verify genesis with 0 blocks Assert.IsNull(chainBuilder.GenesisBlock); // verify genesis with 1 block chainBuilder.AddBlock(header0); Assert.AreEqual(header0, chainBuilder.GenesisBlock); // verify genesis with 2 blocks chainBuilder.AddBlock(header1); Assert.AreEqual(header0, chainBuilder.GenesisBlock); }
public ChainStateBuilder(ChainBuilder chain, Utxo parentUtxo, Logger logger, IKernel kernel, IBlockchainRules rules, BlockHeaderCache blockHeaderCache, BlockCache blockCache, SpentTransactionsCache spentTransactionsCache, SpentOutputsCache spentOutputsCache) { this.logger = logger; this.sha256 = new SHA256Managed(); this.rules = rules; this.blockHeaderCache = blockHeaderCache; this.blockCache = blockCache; this.spentTransactionsCache = spentTransactionsCache; this.spentOutputsCache = spentOutputsCache; this.chainStateMonitor = new ChainStateMonitor(this.logger); this.scriptValidator = new ScriptValidator(this.logger, this.rules); this.chainStateMonitor.Subscribe(this.scriptValidator); this.chain = chain; this.chainStateBuilderStorage = kernel.Get <IChainStateBuilderStorage>(new ConstructorArgument("parentUtxo", parentUtxo.Storage)); this.spentTransactions = ImmutableList.CreateBuilder <KeyValuePair <UInt256, SpentTx> >(); this.spentOutputs = ImmutableList.CreateBuilder <KeyValuePair <TxOutputKey, TxOutput> >(); this.stats = new BuilderStats(); }
public void RollbackBlock(ChainedHeader chainedHeader, IEnumerable <BlockTx> blockTxes) { var savedChain = this.chain.ToImmutable(); this.BeginTransaction(); try { // remove the block from the chain this.chain.RemoveBlock(chainedHeader); this.chainStateCursor.RemoveChainedHeader(chainedHeader); // read spent transaction rollback information IImmutableList <SpentTx> spentTxes; if (!this.chainStateCursor.TryGetBlockSpentTxes(chainedHeader.Height, out spentTxes)) { throw new ValidationException(chainedHeader.Height); } var spentTxesDictionary = ImmutableDictionary.CreateRange( spentTxes.Select(spentTx => new KeyValuePair <UInt256, SpentTx>(spentTx.TxHash, spentTx))); // rollback the utxo this.utxoBuilder.RollbackUtxo(chainedHeader, blockTxes, spentTxesDictionary); //TODO this needs to happen in the same transaction // remove the rollback information this.chainStateCursor.TryRemoveBlockSpentTxes(chainedHeader.Height); // commit the chain state this.CommitTransaction(); } catch (Exception) { this.chain = savedChain.ToBuilder(); this.RollbackTransaction(); throw; } }
public void TestHeight() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(); // verify height with 0 blocks var chainEmpty = chainBuilder.ToImmutable(); Assert.AreEqual(-1, chainEmpty.Height); // verify height with 1 block chainBuilder.AddBlock(header0); var chain0 = chainBuilder.ToImmutable(); Assert.AreEqual(0, chain0.Height); // verify height with 2 blocks chainBuilder.AddBlock(header1); var chain1 = chainBuilder.ToImmutable(); Assert.AreEqual(1, chain1.Height); }
public void TestLastBlock() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(); // verify last block with 0 blocks var chainEmpty = chainBuilder.ToImmutable(); Assert.IsNull(chainEmpty.LastBlock); // verify last block with 1 block chainBuilder.AddBlock(header0); var chain0 = chainBuilder.ToImmutable(); Assert.AreEqual(header0, chain0.LastBlock); // verify last block with 2 blocks chainBuilder.AddBlock(header1); var chain1 = chainBuilder.ToImmutable(); Assert.AreEqual(header1, chain1.LastBlock); }
public void TestRemoveBlock() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(new[] { header0, header1 }); // remove header 1 and verify chainBuilder.RemoveBlock(header1); CollectionAssert.AreEqual(new[] { header0 }, chainBuilder.Blocks); CollectionAssert.AreEquivalent(new Dictionary<UInt256, ChainedHeader> { { header0.Hash, header0 } }, chainBuilder.BlocksByHash); // remove header 0 and verify chainBuilder.RemoveBlock(header0); Assert.AreEqual(0, chainBuilder.Blocks.Count); Assert.AreEqual(0, chainBuilder.BlocksByHash.Count); }
public void TestAddBlockInvalid() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var header2 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(); // adding header 1 first should fail AssertMethods.AssertThrows<InvalidOperationException>(() => chainBuilder.AddBlock(header1)); // add header 0 chainBuilder.AddBlock(header0); // adding header 2 without header 1 should fail AssertMethods.AssertThrows<InvalidOperationException>(() => chainBuilder.AddBlock(header2)); }
public void TestBlocksByHash() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(); // verify blocks dictionary with 0 blocks Assert.AreEqual(0, chainBuilder.BlocksByHash.Count); // verify blocks dictionary with 1 block chainBuilder.AddBlock(header0); CollectionAssert.AreEquivalent(new Dictionary<UInt256, ChainedHeader> { { header0.Hash, header0 } }, chainBuilder.BlocksByHash); // verify blocks dictionary with 2 blocks chainBuilder.AddBlock(header1); CollectionAssert.AreEquivalent(new Dictionary<UInt256, ChainedHeader> { { header0.Hash, header0 }, { header1.Hash, header1 } }, chainBuilder.BlocksByHash); }
public void TestBlocks() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(); // verify block list with 0 blocks Assert.AreEqual(0, chainBuilder.Blocks.Count); // verify block list with 1 block chainBuilder.AddBlock(header0); CollectionAssert.AreEqual(new[] { header0 }, chainBuilder.Blocks); // verify block list with 2 blocks chainBuilder.AddBlock(header1); CollectionAssert.AreEqual(new[] { header0, header1 }, chainBuilder.Blocks); }
public void TestTotalWork() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var totalWork0 = DataCalculator.CalculateWork(header0); var totalWork1 = totalWork0 + DataCalculator.CalculateWork(header1); var chainBuilder = new ChainBuilder(); // verify total work with 0 blocks Assert.AreEqual(0, chainBuilder.TotalWork); // verify total work with 1 block chainBuilder.AddBlock(header0); Assert.AreEqual(totalWork0, chainBuilder.TotalWork); // verify total work with 2 blocks chainBuilder.AddBlock(header1); Assert.AreEqual(totalWork1, chainBuilder.TotalWork); }
/// <summary> /// Create a new Chain instance consisting of a single genesis header. /// </summary> /// <param name="genesisBlock">The genesis header.</param> /// <returns>The Chain instance.</returns> public static Chain CreateForGenesisBlock(ChainedHeader genesisBlock) { var chainBuilder = new ChainBuilder(); chainBuilder.AddBlock(genesisBlock); return chainBuilder.ToImmutable(); }
public void AddBlock(ChainedHeader chainedHeader, IEnumerable <BlockTx> blockTxes) { var savedChain = this.chain.ToImmutable(); this.BeginTransaction(); try { using (var session = this.blockValidator.StartValidation(chainedHeader)) { // add the block to the chain this.chain.AddBlock(chainedHeader); this.chainStateCursor.AddChainedHeader(chainedHeader); // calculate the new block utxo, double spends will be checked for new MethodTimer(false).Time("CalculateUtxo", () => { // ignore transactions on geneis block if (chainedHeader.Height > 0) { foreach (var pendingTx in this.utxoBuilder.CalculateUtxo(this.chain.ToImmutable(), blockTxes.Select(x => x.Transaction))) { this.blockValidator.AddPendingTx(pendingTx); // track stats this.stats.txCount++; this.stats.inputCount += pendingTx.Transaction.Inputs.Length; this.stats.txRateMeasure.Tick(); this.stats.inputRateMeasure.Tick(pendingTx.Transaction.Inputs.Length); } } // finished queuing up block's txes this.blockValidator.CompleteAdding(); // track stats this.stats.blockCount++; }); // wait for block validation to complete this.blockValidator.WaitToComplete(); // check tx loader results if (this.blockValidator.TxLoaderExceptions.Count > 0) { throw new AggregateException(this.blockValidator.TxLoaderExceptions); } // check tx validation results if (this.blockValidator.TxValidatorExceptions.Count > 0) { throw new AggregateException(this.blockValidator.TxValidatorExceptions); } // check script validation results if (this.blockValidator.ScriptValidatorExceptions.Count > 0) { if (!this.rules.IgnoreScriptErrors) { throw new AggregateException(this.blockValidator.ScriptValidatorExceptions); } else { this.logger.Info("Ignoring script errors in block: {0,9:#,##0}, errors: {1:#,##0}".Format2(chainedHeader.Height, this.blockValidator.ScriptValidatorExceptions.Count)); } } } // commit the chain state this.CommitTransaction(); } catch (Exception) { // rollback the chain state on error this.RollbackTransaction(); this.chain = savedChain.ToBuilder(); throw; } // MEASURE: Block Rate this.stats.blockRateMeasure.Tick(); // blockchain processing statistics this.Stats.blockCount++; var logInterval = TimeSpan.FromSeconds(15); if (DateTime.UtcNow - this.Stats.lastLogTime >= logInterval) { this.LogBlockchainProgress(); this.Stats.lastLogTime = DateTime.UtcNow; } }
public void TestNavigateTowardsInvalidChains() { // create distinct chains var fakeHeadersA = new FakeHeaders(); var header0A = fakeHeadersA.GenesisChained(); var header1A = fakeHeadersA.NextChained(); var fakeHeadersB = new FakeHeaders(); var header0B = fakeHeadersB.GenesisChained(); var header1B = fakeHeadersB.NextChained(); var chainEmpty = new ChainBuilder().ToImmutable(); var chainA = new ChainBuilder(new[] { header0A, header1A }).ToImmutable(); var chainB = new ChainBuilder(new[] { header0B, header1B, }).ToImmutable(); // unrelated chains should error AssertMethods.AssertThrows<InvalidOperationException>(() => chainA.NavigateTowards(chainB).ToList()); AssertMethods.AssertThrows<InvalidOperationException>(() => chainB.NavigateTowards(chainA).ToList()); }
public void TestTotalWork() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var totalWork0 = DataCalculator.CalculateWork(header0); var totalWork1 = totalWork0 + DataCalculator.CalculateWork(header1); var chainBuilder = new ChainBuilder(); // verify total work with 0 blocks var chainEmpty = chainBuilder.ToImmutable(); Assert.AreEqual(0, chainEmpty.TotalWork); // verify total work with 1 block chainBuilder.AddBlock(header0); var chain0 = chainBuilder.ToImmutable(); Assert.AreEqual(totalWork0.ToBigInteger(), chain0.TotalWork); // verify total work with 2 blocks chainBuilder.AddBlock(header1); var chain1 = chainBuilder.ToImmutable(); Assert.AreEqual(totalWork1.ToBigInteger(), chain1.TotalWork); }
public void TestNavigateTowardsFunc() { // create chains var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var header2 = fakeHeaders.NextChained(); var header3 = fakeHeaders.NextChained(); var chain0 = new ChainBuilder(new[] { header0, }).ToImmutable(); var chain1 = new ChainBuilder(new[] { header0, header1, }).ToImmutable(); var chain2 = new ChainBuilder(new[] { header0, header1, header2 }).ToImmutable(); var chain3 = new ChainBuilder(new[] { header0, header1, header2, header3 }).ToImmutable(); // the list of target chains to use, stays 1 ahead and then catches up with chain 3 var targetStack = new Stack<Chain>(new[] { chain1, chain2, chain3, chain3 }.Reverse()); // verify navigating towards an updating chain CollectionAssert.AreEqual( new[] { Tuple.Create(+1, header1), Tuple.Create(+1, header2), Tuple.Create(+1, header3) } , chain0.NavigateTowards(() => targetStack.Pop()).ToList()); // verify all targets used Assert.AreEqual(0, targetStack.Count); }
public void TestNavigateTowards() { // create forked chains var fakeHeadersA = new FakeHeaders(); var header0 = fakeHeadersA.GenesisChained(); var header1 = fakeHeadersA.NextChained(); var fakeHeadersB = new FakeHeaders(fakeHeadersA); var header2A = fakeHeadersA.NextChained(); var header3A = fakeHeadersA.NextChained(); var header4A = fakeHeadersA.NextChained(); var header2B = fakeHeadersB.NextChained(); var header3B = fakeHeadersB.NextChained(); var header4B = fakeHeadersB.NextChained(); var chain0 = new ChainBuilder(new[] { header0, header1, header2A, header3A }).ToImmutable(); var chain1 = new ChainBuilder(new[] { header0, header1, header2B, header3B, header4B }).ToImmutable(); // verify path from chain 0 to chain 1 CollectionAssert.AreEqual( new[] { Tuple.Create(-1, header3A), Tuple.Create(-1, header2A), Tuple.Create(+1, header2B), Tuple.Create(+1, header3B), Tuple.Create(+1, header4B) } , chain0.NavigateTowards(chain1).ToList()); // verify path from chain 1 to chain 0 CollectionAssert.AreEqual( new[] { Tuple.Create(-1, header4B), Tuple.Create(-1, header3B), Tuple.Create(-1, header2B), Tuple.Create(+1, header2A), Tuple.Create(+1, header3A) } , chain1.NavigateTowards(chain0).ToList()); }
protected override void SubStart() { //this.chainBuilder = Chain.CreateForGenesisBlock(coreDaemon.ChainParams.GenesisChainedHeader).ToBuilder(); //TODO start from the currently processed chain tip since wallet state isn't persisted this.chainBuilder = coreDaemon.CurrentChain.ToBuilder(); }
public static bool TryReadChain(UInt256 blockHash, out Chain chain, Func<UInt256, ChainedHeader> getChainedHeader) { // return an empty chain for null blockHash // when retrieving a chain by its tip, a null tip represents an empty chain if (blockHash == null) { chain = new ChainBuilder().ToImmutable(); return true; } var retrievedHeaders = new List<ChainedHeader>(); var chainedHeader = getChainedHeader(blockHash); if (chainedHeader != null) { var expectedHeight = chainedHeader.Height; do { if (chainedHeader.Height != expectedHeight) { chain = default(Chain); return false; } retrievedHeaders.Add(chainedHeader); expectedHeight--; } while (expectedHeight >= 0 && chainedHeader.PreviousBlockHash != chainedHeader.Hash && (chainedHeader = getChainedHeader(chainedHeader.PreviousBlockHash)) != null); if (retrievedHeaders.Last().Height != 0) { chain = default(Chain); return false; } var chainBuilder = new ChainBuilder(); for (var i = retrievedHeaders.Count - 1; i >= 0; i--) chainBuilder.AddBlock(retrievedHeaders[i]); chain = chainBuilder.ToImmutable(); return true; } else { chain = default(Chain); return false; } }
public void TestRemoveBlockInvalid() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var header2 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(new[] { header0, header1, header2 }); // removing header 1 first should fail AssertMethods.AssertThrows<InvalidOperationException>(() => chainBuilder.RemoveBlock(header1)); // remove header 2 chainBuilder.RemoveBlock(header2); // removing header 0 with header 1 present should fail AssertMethods.AssertThrows<InvalidOperationException>(() => chainBuilder.AddBlock(header0)); }
public void TestToImmutable() { var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var chainBuilder = new ChainBuilder(); // verify to builder with 0 blocks var chainEmpty = chainBuilder.ToImmutable(); Assert.AreEqual(0, chainEmpty.Blocks.Count); Assert.AreEqual(0, chainEmpty.BlocksByHash.Count); // verify to builder with 1 block chainBuilder.AddBlock(header0); var chain0 = chainBuilder.ToImmutable(); CollectionAssert.AreEqual(new[] { header0 }, chain0.Blocks); CollectionAssert.AreEquivalent(new Dictionary<UInt256, ChainedHeader> { { header0.Hash, header0 } }, chain0.BlocksByHash); // verify to builder with 2 blocks chainBuilder.AddBlock(header1); var chain1 = chainBuilder.ToImmutable(); CollectionAssert.AreEqual(new[] { header0, header1 }, chain1.Blocks); CollectionAssert.AreEquivalent(new Dictionary<UInt256, ChainedHeader> { { header0.Hash, header0 }, { header1.Hash, header1 } }, chain1.BlocksByHash); }
public void TestPruneAllData() { var logger = LogManager.CreateNullLogger(); // create genesis block var genesisblock = CreateFakeBlock(1); var genesisHeader = new ChainedHeader(genesisblock.Header, height: 0, totalWork: genesisblock.Header.CalculateWork(), dateSeen: DateTimeOffset.Now); // create a block var txCount = 100; var block = CreateFakeBlock(txCount, genesisblock.Hash); var chainedHeader = ChainedHeader.CreateFromPrev(genesisHeader, block.Header, dateSeen: DateTimeOffset.Now); // create a long chain based off the block, to account for pruning buffer var fakeHeaders = new FakeHeaders(new[] { genesisHeader, chainedHeader }); var chain = new ChainBuilder(Enumerable.Concat(new[] { genesisHeader, chainedHeader }, Enumerable.Range(0, 2000).Select(x => fakeHeaders.NextChained()))).ToImmutable(); // mock core daemon to return the chain var coreDaemon = new Mock<ICoreDaemon>(); coreDaemon.Setup(x => x.CurrentChain).Returns(chain); // create memory storage with the block var storageManager = new MemoryStorageManager(); storageManager.BlockTxesStorage.TryAddBlockTransactions(block.Hash, block.BlockTxes); // initialize the pruning worker var workerConfig = new WorkerConfig(initialNotify: false, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue); using (var pruningWorker = new PruningWorker(workerConfig, coreDaemon.Object, storageManager, null)) // get a chain state cursor using (var handle = storageManager.OpenChainStateCursor()) { var chainStateCursor = handle.Item; // set the pruning worker to prune all data pruningWorker.Mode = PruningMode.TxIndex | PruningMode.BlockSpentIndex | PruningMode.BlockTxesPreserveMerkle; // wire event to wait for work var workFinishedEvent = new AutoResetEvent(false); pruningWorker.OnWorkFinished += () => workFinishedEvent.Set(); // wire event to track exceptions Exception workException = null; pruningWorker.OnWorkError += e => workException = e; // start the worker pruningWorker.Start(); // pick a random pruning order var random = new Random(); var pruneOrderSource = Enumerable.Range(0, txCount).ToList(); var pruneOrder = new List<int>(txCount); while (pruneOrderSource.Count > 0) { var randomIndex = random.Next(pruneOrderSource.Count); pruneOrder.Add(pruneOrderSource[randomIndex]); pruneOrderSource.RemoveAt(randomIndex); } // add an unspent tx for each transaction to storage var unspentTxes = new UnspentTx[block.Transactions.Length]; chainStateCursor.BeginTransaction(); for (var txIndex = 0; txIndex < block.Transactions.Length; txIndex++) { var tx = block.Transactions[txIndex]; var unspentTx = new UnspentTx(tx.Hash, blockIndex: 1, txIndex: txIndex, txVersion: tx.Version, isCoinbase: txIndex == 0, outputStates: new OutputStates(1, OutputState.Spent)); unspentTxes[txIndex] = unspentTx; chainStateCursor.TryAddUnspentTx(unspentTx); } chainStateCursor.CommitTransaction(); // create a memory pruning cursor to verify expected pruning results var pruneCursor = new MemoryMerkleTreePruningCursor<BlockTxNode>(block.BlockTxes.Select(x => (BlockTxNode)x)); // prune each transaction in random order var pruneHeight = 0; foreach (var pruneTxIndex in pruneOrder) { // create a spent tx to prune the transaction var pruneTx = block.Transactions[pruneTxIndex]; var spentTxes = BlockSpentTxes.CreateRange(new[] { unspentTxes[pruneTxIndex].ToSpentTx() }); // store the spent txes for the current pruning block pruneHeight++; chainStateCursor.BeginTransaction(); Assert.IsTrue(chainStateCursor.TryAddBlockSpentTxes(pruneHeight, spentTxes)); pruningWorker.PrunableHeight = pruneHeight; // verify unspent tx is present before pruning Assert.IsTrue(chainStateCursor.ContainsUnspentTx(pruneTx.Hash)); chainStateCursor.CommitTransaction(); // notify the pruning worker and wait pruningWorker.NotifyWork(); workFinishedEvent.WaitOne(); // verify unspent tx is removed after pruning chainStateCursor.BeginTransaction(); Assert.IsFalse(chainStateCursor.ContainsUnspentTx(pruneTx.Hash)); // verify unspent tx outputs are removed after pruning for (var outputIndex = 0; outputIndex < pruneTx.Outputs.Length; outputIndex++) Assert.IsFalse(chainStateCursor.ContainsUnspentTxOutput(new TxOutputKey(pruneTx.Hash, (uint)outputIndex))); // verify the spent txes were removed Assert.IsFalse(chainStateCursor.ContainsBlockSpentTxes(pruneHeight)); chainStateCursor.RollbackTransaction(); // prune to determine expected results MerkleTree.PruneNode(pruneCursor, pruneTxIndex); var expectedPrunedTxes = pruneCursor.ReadNodes().ToList(); // retrieve the actual transaction after pruning IEnumerator<BlockTxNode> actualPrunedTxNodes; Assert.IsTrue(storageManager.BlockTxesStorage.TryReadBlockTxNodes(block.Hash, out actualPrunedTxNodes)); // verify the actual pruned transactions match the expected results CollectionAssert.AreEqual(expectedPrunedTxes, actualPrunedTxNodes.UsingAsEnumerable().ToList()); } // verify all unspent txes were removed chainStateCursor.BeginTransaction(); Assert.AreEqual(0, chainStateCursor.ReadUnspentTransactions().Count()); chainStateCursor.CommitTransaction(); // verify final block with all transactions pruned IEnumerator<BlockTxNode> finalPrunedTxNodes; Assert.IsTrue(storageManager.BlockTxesStorage.TryReadBlockTxNodes(block.Hash, out finalPrunedTxNodes)); var finalPrunedTxesList = finalPrunedTxNodes.UsingAsEnumerable().ToList(); Assert.AreEqual(1, finalPrunedTxesList.Count); Assert.AreEqual(block.Header.MerkleRoot, finalPrunedTxesList.Single().Hash); // verify no work exceptions occurred Assert.IsNull(workException); } }
public void TestNavigateTowardsEmpty() { // create chain var fakeHeaders = new FakeHeaders(); var header0 = fakeHeaders.GenesisChained(); var header1 = fakeHeaders.NextChained(); var header2 = fakeHeaders.NextChained(); var chainEmpty = new ChainBuilder().ToImmutable(); var chain = new ChainBuilder(new[] { header0, header1, header2 }).ToImmutable(); // verify chaining to empty does nothing Assert.AreEqual(0, chainEmpty.NavigateTowards(chainEmpty).Count()); Assert.AreEqual(0, chain.NavigateTowards(chainEmpty).Count()); // verify path from empty chain to chain CollectionAssert.AreEqual( new[] { Tuple.Create(+1, header0), Tuple.Create(+1, header1), Tuple.Create(+1, header2) } , chainEmpty.NavigateTowards(chain).ToList()); }
public void TestAddBlockConfirmingTx() { // create tx spending a previous output that exists var decodedTx = Transaction.Create( 0, ImmutableArray.Create(new TxInput(UInt256.One, 0, ImmutableArray<byte>.Empty, 0)), ImmutableArray.Create(new TxOutput(0, ImmutableArray<byte>.Empty)), 0); var tx = decodedTx.Transaction; // create prev output tx var unspentTx = new UnspentTx(tx.Inputs[0].PrevTxHash, 0, 1, 0, false, new OutputStates(1, OutputState.Unspent)); var txOutput = new TxOutput(0, ImmutableArray<byte>.Empty); // create a fake chain var fakeHeaders = new FakeHeaders(); var genesisHeader = fakeHeaders.GenesisChained(); // create a block confirming the tx var block = Block.Create(RandomData.RandomBlockHeader().With(PreviousBlock: genesisHeader.Hash), ImmutableArray.Create(tx)); var chainedHeader = new ChainedHeader(block.Header, 1, 0, DateTimeOffset.Now); // mock core storage with chained header var coreStorage = new Mock<ICoreStorage>(); var initialChain = new ChainBuilder().ToImmutable(); coreStorage.Setup(x => x.TryReadChain(null, out initialChain)).Returns(true); coreStorage.Setup(x => x.TryGetChainedHeader(chainedHeader.Hash, out chainedHeader)).Returns(true); // mock chain state with prev output var chainState = new Mock<IChainState>(); chainState.Setup(x => x.TryGetUnspentTx(tx.Inputs[0].PrevTxHash, out unspentTx)).Returns(true); chainState.Setup(x => x.TryGetUnspentTxOutput(tx.Inputs[0].PrevTxOutputKey, out txOutput)).Returns(true); // mock core daemon for chain state retrieval var coreDaemon = new Mock<ICoreDaemon>(); coreDaemon.Setup(x => x.GetChainState()).Returns(chainState.Object); using (var unconfirmedTxesBuilder = new UnconfirmedTxesBuilder(coreDaemon.Object, coreStorage.Object, storageManager)) { // add the tx Assert.IsTrue(unconfirmedTxesBuilder.TryAddTransaction(decodedTx)); // add the block unconfirmedTxesBuilder.AddBlock(genesisHeader, Enumerable.Empty<BlockTx>()); unconfirmedTxesBuilder.AddBlock(chainedHeader, block.BlockTxes); // verify the confirmed tx was removed UnconfirmedTx unconfirmedTx; Assert.IsFalse(unconfirmedTxesBuilder.TryGetTransaction(tx.Hash, out unconfirmedTx)); Assert.IsNull(unconfirmedTx); // verify the confirmed tx was de-indexed against its input Assert.AreEqual(0, unconfirmedTxesBuilder.GetTransactionsSpending(tx.Inputs[0].PrevTxOutputKey).Count); } }