Пример #1
0
        /// <summary>
        /// Attempts to connect a block to a chain with specified tip.
        /// </summary>
        /// <param name="chainTipToExtend">Tip of the chain to extend.</param>
        /// <param name="blockToConnect">Block to connect.</param>
        /// <exception cref="ConsensusException">Thrown in case CHT is not in a consistent state.</exception>
        private async Task <ConnectBlocksResult> ConnectBlockAsync(ChainedHeader chainTipToExtend, ChainedHeaderBlock blockToConnect)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}')", nameof(chainTipToExtend), chainTipToExtend, nameof(blockToConnect), blockToConnect);

            if ((blockToConnect.ChainedHeader.BlockValidationState != ValidationState.PartiallyValidated) &&
                (blockToConnect.ChainedHeader.BlockValidationState != ValidationState.FullyValidated))
            {
                this.logger.LogError("Block '{0}' must be partially or fully validated but it is {1}.", blockToConnect, blockToConnect.ChainedHeader.BlockValidationState);
                this.logger.LogTrace("(-)[BLOCK_INVALID_STATE]");
                throw new ConsensusException("Block must be partially or fully validated.");
            }

            var validationContext = new ValidationContext()
            {
                Block = blockToConnect.Block
            };

            // Call the validation engine.
            await this.consensusRules.FullValidationAsync(validationContext, chainTipToExtend).ConfigureAwait(false);

            if (validationContext.Error != null)
            {
                List <int> badPeers;

                lock (this.peerLock)
                {
                    badPeers = this.chainedHeaderTree.PartialOrFullValidationFailed(blockToConnect.ChainedHeader);
                }

                var failureResult = new ConnectBlocksResult(false, false, badPeers, validationContext.Error.Message, validationContext.BanDurationSeconds);

                this.logger.LogTrace("(-)[FAILED]:'{0}'", failureResult);
                return(failureResult);
            }

            lock (this.peerLock)
            {
                this.chainedHeaderTree.FullValidationSucceeded(blockToConnect.ChainedHeader);
            }

            var result = new ConnectBlocksResult(true);

            this.logger.LogTrace("(-):'{0}'", result);
            return(result);
        }
Пример #2
0
        /// <summary>Reconnects the old chain.</summary>
        /// <param name="oldTip">The old tip.</param>
        /// <param name="currentTip">Current tip.</param>
        /// <param name="blocksToReconnect">List of blocks to reconnect.</param>
        private async Task <ConnectBlocksResult> ReconnectOldChainAsync(ChainedHeader oldTip, ChainedHeader currentTip, List <ChainedHeaderBlock> blocksToReconnect)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}',{4}.{5}:{6})", nameof(oldTip), oldTip, nameof(currentTip), currentTip, nameof(blocksToReconnect), nameof(blocksToReconnect.Count), blocksToReconnect.Count);

            // Connect back the old blocks.
            ConnectBlocksResult connectBlockResult = await this.ConnectChainAsync(oldTip, currentTip, blocksToReconnect).ConfigureAwait(false);

            if (connectBlockResult.Succeeded)
            {
                var result = new ConnectBlocksResult(false, false);
                this.logger.LogTrace("(-):'{0}'", result);
                return(result);
            }

            // We failed to jump back on the previous chain after a failed reorg.
            // And we failed to reconnect the old chain, database might be corrupted.
            this.logger.LogError("A critical error has prevented reconnecting blocks");
            this.logger.LogTrace("(-)[FAILED_TO_RECONNECT]");
            throw new ConsensusException("A critical error has prevented reconnecting blocks.");
        }
