Example #1
0
        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;
        }
Example #2
0
        private void RollbackTransaction()
        {
            if (!this.inTransaction)
            {
                throw new InvalidOperationException();
            }

            this.chainStateBuilderStorage.RollbackTransaction();
            this.chain         = this.savedChain.ToBuilder();
            this.inTransaction = false;
        }
Example #3
0
        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();
        }
Example #4
0
        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);
        }
Example #5
0
        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();
        }
Example #6
0
        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);
        }
Example #7
0
        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();
        }
Example #8
0
        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;
            }
        }
Example #9
0
        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);
        }
Example #10
0
        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);
        }
Example #11
0
        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);
        }
Example #12
0
        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));
        }
Example #13
0
        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);
        }
Example #14
0
        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);
        }
Example #15
0
        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);
        }
Example #16
0
 /// <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();
 }
Example #17
0
        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;
            }
        }
Example #18
0
        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());
        }
Example #19
0
        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);
        }
Example #20
0
        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);
        }
Example #21
0
        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());
        }
Example #22
0
 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();
 }
Example #23
0
        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;
            }
        }
Example #24
0
        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));
        }
Example #25
0
        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);
        }
Example #26
0
        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);
            }
        }
Example #27
0
        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);
            }
        }