public ContextInformation(BlockValidationContext blockValidationContext, NBitcoin.Consensus consensus) { Guard.NotNull(blockValidationContext, nameof(blockValidationContext)); Guard.NotNull(consensus, nameof(consensus)); this.BlockValidationContext = blockValidationContext; this.Consensus = consensus; // TODO: adding flags to determine the flow of logic is not ideal // a refator is in depbate on moving to a consensus rules engine // this will remove hte need for flags as a validation will // only use the required rules (i.e if the check pow rule will be ommited form the flow) this.CheckPow = true; this.CheckMerkleRoot = true; }
public RuleContext(BlockValidationContext blockValidationContext, NBitcoin.Consensus consensus, ChainedHeader 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("(-)"); }
/// <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 null that means the puller is signalling 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> /// <param name="cancellationToken">A cancellation token that will stop the loop.</param> private async Task PullerLoopAsync(CancellationToken cancellationToken) { while (!cancellationToken.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 method will block until the next block is downloaded. blockValidationContext.Block = this.Puller.NextBlock(cancellationToken); if (blockValidationContext.Block == null) { using (await this.consensusLock.LockAsync().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; } } } this.logger.LogTrace("Block received from puller."); await this.AcceptBlockAsync(blockValidationContext).ConfigureAwait(false); } }
/// <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); // TODO: Once all code is migrated to rules this can be uncommented and the logic in this method moved to the IConsensusRules.AcceptBlockAsync() // await this.consensusRules.AcceptBlockAsync(blockValidationContext); try { await this.ValidateAndExecuteBlockAsync(blockValidationContext.RuleContext).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); bool peerShouldGetBanned = blockValidationContext.BanDurationSeconds != BlockValidationContext.BanDurationNoBan; if ((blockValidationContext.Peer != null) && peerShouldGetBanned) { int banDuration = blockValidationContext.BanDurationSeconds == BlockValidationContext.BanDurationDefaultBan ? this.connectionManager.ConnectionSettings.BanTimeSeconds : blockValidationContext.BanDurationSeconds; this.peerBanning.BanPeer(blockValidationContext.Peer, banDuration, $"Invalid block received: {blockValidationContext.Error.Message}"); } if (blockValidationContext.Error != ConsensusErrors.BadTransactionDuplicate) { // 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); }
/// <summary> /// A method that will accept a new block to the node. /// The block will be validated and the <see cref="CoinView"/> db will be updated. /// If it's a new block that was mined or staked it will extend the chain and the new block will set <see cref="ConcurrentChain.Tip"/>. /// </summary> /// <param name="blockValidationContext">Information about the block to validate.</param> public async Task AcceptBlockAsync(BlockValidationContext blockValidationContext) { this.logger.LogTrace("()"); using (await this.consensusLock.LockAsync().ConfigureAwait(false)) { try { await this.ValidateAndExecuteBlockAsync(new ContextInformation(blockValidationContext, this.Validator.ConsensusParams)).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(NodeServices.NODE_WITNESS); this.Puller.RequestOptions(TransactionOptions.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); // Since ChainHeadersBehavior check PoW, MarkBlockInvalid can't be spammed. this.logger.LogError("Marking block '{0}' as invalid.", rejectedBlockHash); this.chainState.MarkBlockInvalid(rejectedBlockHash); } 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); }