Beispiel #1
0
        private async Task PruneBlockTxesAsync(PruningMode mode, Chain chain, ChainedHeader pruneBlock, BlockSpentTxes spentTxes)
        {
            if (mode.HasFlag(PruningMode.BlockTxesDelete))
            {
                storageManager.BlockTxesStorage.TryRemoveBlockTransactions(pruneBlock.Hash);
            }
            else if (mode.HasFlag(PruningMode.BlockTxesPreserveMerkle) || mode.HasFlag(PruningMode.BlockTxesDestroyMerkle))
            {
                // create a source of txes to prune sources, for each block
                var pruningQueue = new BufferBlock <Tuple <int, List <int> > >();

                // prepare tx pruner, to prune a txes source for a given block
                var txPruner = new ActionBlock <Tuple <int, List <int> > >(
                    blockWorkItem =>
                {
                    var blockIndex     = blockWorkItem.Item1;
                    var blockHash      = chain.Blocks[blockIndex].Hash;
                    var spentTxIndices = blockWorkItem.Item2;
                    var pruneWorkItem  = new KeyValuePair <UInt256, IEnumerable <int> >(blockHash, spentTxIndices);

                    if (mode.HasFlag(PruningMode.BlockTxesPreserveMerkle))
                    {
                        this.storageManager.BlockTxesStorage.PruneElements(new[] { pruneWorkItem });
                    }
                    else
                    {
                        this.storageManager.BlockTxesStorage.DeleteElements(new[] { pruneWorkItem });
                    }
                },
                    new ExecutionDataflowBlockOptions {
                    MaxDegreeOfParallelism = Environment.ProcessorCount
                });

                pruningQueue.LinkTo(txPruner, new DataflowLinkOptions {
                    PropagateCompletion = true
                });

                // queue spent txes, grouped by block
                await pruningQueue.SendAndCompleteAsync(
                    spentTxes.ReadByBlock().Select(
                        spentTxesByBlock =>
                {
                    var blockIndex = spentTxesByBlock.Item1;
                    var txIndices  = spentTxesByBlock.Item2.Select(x => x.TxIndex).ToList();

                    return(Tuple.Create(blockIndex, txIndices));
                }));

                await txPruner.Completion;
            }
        }
Beispiel #2
0
        private async Task PruneBlockSpentTxes(PruningMode mode, Chain chain, ChainedHeader pruneBlock)
        {
            if (!mode.HasFlag(PruningMode.BlockSpentIndex))
            {
                return;
            }

            using (var handle = this.storageManager.OpenChainStateCursor())
            {
                var chainStateCursor = handle.Item;

                chainStateCursor.BeginTransaction();

                // TODO don't immediately remove list of spent txes per block from chain state,
                //      use an additional safety buffer in case there was an issue pruning block
                //      txes (e.g. didn't flush and crashed), keeping the information  will allow
                //      the block txes pruning to be performed again
                chainStateCursor.TryRemoveBlockSpentTxes(pruneBlock.Height);

                await chainStateCursor.CommitTransactionAsync();
            }
        }
