/// <summary>
        /// Validates a block using the consensus rules.
        /// </summary>
        public void ValidateBlock(ContextInformation context)
        {
            this.logger.LogTrace("()");

            using (new StopwatchDisposable(o => this.Validator.PerformanceCounter.AddBlockProcessingTime(o)))
            {
                // Check that the current block has not been reorged.
                // Catching a reorg at this point will not require a rewind.
                if (context.BlockValidationContext.Block.Header.HashPrevBlock != this.Tip.HashBlock)
                {
                    this.logger.LogTrace("Reorganization detected.");
                    ConsensusErrors.InvalidPrevTip.Throw();
                }

                this.logger.LogTrace("Validating new block.");

                // Build the next block in the chain of headers. The chain header is most likely already created by
                // one of the peers so after we create a new chained block (mainly for validation)
                // we ask the chain headers for its version (also to prevent memory leaks).
                context.BlockValidationContext.ChainedBlock = new ChainedBlock(context.BlockValidationContext.Block.Header, context.BlockValidationContext.Block.Header.GetHash(), this.Tip);

                // Liberate from memory the block created above if possible.
                context.BlockValidationContext.ChainedBlock = this.Chain.GetBlock(context.BlockValidationContext.ChainedBlock.HashBlock) ?? context.BlockValidationContext.ChainedBlock;
                context.SetBestBlock(this.dateTimeProvider.GetTimeOffset());

                // == Validation flow ==

                // Check the block header is correct.
                this.Validator.CheckBlockHeader(context);
                this.Validator.ContextualCheckBlockHeader(context);

                // Calculate the consensus flags and check they are valid.
                context.Flags = this.NodeDeployments.GetFlags(context.BlockValidationContext.ChainedBlock);

                int lastCheckpointHeight = this.checkpoints.GetLastCheckpointHeight();
                if (context.BlockValidationContext.ChainedBlock.Height > lastCheckpointHeight)
                {
                    this.Validator.ContextualCheckBlock(context);

                    // Check the block itself.
                    this.Validator.CheckBlock(context);
                }
                else
                {
                    this.logger.LogTrace("Block validation partially skipped because block height {0} is not greater than last checkpointed block height {1}.", context.BlockValidationContext.ChainedBlock.Height, lastCheckpointHeight);
                }
            }

            this.logger.LogTrace("(-)[OK]");
        }
        public void AcceptBlock(ContextInformation context)
        {
            this.logger.LogTrace("()");

            using (new StopwatchDisposable(o => this.Validator.PerformanceCounter.AddBlockProcessingTime(o)))
            {
                // Check that the current block has not been reorged.
                // Catching a reorg at this point will not require a rewind.
                if (context.BlockResult.Block.Header.HashPrevBlock != this.Tip.HashBlock)
                {
                    this.logger.LogTrace("Reorganization detected.");
                    ConsensusErrors.InvalidPrevTip.Throw(); // reorg
                }

                this.logger.LogTrace("Validating new block.");

                // Build the next block in the chain of headers. The chain header is most likely already created by
                // one of the peers so after we create a new chained block (mainly for validation)
                // we ask the chain headers for its version (also to prevent memory leaks).
                context.BlockResult.ChainedBlock = new ChainedBlock(context.BlockResult.Block.Header, context.BlockResult.Block.Header.GetHash(), this.Tip);

                // Liberate from memory the block created above if possible.
                context.BlockResult.ChainedBlock = this.Chain.GetBlock(context.BlockResult.ChainedBlock.HashBlock) ?? context.BlockResult.ChainedBlock;
                context.SetBestBlock();

                // == validation flow ==

                // Check the block header is correct.
                this.Validator.CheckBlockHeader(context);
                this.Validator.ContextualCheckBlockHeader(context);

                // Calculate the consensus flags and check they are valid.
                context.Flags = this.NodeDeployments.GetFlags(context.BlockResult.ChainedBlock);
                this.Validator.ContextualCheckBlock(context);

                // check the block itself
                this.Validator.CheckBlock(context);
            }

            if (context.OnlyCheck)
            {
                this.logger.LogTrace("(-)[CHECK_ONLY]");
                return;
            }

            // Load the UTXO set of the current block. UTXO may be loaded from cache or from disk.
            // The UTXO set is stored in the context.
            this.logger.LogTrace("Loading UTXO set of the new block.");
            context.Set = new UnspentOutputSet();
            using (new StopwatchDisposable(o => this.Validator.PerformanceCounter.AddUTXOFetchingTime(o)))
            {
                uint256[]          ids   = GetIdsToFetch(context.BlockResult.Block, context.Flags.EnforceBIP30);
                FetchCoinsResponse coins = this.UTXOSet.FetchCoinsAsync(ids).GetAwaiter().GetResult();
                context.Set.SetCoins(coins.UnspentOutputs);
            }

            // Attempt to load into the cache the next set of UTXO to be validated.
            // The task is not awaited so will not stall main validation process.
            this.TryPrefetchAsync(context.Flags);

            // Validate the UTXO set is correctly spent.
            this.logger.LogTrace("Executing block.");
            using (new StopwatchDisposable(o => this.Validator.PerformanceCounter.AddBlockProcessingTime(o)))
            {
                this.Validator.ExecuteBlock(context, null);
            }

            // Persist the changes to the coinview. This will likely only be stored in memory,
            // unless the coinview treashold is reached.
            this.logger.LogTrace("Saving coinview changes.");
            this.UTXOSet.SaveChangesAsync(context.Set.GetCoins(this.UTXOSet), null, this.Tip.HashBlock, context.BlockResult.ChainedBlock.HashBlock).GetAwaiter().GetResult();

            // Set the new tip.
            this.Tip = context.BlockResult.ChainedBlock;
            this.logger.LogTrace("(-)[OK]");
        }
        public void AcceptBlock(ContextInformation context)
        {
            using (this.watch.Start(o => this.Validator.PerformanceCounter.AddBlockProcessingTime(o)))
            {
                // check that the current block has not been reorged
                // catching a reorg at this point will not require a rewind
                if (context.BlockResult.Block.Header.HashPrevBlock != this.Tip.HashBlock)
                {
                    ConsensusErrors.InvalidPrevTip.Throw();                     // reorg
                }
                // build the next block in the chain of headers
                // the chain header is most likely already created by
                // one of the peers so after we create a new chained block (mainly for validation)
                // we ask the chain headers for its version (also to prevent mempry leaks)
                context.BlockResult.ChainedBlock = new ChainedBlock(context.BlockResult.Block.Header, context.BlockResult.Block.Header.GetHash(), this.Tip);
                //Liberate from memory the block created above if possible
                context.BlockResult.ChainedBlock = this.Chain.GetBlock(context.BlockResult.ChainedBlock.HashBlock) ?? context.BlockResult.ChainedBlock;
                context.SetBestBlock();

                // == validation flow ==

                // check the block hedaer is correct
                this.Validator.CheckBlockHeader(context);
                this.Validator.ContextualCheckBlockHeader(context);

                // calculate the consensus flags
                // and check they are valid
                context.Flags = this.NodeDeployments.GetFlags(context.BlockResult.ChainedBlock);
                this.Validator.ContextualCheckBlock(context);

                // check the block itself
                this.Validator.CheckBlock(context);
            }

            if (context.OnlyCheck)
            {
                return;
            }

            // load the UTXO set of the current block
            // UTXO may be loaded form cache or from disk
            // the UTXO set are stored in the context
            context.Set = new UnspentOutputSet();
            using (this.watch.Start(o => this.Validator.PerformanceCounter.AddUTXOFetchingTime(o)))
            {
                var ids   = GetIdsToFetch(context.BlockResult.Block, context.Flags.EnforceBIP30);
                var coins = this.UTXOSet.FetchCoinsAsync(ids).GetAwaiter().GetResult();
                context.Set.SetCoins(coins);
            }

            // attempt to load in to cach the
            // next set of UTXO to be validated
            // the task is not awaited so will not
            // stall main validation process
            this.TryPrefetchAsync(context.Flags);

            // validate the UTXO set are correctly spent
            using (this.watch.Start(o => this.Validator.PerformanceCounter.AddBlockProcessingTime(o)))
            {
                this.Validator.ExecuteBlock(context, null);
            }

            // persist the changes to the coinview
            // this will likely only be sotred in mempry
            // unless the coinview trashold is reached
            this.UTXOSet.SaveChangesAsync(context.Set.GetCoins(this.UTXOSet), null, this.Tip.HashBlock, context.BlockResult.ChainedBlock.HashBlock);

            // set the new tip.
            this.Tip = context.BlockResult.ChainedBlock;
        }