public RuleContext(BlockValidationContext blockValidationContext, NBitcoin.Consensus consensus, ChainedBlock consensusTip) { Guard.NotNull(blockValidationContext, nameof(blockValidationContext)); Guard.NotNull(consensus, nameof(consensus)); this.BlockValidationContext = blockValidationContext; this.Consensus = consensus; this.ConsensusTip = consensusTip; // TODO: adding flags to determine the flow of logic is not ideal // a re-factor is in debate on moving to a consensus rules engine // this will remove the need for flags as validation will only use // the required rules (i.e if the check pow rule will be omitted form the flow) this.CheckPow = true; this.CheckMerkleRoot = true; }
/// <summary> /// A puller method that will continuously loop and ask for the next block in the chain from peers. /// The block will then be passed to the consensus validation. /// </summary> /// <remarks> /// If the <see cref="Block"/> returned from the puller is <c>null</c> that means the puller is signaling a reorg was detected. /// In this case a rewind of the <see cref="CoinView"/> db will be triggered to roll back consensus until a block is found that is in the best chain. /// </remarks> private async Task PullerLoopAsync() { this.logger.LogTrace("()"); while (!this.nodeLifetime.ApplicationStopping.IsCancellationRequested) { BlockValidationContext blockValidationContext = new BlockValidationContext(); using (new StopwatchDisposable(o => this.Validator.PerformanceCounter.AddBlockFetchingTime(o))) { // Save the current consensus tip to later check if it changed. ChainedBlock consensusTip = this.Tip; this.logger.LogTrace("Asking block puller to deliver next block."); // This method will block until the next block is downloaded. LookaheadResult lookaheadResult = this.Puller.NextBlock(this.nodeLifetime.ApplicationStopping); if (lookaheadResult.Block == null) { using (await this.consensusLock.LockAsync(this.nodeLifetime.ApplicationStopping).ConfigureAwait(false)) { this.logger.LogTrace("No block received from puller due to reorganization."); if (!consensusTip.Equals(this.Tip)) { this.logger.LogTrace("Consensus tip changed from '{0}' to '{1}', no rewinding.", consensusTip, this.Tip); continue; } this.logger.LogTrace("Rewinding."); await this.RewindCoinViewLockedAsync().ConfigureAwait(false); continue; } } blockValidationContext.Block = lookaheadResult.Block; blockValidationContext.Peer = lookaheadResult.Peer; } this.logger.LogTrace("Block received from puller."); await this.AcceptBlockAsync(blockValidationContext).ConfigureAwait(false); } this.logger.LogTrace("(-)"); }
/// <inheritdoc/> public async Task AcceptBlockAsync(BlockValidationContext blockValidationContext) { this.logger.LogTrace("()"); using (await this.consensusLock.LockAsync(this.nodeLifetime.ApplicationStopping).ConfigureAwait(false)) { blockValidationContext.RuleContext = new RuleContext(blockValidationContext, this.Validator.ConsensusParams, this.Tip); await this.consensusRules.ExecuteAsync(blockValidationContext); if (blockValidationContext.Error == null) { try { await this.ValidateAndExecuteBlockAsync(blockValidationContext.RuleContext, true).ConfigureAwait(false); } catch (ConsensusErrorException ex) { blockValidationContext.Error = ex.ConsensusError; } } if (blockValidationContext.Error != null) { uint256 rejectedBlockHash = blockValidationContext.Block.GetHash(); this.logger.LogError("Block '{0}' rejected: {1}", rejectedBlockHash, blockValidationContext.Error.Message); // Check if the error is a consensus failure. if (blockValidationContext.Error == ConsensusErrors.InvalidPrevTip) { if (!this.Chain.Contains(this.Tip.HashBlock)) { // Our consensus tip is not on the best chain, which means that the current block // we are processing might be rejected only because of that. The consensus is on wrong chain // and need to be reset. await this.RewindCoinViewLockedAsync().ConfigureAwait(false); } this.logger.LogTrace("(-)[INVALID_PREV_TIP]"); return; } // Pull again. this.Puller.SetLocation(this.Tip); if (blockValidationContext.Error == ConsensusErrors.BadWitnessNonceSize) { this.logger.LogInformation("You probably need witness information, activating witness requirement for peers."); this.connectionManager.AddDiscoveredNodesRequirement(NetworkPeerServices.NODE_WITNESS); this.Puller.RequestOptions(NetworkOptions.Witness); this.logger.LogTrace("(-)[BAD_WITNESS_NONCE_SIZE]"); return; } // Set the chain back to ConsensusLoop.Tip. this.Chain.SetTip(this.Tip); this.logger.LogTrace("Chain reverted back to block '{0}'.", this.Tip); if ((blockValidationContext.Peer != null) && (blockValidationContext.BanDurationSeconds != BlockValidationContext.BanDurationNoBan)) { int banDuration = blockValidationContext.BanDurationSeconds == BlockValidationContext.BanDurationDefaultBan ? this.connectionManager.ConnectionSettings.BanTimeSeconds : blockValidationContext.BanDurationSeconds; this.peerBanning.BanPeer(blockValidationContext.Peer, banDuration, $"Invalid block received: {blockValidationContext.Error.Message}"); } // Since ChainHeadersBehavior check PoW, MarkBlockInvalid can't be spammed. this.logger.LogError("Marking block '{0}' as invalid{1}.", rejectedBlockHash, blockValidationContext.RejectUntil != null ? string.Format(" until {0:yyyy-MM-dd HH:mm:ss}", blockValidationContext.RejectUntil.Value) : ""); this.chainState.MarkBlockInvalid(rejectedBlockHash, blockValidationContext.RejectUntil); } else { this.logger.LogTrace("Block '{0}' accepted.", this.Tip); this.chainState.ConsensusTip = this.Tip; // We really want to flush if we are at the top of the chain. // Otherwise, we just allow the flush to happen if it is needed. bool forceFlush = this.Chain.Tip.HashBlock == blockValidationContext.ChainedBlock?.HashBlock; await this.FlushAsync(forceFlush).ConfigureAwait(false); if (this.Tip.ChainWork > this.Chain.Tip.ChainWork) { // This is a newly mined block. this.Chain.SetTip(this.Tip); this.Puller.SetLocation(this.Tip); this.logger.LogDebug("Block extends best chain tip to '{0}'.", this.Tip); } this.signals.SignalBlock(blockValidationContext.Block); } } this.logger.LogTrace("(-):*.{0}='{1}',*.{2}='{3}'", nameof(blockValidationContext.ChainedBlock), blockValidationContext.ChainedBlock, nameof(blockValidationContext.Error), blockValidationContext.Error?.Message); }