Beispiel #3
0
        private async Task PruneTxIndexAsync(PruningMode mode, Chain chain, ChainedHeader pruneBlock, BlockSpentTxes spentTxes)
        {
            if (!mode.HasFlag(PruningMode.TxIndex))
            {
                return;
            }

            var maxParallelism = Environment.ProcessorCount;

            // prepare a cache of cursors to be used by the pruning action block, allowing a pool of transactions
            var openedCursors = new ConcurrentBag <IChainStateCursor>();

            using (var cursorHandles = new DisposableCache <DisposeHandle <IChainStateCursor> >(maxParallelism,
                                                                                                () =>
            {
                // retrieve a new cursor and start its transaction, keeping track of any cursors opened
                var cursorHandle = this.storageManager.OpenChainStateCursor();
                cursorHandle.Item.BeginTransaction();
                openedCursors.Add(cursorHandle.Item);

                return(cursorHandle);
            }))
            {
                var pruneTxIndex = new ActionBlock <SpentTx>(
                    spentTx =>
                {
                    using (var handle = cursorHandles.TakeItem())
                    {
                        var chainStateCursor = handle.Item.Item;

                        chainStateCursor.RemoveUnspentTx(spentTx.TxHash);
                        for (var outputIndex = 0; outputIndex < spentTx.OutputCount; outputIndex++)
                        {
                            chainStateCursor.RemoveUnspentTxOutput(new TxOutputKey(spentTx.TxHash, (uint)outputIndex));
                        }
                    }
                },
                    new ExecutionDataflowBlockOptions {
                    MaxDegreeOfParallelism = maxParallelism
                });

                var spentTxesQueue = new BufferBlock <SpentTx>();
                spentTxesQueue.LinkTo(pruneTxIndex, new DataflowLinkOptions {
                    PropagateCompletion = true
                });

                await spentTxesQueue.SendAndCompleteAsync(spentTxes);

                await pruneTxIndex.Completion;

                // commit all opened cursors on success
                var commitTasks = new Task[openedCursors.Count];
                var i           = 0;
                foreach (var cursor in openedCursors)
                {
                    commitTasks[i++] = cursor.CommitTransactionAsync();
                }

                await Task.WhenAll(commitTasks);
            }
        }
