/// <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); }
/// <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."); }
/// <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); }
/// <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); }
/// <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("(-)"); }