Пример #3
0
        /// <summary>Connects new chain.</summary>
        /// <param name="newTip">New tip.</param>
        /// <param name="currentTip">Current tip.</param>
        /// <param name="blocksToConnect">List of blocks to connect.</param>
        private async Task <ConnectBlocksResult> ConnectChainAsync(ChainedHeader newTip, ChainedHeader currentTip, List <ChainedHeaderBlock> blocksToConnect)
        {
            this.logger.LogTrace("({0}:'{1}',{2}:'{3}',{4}.{5}:{6})", nameof(newTip), newTip, nameof(currentTip), currentTip, nameof(blocksToConnect), nameof(blocksToConnect.Count), blocksToConnect.Count);

            ChainedHeader       lastValidatedBlockHeader = null;
            ConnectBlocksResult connectBlockResult       = null;

            foreach (ChainedHeaderBlock blockToConnect in blocksToConnect)
            {
                connectBlockResult = await this.ConnectBlockAsync(currentTip, blockToConnect).ConfigureAwait(false);

                if (!connectBlockResult.Succeeded)
                {
                    connectBlockResult.LastValidatedBlockHeader = lastValidatedBlockHeader;

                    this.logger.LogTrace("(-)[FAILED_TO_CONNECT]:'{0}'", connectBlockResult);
                    return(connectBlockResult);
                }

                lastValidatedBlockHeader = blockToConnect.ChainedHeader;

                // Block connected successfully.
                List <int> peersToResync = this.SetConsensusTip(newTip);

                await this.ResyncPeersAsync(peersToResync).ConfigureAwait(false);

                if (this.network.Consensus.MaxReorgLength != 0)
                {
                    int newFinalizedHeight = newTip.Height - (int)this.network.Consensus.MaxReorgLength;

                    await this.finalizedBlockHeight.SaveFinalizedBlockHeightAsync(newFinalizedHeight).ConfigureAwait(false);
                }

                // TODO: change signal to take ChainedHeaderBlock
                this.signals.SignalBlockConnected(blockToConnect.Block);
            }

            this.logger.LogTrace("(-):'{0}'", connectBlockResult);
            return(connectBlockResult);
        }
Пример #4
0
        /// <summary>Attempt to switch to new chain, which may require rewinding blocks from the current chain.</summary>
        /// <remarks>
        /// It is possible that during connection we find out that blocks that we tried to connect are invalid and we switch back to original chain.
        /// Switching that requires rewinding may fail in case rewind goes beyond fork point and the block data is not available to advance to the fork point.
        /// </remarks>
        /// <param name="proposedNewTip">Tip of the chain that will become the tip of our consensus chain if full validation will succeed.</param>
        /// <returns>Validation related information.</returns>
        private async Task <ConnectBlocksResult> FullyValidateLockedAsync(ChainedHeaderBlock proposedNewTip)
        {
            this.logger.LogTrace("({0}:'{1}')", nameof(proposedNewTip), proposedNewTip);

            ChainedHeader oldTip = this.Tip;
            ChainedHeader newTip = proposedNewTip.ChainedHeader;

            ChainedHeader fork = oldTip.FindFork(newTip);

            if (fork == newTip)
            {
                // The new header is behind the current tip this is a bug.
                this.logger.LogError("New header '{0}' is behind the current tip '{1}'.", newTip, oldTip);
                this.logger.LogTrace("(-)[INVALID_NEW_TIP]");
                throw new ConsensusException("New tip must be ahead of old tip.");
            }

            ChainedHeader currentTip = fork;

            // If the new block is not on the current chain as our current consensus tip
            // then rewind consensus tip to the common fork (or earlier because rewind might jump a few blocks back).
            bool isExtension = fork == oldTip;

            if (!isExtension)
            {
                currentTip = await this.RewindToForkPointOrBelowAsync(fork, oldTip).ConfigureAwait(false);
            }

            List <ChainedHeaderBlock> blocksToConnect = await this.TryGetBlocksToConnectAsync(newTip, currentTip.Height + 1).ConfigureAwait(false);

            if (blocksToConnect == null)
            {
                // In a situation where the rewind operation ended up behind fork point we may end up with a gap with missing blocks (if the reorg is big enough)
                // In that case we try to load the blocks from store, if store is not present we disconnect all peers.
                this.HandleMissingBlocksGap(currentTip);

                var result = new ConnectBlocksResult(false);
                this.logger.LogTrace("(-)[GAP_BEFORE_CONNECTING]:'{0}'", result);
                return(result);
            }

            ConnectBlocksResult connectBlockResult = await this.ConnectChainAsync(newTip, currentTip, blocksToConnect).ConfigureAwait(false);

            if (connectBlockResult.Succeeded)
            {
                this.logger.LogTrace("(-)[SUCCEEDED]:'{0}'", connectBlockResult);
                return(connectBlockResult);
            }

            if (connectBlockResult.LastValidatedBlockHeader != null)
            {
                // Block validation failed we need to rewind any blocks that were added to the chain.
                await this.RewindPartiallyConnectedChainAsync(connectBlockResult.LastValidatedBlockHeader, currentTip).ConfigureAwait(false);
            }

            if (isExtension)
            {
                this.logger.LogTrace("(-)[DIDNT_REWIND]:'{0}'", connectBlockResult);
                return(connectBlockResult);
            }

            List <ChainedHeaderBlock> blocksToReconnect = await this.TryGetBlocksToConnectAsync(oldTip, currentTip.Height + 1).ConfigureAwait(false);

            if (blocksToReconnect == null)
            {
                // We tried to reapply old chain but we don't have all the blocks to do that.
                this.HandleMissingBlocksGap(currentTip);

                var result = new ConnectBlocksResult(false);
                this.logger.LogTrace("(-)[GAP_AFTER_CONNECTING]:'{0}'", result);
                return(result);
            }

            ConnectBlocksResult reconnectionResult = await this.ReconnectOldChainAsync(oldTip, currentTip, blocksToReconnect).ConfigureAwait(false);

            this.logger.LogTrace("(-):'{0}'", reconnectionResult);
            return(reconnectionResult);
        }