Beispiel #4
0
        private async Task PruneBlock(PruningMode mode, Chain chain, ChainedHeader pruneBlock)
        {
            //TODO the replay information about blocks that have been rolled back also needs to be pruned (UnmintedTx)

            var txCount                 = 0;
            var totalStopwatch          = Stopwatch.StartNew();
            var pruneBlockTxesStopwatch = new Stopwatch();
            var pruneTxIndexStopwatch   = new Stopwatch();
            var pruneSpentTxesStopwatch = new Stopwatch();

            // retrieve the spent txes for this block
            BlockSpentTxes spentTxes;

            using (var handle = this.storageManager.OpenChainStateCursor())
            {
                var chainStateCursor = handle.Item;

                chainStateCursor.BeginTransaction(readOnly: true);
                chainStateCursor.TryGetBlockSpentTxes(pruneBlock.Height, out spentTxes);
            }

            if (spentTxes != null)
            {
                txCount = spentTxes.Count;

                pruneBlockTxesStopwatch.Start();
                pruneTxIndexStopwatch.Start();

                await Task.WhenAll(
                    // prune block txes (either merkle prune or delete)
                    PruneBlockTxesAsync(mode, chain, pruneBlock, spentTxes)
                    .ContinueWith(_ => pruneBlockTxesStopwatch.Stop()),
                    // prune tx index
                    PruneTxIndexAsync(mode, chain, pruneBlock, spentTxes)
                    .ContinueWith(_ => pruneTxIndexStopwatch.Stop())
                    );

                // remove block spent txes information
                //TODO should have a buffer on removing this, block txes pruning may need it again if flush doesn't happen
                var pruneSpentTxesTask = PruneBlockSpentTxes(mode, chain, pruneBlock);
                await pruneSpentTxesStopwatch.TimeAsync(pruneSpentTxesTask);
            }
            // if spent txes aren't available, block txes can still be deleted entirely for that pruning style
            else if (mode.HasFlag(PruningMode.BlockTxesDelete))
            {
                pruneBlockTxesStopwatch.Start();
                await PruneBlockTxesAsync(mode, chain, pruneBlock, null);

                pruneBlockTxesStopwatch.Stop();
            }
            else //if (pruneBlock.Height > 0)
            {
                //TODO can't throw an exception unless the pruned chain is persisted
                //logger.Info("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: {0:N0}".Format2(pruneBlock.Height));
                //throw new InvalidOperationException();
                txCount = 0;
            }

            // track stats
            txCountMeasure.Tick(txCount);
            txRateMeasure.Tick((float)(txCount / totalStopwatch.Elapsed.TotalSeconds));
            pruneBlockTxesDurationMeasure.Tick(pruneBlockTxesStopwatch.Elapsed);
            pruneTxIndexDurationMeasure.Tick(pruneTxIndexStopwatch.Elapsed);
            pruneSpentTxesDurationMeasure.Tick(pruneSpentTxesStopwatch.Elapsed);
            totalDurationMeasure.Tick(totalStopwatch.Elapsed);
        }
        private void PruneBlockSpentTxes(PruningMode mode, Chain chain, ChainedHeader pruneBlock)
        {
            if (!mode.HasFlag(PruningMode.BlockSpentIndex))
                return;

            using (var handle = this.storageManager.OpenChainStateCursor())
            {
                var chainStateCursor = handle.Item;

                chainStateCursor.BeginTransaction(pruneOnly: true);

                // TODO don't immediately remove list of spent txes per block from chain state,
                //      use an additional safety buffer in case there was an issue pruning block
                //      txes (e.g. didn't flush and crashed), keeping the information  will allow
                //      the block txes pruning to be performed again
                chainStateCursor.TryRemoveBlockSpentTxes(pruneBlock.Height);

                chainStateCursor.CommitTransaction();
            }
        }
        private async Task PruneTxIndexAsync(PruningMode mode, Chain chain, ChainedHeader pruneBlock, BlockSpentTxes spentTxes)
        {
            if (!mode.HasFlag(PruningMode.TxIndex))
                return;

            var maxParallelism = Environment.ProcessorCount;

            // prepare a cache of cursors to be used by the pruning action block, allowing a pool of transactions
            var openedCursors = new ConcurrentBag<IChainStateCursor>();
            using (var cursorHandles = new DisposableCache<DisposeHandle<IChainStateCursor>>(maxParallelism,
                () =>
                {
                    // retrieve a new cursor and start its transaction, keeping track of any cursors opened
                    var cursorHandle = this.storageManager.OpenChainStateCursor();
                    cursorHandle.Item.BeginTransaction(pruneOnly: true);
                    openedCursors.Add(cursorHandle.Item);

                    return cursorHandle;
                }))
            {
                var pruneTxIndex = new ActionBlock<SpentTx>(
                    spentTx =>
                    {
                        using (var handle = cursorHandles.TakeItem())
                        {
                            var chainStateCursor = handle.Item.Item;

                            chainStateCursor.TryRemoveUnspentTx(spentTx.TxHash);
                        }
                    },
                    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = maxParallelism });

                var spentTxesQueue = new BufferBlock<SpentTx>();
                spentTxesQueue.LinkTo(pruneTxIndex, new DataflowLinkOptions { PropagateCompletion = true });

                await spentTxesQueue.SendAndCompleteAsync(spentTxes);
                await pruneTxIndex.Completion;

                // commit all opened cursors on success
                Parallel.ForEach(openedCursors, cursor =>
                    cursor.CommitTransaction());
            }
        }
        private async Task PruneBlockTxesAsync(PruningMode mode, Chain chain, ChainedHeader pruneBlock, BlockSpentTxes spentTxes)
        {
            if (!mode.HasFlag(PruningMode.BlockTxesPreserveMerkle) && !mode.HasFlag(PruningMode.BlockTxesDestroyMerkle))
                return;

            // create a source of txes to prune sources, for each block
            var pruningQueue = new BufferBlock<Tuple<int, List<int>>>();

            // prepare tx pruner, to prune a txes source for a given block
            var txPruner = new ActionBlock<Tuple<int, List<int>>>(
                blockWorkItem =>
                {
                    var blockIndex = blockWorkItem.Item1;
                    var blockHash = chain.Blocks[blockIndex].Hash;
                    var spentTxIndices = blockWorkItem.Item2;
                    var pruneWorkItem = new KeyValuePair<UInt256, IEnumerable<int>>(blockHash, spentTxIndices);

                    if (mode.HasFlag(PruningMode.BlockTxesPreserveMerkle))
                        this.storageManager.BlockTxesStorage.PruneElements(new[] { pruneWorkItem });
                    else
                        this.storageManager.BlockTxesStorage.DeleteElements(new[] { pruneWorkItem });
                },
                new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount });

            pruningQueue.LinkTo(txPruner, new DataflowLinkOptions { PropagateCompletion = true });

            // queue spent txes, grouped by block
            await pruningQueue.SendAndCompleteAsync(
                spentTxes.ReadByBlock().Select(
                    spentTxesByBlock =>
                    {
                        var blockIndex = spentTxesByBlock.Item1;
                        var txIndices = spentTxesByBlock.Item2.Select(x => x.TxIndex).ToList();

                        return Tuple.Create(blockIndex, txIndices);
                    }));

            await txPruner.Completion;
        }
