private void ValidateSeals(CancellationToken cancellation, BlockHeader[] headers) { if (_logger.IsTrace) { _logger.Trace("Starting seal validation"); } var exceptions = new ConcurrentQueue <Exception>(); Parallel.For(0, headers.Length, (i, state) => { if (cancellation.IsCancellationRequested) { if (_logger.IsTrace) { _logger.Trace("Returning fom seal validation"); } state.Stop(); return; } BlockHeader header = headers[i]; if (header == null) { return; } try { if (!_sealValidator.ValidateSeal(headers[i])) { if (_logger.IsTrace) { _logger.Trace("One of the seals is invalid"); } throw new EthSynchronizationException("Peer sent a block with an invalid seal"); } } catch (Exception e) { exceptions.Enqueue(e); state.Stop(); } }); if (_logger.IsTrace) { _logger.Trace("Seal validation complete"); } if (exceptions.Count > 0) { if (_logger.IsDebug) { _logger.Debug("Seal validation failure"); } throw new AggregateException(exceptions); } }
private void ValidateHeaders(CancellationToken cancellation, FastBlocksBatch batch) { batch.MarkValidation(); try { if (_logger.IsTrace) { _logger.Trace("Starting block validation"); } BlockHeader[] headers = batch.Headers.Response; for (int i = 0; i < headers.Length; i++) { if (cancellation.IsCancellationRequested) { if (_logger.IsTrace) { _logger.Trace("Returning fom seal validation"); } return; } BlockHeader header = headers[i]; if (header == null) { continue; } bool isHashValid = _blockValidator.ValidateHash(header); bool isSealValid = _sealValidator.ValidateSeal(header); if (!(isHashValid && isSealValid)) { if (_logger.IsTrace) { _logger.Trace("One of the blocks is invalid"); } _syncPeerPool.ReportInvalid(batch.Allocation?.Current); batch.Headers.Response = null; } } } catch (Exception ex) { if (_logger.IsError) { _logger.Error($"Error when validating headers of {batch}", ex); } batch.Headers.Response = null; } }
private void ValidateSeal(Block block, ISyncPeer syncPeer) { if (_logger.IsTrace) { _logger.Trace($"Validating seal of {block.ToString(Block.Format.Short)}) from {syncPeer:c}"); } // We hint validation range mostly to help ethash to cache epochs. // It is important that we only do that here, after we ensured that the block is // in the range of [Head - MaxReorganizationLength, Head]. // Otherwise we could hint incorrect ranges and cause expensive cache recalculations. _sealValidator.HintValidationRange(_sealValidatorUserGuid, block.Number - 128, block.Number + 1024); if (!_sealValidator.ValidateSeal(block.Header, true)) { string message = $"Peer {syncPeer?.Node:c} sent a block with an invalid seal"; if (_logger.IsDebug) { _logger.Debug($"Peer {syncPeer?.Node:c} sent a block with an invalid seal"); } throw new EthSyncException(message); } }
public bool ValidateSeal(BlockHeader header, bool force) => _sealValidator.ValidateSeal(header, force);
public void AddNewBlock(Block block, Node nodeWhoSentTheBlock) { if (block.TotalDifficulty == null) { throw new InvalidOperationException("Cannot add a block with unknown total difficulty"); } _pool.TryFind(nodeWhoSentTheBlock.Id, out PeerInfo peerInfo); if (peerInfo == null) { string errorMessage = $"Received a new block from an unknown peer {nodeWhoSentTheBlock:c} {nodeWhoSentTheBlock.Id} {_pool.PeerCount}"; if (_logger.IsDebug) { _logger.Debug(errorMessage); } return; } if ((block.TotalDifficulty ?? 0) > peerInfo.TotalDifficulty) { if (_logger.IsTrace) { _logger.Trace($"ADD NEW BLOCK Updating header of {peerInfo} from {peerInfo.HeadNumber} {peerInfo.TotalDifficulty} to {block.Number} {block.TotalDifficulty}"); } peerInfo.HeadNumber = block.Number; peerInfo.HeadHash = block.Hash; peerInfo.TotalDifficulty = block.TotalDifficulty ?? peerInfo.TotalDifficulty; } if ((block.TotalDifficulty ?? 0) < _blockTree.BestSuggestedHeader.TotalDifficulty) { return; } lock (_recentlySuggested) { if (_recentlySuggested.Get(block.Hash) != null) { return; } _recentlySuggested.Set(block.Hash, _dummyValue); } if (block.Number > _blockTree.BestKnownNumber + 8) { // ignore blocks when syncing in a simple non-locking way _synchronizer.RequestSynchronization(SyncTriggerType.NewDistantBlock); return; } if (_logger.IsTrace) { _logger.Trace($"Adding new block {block.ToString(Block.Format.Short)}) from {nodeWhoSentTheBlock:c}"); } if (!_sealValidator.ValidateSeal(block.Header)) { throw new EthSynchronizationException("Peer sent a block with an invalid seal"); } if (block.Number <= _blockTree.BestKnownNumber + 1) { if (_logger.IsInfo) { string authorString = block.Author == null ? string.Empty : "sealed by " + (KnownAddresses.GoerliValidators.ContainsKey(block.Author) ? KnownAddresses.GoerliValidators[block.Author] : block.Author?.ToString()); if (_logger.IsInfo) { _logger.Info($"Discovered a new block {string.Empty.PadLeft(9 - block.Number.ToString().Length, ' ')}{block.ToString(Block.Format.HashNumberAndTx)} {authorString}, sent by {nodeWhoSentTheBlock:s}"); } } if (_logger.IsTrace) { _logger.Trace($"{block}"); } if (_synchronizer.SyncMode == SyncMode.Full) { AddBlockResult result = _blockTree.SuggestBlock(block); if (_logger.IsTrace) { _logger.Trace($"{block.Hash} ({block.Number}) adding result is {result}"); } if (result == AddBlockResult.UnknownParent) { _synchronizer.RequestSynchronization(SyncTriggerType.Reorganization); } } } else { if (_logger.IsTrace) { _logger.Trace($"Received a block {block.Hash} ({block.Number}) from {nodeWhoSentTheBlock} - need to resync"); } _synchronizer.RequestSynchronization(SyncTriggerType.NewNearBlock); } }
public bool Validate(BlockHeader header, bool isOmmer = false) { bool areNonceValidAndMixHashValid = header.Number == 0 || header.SealEngineType == SealEngineType.None || _sealValidator.ValidateSeal(header); if (!areNonceValidAndMixHashValid) { _logger.Warn($"Invalid block header ({header.Hash}) - invalid mix hash / nonce"); } bool hashAsExpected = header.Hash == BlockHeader.CalculateHash(header); if (!hashAsExpected) { _logger.Warn($"Invalid block header ({header.Hash}) - invalid block hash"); } Block parent = _blockTree.FindBlock(header.ParentHash, false); if (parent == null) { if (header.Number == 0) { var isGenesisValid = IsGenesisHeaderValid(header);; if (!isGenesisValid) { _logger.Warn($"Invalid genesis block header ({header.Hash})"); } return(isGenesisValid); } _logger.Warn($"Orphan block, could not find parent ({header.Hash})"); return(false); } bool sealParamsCorrect = _sealValidator.ValidateParams(parent, header); if (!sealParamsCorrect) { _logger.Warn($"Invalid block header ({header.Hash}) - seal parameters incorrect"); } // difficulty check bool gasUsedBelowLimit = header.GasUsed <= header.GasLimit; if (!gasUsedBelowLimit) { _logger.Warn($"Invalid block header ({header.Hash}) - gas used above gas limit"); } bool gasLimitNotTooHigh = header.GasLimit < parent.Header.GasLimit + BigInteger.Divide(parent.Header.GasLimit, 1024); if (!gasLimitNotTooHigh) { _logger.Warn($"Invalid block header ({header.Hash}) - gas limit too high"); } bool gasLimitNotTooLow = header.GasLimit > parent.Header.GasLimit - BigInteger.Divide(parent.Header.GasLimit, 1024); if (!gasLimitNotTooLow) { _logger.Warn($"Invalid block header ({header.Hash}) - invalid mix hash / nonce"); } // bool gasLimitAboveAbsoluteMinimum = header.GasLimit >= 125000; // TODO: tests are consistently not following this rule bool timestampMoreThanAtParent = header.Timestamp > parent.Header.Timestamp; if (!timestampMoreThanAtParent) { _logger.Warn($"Invalid block header ({header.Hash}) - timestamp before parent"); } bool numberIsParentPlusOne = header.Number == parent.Header.Number + 1; if (!numberIsParentPlusOne) { _logger.Warn($"Invalid block header ({header.Hash}) - block number is not parent + 1"); } if (_logger.IsTrace) { _logger.Trace($"Validating block {header.Hash} ({header.Number}) - DAO block {_daoBlockNumber}, extraData {header.ExtraData.ToHexString(true)}"); } bool extraDataValid = isOmmer || _daoBlockNumber == null || header.Number < _daoBlockNumber || header.Number >= _daoBlockNumber + 10 || Bytes.AreEqual(header.ExtraData, DaoExtraData); if (!extraDataValid) { _logger.Warn($"Invalid block header ({header.Hash}) - DAO extra data not valid"); } return (areNonceValidAndMixHashValid && gasUsedBelowLimit && gasLimitNotTooLow && gasLimitNotTooHigh && sealParamsCorrect && // gasLimitAboveAbsoluteMinimum && // TODO: tests are consistently not following this rule timestampMoreThanAtParent && numberIsParentPlusOne && hashAsExpected && extraDataValid); }
public bool ValidateSeal(BlockHeader header, bool force) => _poSSwitcher.GetBlockConsensusInfo(header, true).IsPostMerge || _preMergeSealValidator.ValidateSeal(header, force);
/// <summary> /// Validates all the header elements (usually in relation to parent). Difficulty calculation is validated in <see cref="ISealValidator"/> /// </summary> /// <param name="header">Block header to validate</param> /// <param name="isOmmer"><value>True</value> if the <paramref name="header"/> is an ommer, otherwise <value>False</value></param> /// <returns><value>True</value> if <paramref name="header"/> is valid, otherwise <value>False</value></returns> public bool Validate(BlockHeader header, bool isOmmer = false) { // the rule here is to validate the seal first (avoid any cheap attacks on validation logic) // then validate whatever does not need to load parent from disk (the most expensive operation) bool areNonceValidAndMixHashValid = header.Number == 0 || header.SealEngineType == SealEngineType.None || _sealValidator.ValidateSeal(header); if (!areNonceValidAndMixHashValid) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - invalid mix hash / nonce"); } } bool hashAsExpected = header.Hash == BlockHeader.CalculateHash(header); if (!hashAsExpected) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - invalid block hash"); } } bool extraDataValid = isOmmer || _daoBlockNumber == null || header.Number < _daoBlockNumber || header.Number >= _daoBlockNumber + 10 || Bytes.AreEqual(header.ExtraData, DaoExtraData); if (!extraDataValid) { _logger.Warn($"Invalid block header ({header.Hash}) - DAO extra data not valid"); } BlockHeader parent = _blockTree.FindHeader(header.ParentHash, false); if (parent == null) { if (header.Number == 0) { var isGenesisValid = ValidateGenesis(header);; if (!isGenesisValid) { if (_logger.IsWarn) { _logger.Warn($"Invalid genesis block header ({header.Hash})"); } } return(isGenesisValid); } if (_logger.IsWarn) { _logger.Warn($"Orphan block, could not find parent ({header.Hash})"); } return(false); } // seal is validated when synchronizing so we can remove it from here - review and test bool sealParamsCorrect = _sealValidator.ValidateParams(parent, header); if (!sealParamsCorrect) { _logger.Warn($"Invalid block header ({header.Hash}) - seal parameters incorrect"); } bool gasUsedBelowLimit = header.GasUsed <= header.GasLimit; if (!gasUsedBelowLimit) { _logger.Warn($"Invalid block header ({header.Hash}) - gas used above gas limit"); } long maxGasLimitDifference = parent.GasLimit / 1024; bool gasLimitNotTooHigh = header.GasLimit < parent.GasLimit + maxGasLimitDifference; if (!gasLimitNotTooHigh) { _logger.Warn($"Invalid block header ({header.Hash}) - gas limit too high"); } bool gasLimitNotTooLow = header.GasLimit > parent.GasLimit - maxGasLimitDifference; if (!gasLimitNotTooLow) { _logger.Warn($"Invalid block header ({header.Hash}) - invalid mix hash / nonce"); } // bool gasLimitAboveAbsoluteMinimum = header.GasLimit >= 125000; // described in the YellowPaper but not followed bool timestampMoreThanAtParent = header.Timestamp > parent.Timestamp; if (!timestampMoreThanAtParent) { _logger.Warn($"Invalid block header ({header.Hash}) - timestamp before parent"); } bool numberIsParentPlusOne = header.Number == parent.Number + 1; if (!numberIsParentPlusOne) { _logger.Warn($"Invalid block header ({header.Hash}) - block number is not parent + 1"); } if (_logger.IsTrace) { _logger.Trace($"Validating block {header.ToString(BlockHeader.Format.Short)}, extraData {header.ExtraData.ToHexString(true)}"); } return (areNonceValidAndMixHashValid && gasUsedBelowLimit && gasLimitNotTooLow && gasLimitNotTooHigh && sealParamsCorrect && // gasLimitAboveAbsoluteMinimum && // described in the YellowPaper but not followed timestampMoreThanAtParent && numberIsParentPlusOne && hashAsExpected && extraDataValid); }