Пример #5
0
        /// <summary>
        /// Handles a situation when partial validation of a block was successful. Informs CHT about
        /// finishing partial validation process and starting a new partial validation or full validation.
        /// </summary>
        /// <param name="chainedHeaderBlock">Block which validation was successful.</param>
        private async Task OnPartialValidationSucceededAsync(ChainedHeaderBlock chainedHeaderBlock)
        {
            this.logger.LogTrace("({0}:'{1}')", nameof(chainedHeaderBlock), chainedHeaderBlock);

            List <ChainedHeaderBlock> chainedHeaderBlocksToValidate;
            ConnectBlocksResult       connectBlocksResult = null;

            using (await this.reorgLock.LockAsync().ConfigureAwait(false))
            {
                bool fullValidationRequired;

                lock (this.peerLock)
                {
                    chainedHeaderBlocksToValidate = this.chainedHeaderTree.PartialValidationSucceeded(chainedHeaderBlock.ChainedHeader, out fullValidationRequired);
                }

                if (fullValidationRequired)
                {
                    connectBlocksResult = await this.FullyValidateLockedAsync(chainedHeaderBlock).ConfigureAwait(false);
                }
            }

            if (connectBlocksResult != null)
            {
                if (connectBlocksResult.PeersToBan != null)
                {
                    var peersToBan = new List <INetworkPeer>();

                    lock (this.peerLock)
                    {
                        foreach (int peerId in connectBlocksResult.PeersToBan)
                        {
                            if (this.peersByPeerId.TryGetValue(peerId, out INetworkPeer peer))
                            {
                                peersToBan.Add(peer);
                            }
                        }
                    }

                    this.logger.LogTrace("{0} peers will be banned.", peersToBan.Count);

                    foreach (INetworkPeer peer in peersToBan)
                    {
                        this.peerBanning.BanAndDisconnectPeer(peer.PeerEndPoint, connectBlocksResult.BanDurationSeconds, connectBlocksResult.BanReason);
                    }
                }

                if (connectBlocksResult.ConsensusTipChanged)
                {
                    await this.NotifyBehaviorsOnConsensusTipChangedAsync().ConfigureAwait(false);
                }

                lock (this.peerLock)
                {
                    this.ProcessDownloadQueueLocked();
                }
            }

            if (chainedHeaderBlocksToValidate != null)
            {
                this.logger.LogTrace("Partial validation of {0} block will be started.", chainedHeaderBlocksToValidate.Count);

                // Start validating all next blocks that come after the current block,
                // all headers in this list have the blocks present in the header.
                foreach (ChainedHeaderBlock toValidate in chainedHeaderBlocksToValidate)
                {
                    this.partialValidation.StartPartialValidation(toValidate, this.OnPartialValidationCompletedCallbackAsync);
                }
            }

            this.logger.LogTrace("(-)");
        }