Beispiel #8
0
        private async Task PruneBlock(PruningMode mode, Chain chain, ChainedHeader pruneBlock)
        {
            //TODO the replay information about blocks that have been rolled back also needs to be pruned (UnmintedTx)

            var txCount = 0;
            var totalStopwatch = Stopwatch.StartNew();
            var pruneBlockTxesStopwatch = new Stopwatch();
            var pruneTxIndexStopwatch = new Stopwatch();
            var pruneSpentTxesStopwatch = new Stopwatch();

            // retrieve the spent txes for this block
            BlockSpentTxes spentTxes;
            using (var handle = this.storageManager.OpenChainStateCursor())
            {
                var chainStateCursor = handle.Item;

                chainStateCursor.BeginTransaction(readOnly: true);
                chainStateCursor.TryGetBlockSpentTxes(pruneBlock.Height, out spentTxes);
            }

            if (spentTxes != null)
            {
                txCount = spentTxes.Count;

                pruneBlockTxesStopwatch.Start();
                pruneTxIndexStopwatch.Start();

                await Task.WhenAll(
                    // prune block txes (either merkle prune or delete)
                    PruneBlockTxesAsync(mode, chain, pruneBlock, spentTxes)
                        .ContinueWith(_ => pruneBlockTxesStopwatch.Stop()),
                    // prune tx index
                    PruneTxIndexAsync(mode, chain, pruneBlock, spentTxes)
                        .ContinueWith(_ => pruneTxIndexStopwatch.Stop())
                    );

                // remove block spent txes information
                //TODO should have a buffer on removing this, block txes pruning may need it again if flush doesn't happen
                var pruneSpentTxesTask = PruneBlockSpentTxes(mode, chain, pruneBlock);
                await pruneSpentTxesStopwatch.TimeAsync(pruneSpentTxesTask);
            }
            // if spent txes aren't available, block txes can still be deleted entirely for that pruning style
            else if (mode.HasFlag(PruningMode.BlockTxesDelete))
            {
                pruneBlockTxesStopwatch.Start();
                await PruneBlockTxesAsync(mode, chain, pruneBlock, null);
                pruneBlockTxesStopwatch.Stop();
            }
            else //if (pruneBlock.Height > 0)
            {
                //TODO can't throw an exception unless the pruned chain is persisted
                //logger.Info("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: {0:N0}".Format2(pruneBlock.Height));
                //throw new InvalidOperationException();
                txCount = 0;
            }

            // track stats
            txCountMeasure.Tick(txCount);
            txRateMeasure.Tick((float)(txCount / totalStopwatch.Elapsed.TotalSeconds));
            pruneBlockTxesDurationMeasure.Tick(pruneBlockTxesStopwatch.Elapsed);
            pruneTxIndexDurationMeasure.Tick(pruneTxIndexStopwatch.Elapsed);
            pruneSpentTxesDurationMeasure.Tick(pruneSpentTxesStopwatch.Elapsed);
            totalDurationMeasure.Tick(totalStopwatch.Elapsed);
        }