public void Eip_1559_CalculateBaseFee_should_returns_zero_when_eip1559_not_enabled() { IReleaseSpec releaseSpec = Substitute.For <IReleaseSpec>(); releaseSpec.IsEip1559Enabled.Returns(false); BlockHeader blockHeader = Build.A.BlockHeader.TestObject; blockHeader.Number = 2001; blockHeader.GasLimit = 100; UInt256 baseFee = BlockHeader.CalculateBaseFee(blockHeader, releaseSpec); Assert.AreEqual(UInt256.Zero, baseFee); }
public (bool Allowed, string Reason) IsAllowed(Transaction tx, BlockHeader parentHeader) { long blockNumber = parentHeader.Number + 1; IReleaseSpec releaseSpec = _specProvider.GetSpec(blockNumber); UInt256 baseFee = BlockHeader.CalculateBaseFee(parentHeader, releaseSpec); bool isEip1559Enabled = releaseSpec.IsEip1559Enabled; bool skipCheck = tx.IsServiceTransaction || !isEip1559Enabled; bool allowed = skipCheck || tx.FeeCap >= baseFee; return(allowed, allowed ? string.Empty : $"FeeCap too low. FeeCap: {tx.FeeCap}, BaseFee: {baseFee}, GasPremium:{tx.GasPremium}, Block number: {blockNumber}"); }
public void Eip_1559_CalculateBaseFee(long gasTarget, long baseFee, long expectedBaseFee, long gasUsed) { IReleaseSpec releaseSpec = Substitute.For <IReleaseSpec>(); releaseSpec.IsEip1559Enabled.Returns(true); BlockHeader blockHeader = Build.A.BlockHeader.TestObject; blockHeader.Number = 2001; blockHeader.GasLimit = gasTarget; blockHeader.BaseFee = (UInt256)baseFee; blockHeader.GasUsed = gasUsed; UInt256 actualBaseFee = BlockHeader.CalculateBaseFee(blockHeader, releaseSpec); Assert.AreEqual((UInt256)expectedBaseFee, actualBaseFee); }
public (bool Allowed, string Reason) IsAllowed(Transaction tx, BlockHeader?parentHeader, UInt256 minGasPriceFloor) { UInt256 gasPrice = tx.GasPrice; long blockNumber = (parentHeader?.Number ?? 0) + 1; IReleaseSpec spec = _specProvider.GetSpec(blockNumber); if (spec.IsEip1559Enabled && tx.IsEip1559) { UInt256 baseFee = BlockHeader.CalculateBaseFee(parentHeader, spec); gasPrice = tx.CalculateEffectiveGasPrice(true, baseFee); } bool allowed = gasPrice >= minGasPriceFloor; return(allowed, allowed ? string.Empty : $"gas price too low {gasPrice} < {minGasPriceFloor}"); }
private Block?PrepareBlock(Block parentBlock) { BlockHeader parentHeader = parentBlock.Header; if (parentHeader == null) { if (_logger.IsError) { _logger.Error( $"Preparing new block on top of {parentBlock.ToString(Block.Format.Short)} - parent header is null"); } return(null); } if (_recentNotAllowedParent == parentBlock.Hash) { return(null); } if (!_sealer.CanSeal(parentHeader.Number + 1, parentHeader.Hash)) { if (_logger.IsTrace) { _logger.Trace($"Not allowed to sign block ({parentBlock.Number + 1})"); } _recentNotAllowedParent = parentHeader.Hash; return(null); } if (_logger.IsInfo) { _logger.Info($"Preparing new block on top of {parentBlock.ToString(Block.Format.Short)}"); } UInt256 timestamp = _timestamper.UnixTime.Seconds; BlockHeader header = new BlockHeader( parentBlock.Hash, Keccak.OfAnEmptySequenceRlp, Address.Zero, 1, parentBlock.Number + 1, _gasLimitCalculator.GetGasLimit(parentBlock.Header), timestamp > parentBlock.Timestamp ? timestamp : parentBlock.Timestamp + 1, Array.Empty <byte>()); // If the block isn't a checkpoint, cast a random vote (good enough for now) long number = header.Number; // Assemble the voting snapshot to check which votes make sense Snapshot snapshot = _snapshotManager.GetOrCreateSnapshot(number - 1, header.ParentHash); bool isEpochBlock = (ulong)number % 30000 == 0; if (!isEpochBlock && _proposals.Any()) { // Gather all the proposals that make sense voting on List <Address> addresses = new List <Address>(); foreach (var proposal in _proposals) { Address address = proposal.Key; bool authorize = proposal.Value; if (_snapshotManager.IsValidVote(snapshot, address, authorize)) { addresses.Add(address); } } // If there's pending proposals, cast a vote on them if (addresses.Count > 0) { header.Beneficiary = addresses[_cryptoRandom.NextInt(addresses.Count)]; if (_proposals.TryGetValue(header.Beneficiary !, out bool proposal)) { header.Nonce = proposal ? Clique.NonceAuthVote : Clique.NonceDropVote; } } } // Set the correct difficulty header.BaseFee = BlockHeader.CalculateBaseFee(parentHeader, _specProvider.GetSpec(header.Number)); header.Difficulty = CalculateDifficulty(snapshot, _sealer.Address); header.TotalDifficulty = parentBlock.TotalDifficulty + header.Difficulty; if (_logger.IsDebug) { _logger.Debug($"Setting total difficulty to {parentBlock.TotalDifficulty} + {header.Difficulty}."); } // Set extra data int mainBytesLength = Clique.ExtraVanityLength + Clique.ExtraSealLength; int signerBytesLength = isEpochBlock ? 20 * snapshot.Signers.Count : 0; int extraDataLength = mainBytesLength + signerBytesLength; header.ExtraData = new byte[extraDataLength]; byte[] clientName = Encoding.UTF8.GetBytes("Nethermind " + ClientVersion.Version); Array.Copy(clientName, header.ExtraData, clientName.Length); if (isEpochBlock) { for (int i = 0; i < snapshot.Signers.Keys.Count; i++) { Address signer = snapshot.Signers.Keys[i]; int index = Clique.ExtraVanityLength + 20 * i; Array.Copy(signer.Bytes, 0, header.ExtraData, index, signer.Bytes.Length); } } // Mix digest is reserved for now, set to empty header.MixHash = Keccak.Zero; // Ensure the timestamp has the correct delay header.Timestamp = parentBlock.Timestamp + _config.BlockPeriod; if (header.Timestamp < _timestamper.UnixTime.Seconds) { header.Timestamp = new UInt256(_timestamper.UnixTime.Seconds); } _stateProvider.StateRoot = parentHeader.StateRoot; var selectedTxs = _txSource.GetTransactions(parentBlock.Header, header.GasLimit); Block block = new Block(header, selectedTxs, Array.Empty <BlockHeader>()); header.TxRoot = new TxTrie(block.Transactions).RootHash; block.Header.Author = _sealer.Address; return(block); }
/// <summary> /// Note that this does not validate seal which is the responsibility of <see cref="ISealValidator"/>> /// </summary> /// <param name="header">BlockHeader to validate</param> /// <param name="parent">BlockHeader which is the parent of <paramref name="header"/></param> /// <param name="isOmmer"><value>True</value> if uncle block, otherwise <value>False</value></param> /// <returns></returns> public bool Validate(BlockHeader header, BlockHeader parent, bool isOmmer = false) { bool hashAsExpected = ValidateHash(header); IReleaseSpec spec = _specProvider.GetSpec(header.Number); bool extraDataValid = header.ExtraData.Length <= spec.MaximumExtraDataSize && (isOmmer || _daoBlockNumber == null || header.Number < _daoBlockNumber || header.Number >= _daoBlockNumber + 10 || Bytes.AreEqual(header.ExtraData, DaoExtraData)); if (!extraDataValid) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - DAO extra data not valid"); } } if (parent == null) { if (header.Number == 0) { bool isGenesisValid = ValidateGenesis(header); if (!isGenesisValid) { if (_logger.IsWarn) { _logger.Warn($"Invalid genesis block header ({header.Hash})"); } } return(isGenesisValid); } if (_logger.IsDebug) { _logger.Debug($"Orphan block, could not find parent ({header.ParentHash}) of ({header.Hash})"); } return(false); } bool totalDifficultyCorrect = true; if (header.TotalDifficulty != null) { if (parent.TotalDifficulty + header.Difficulty != header.TotalDifficulty) { if (_logger.IsDebug) { _logger.Debug($"Invalid total difficulty"); } totalDifficultyCorrect = false; } } // seal is validated when synchronizing so we can remove it from here - review and test bool sealParamsCorrect = _sealValidator.ValidateParams(parent, header); if (!sealParamsCorrect) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - seal parameters incorrect"); } } bool gasUsedBelowLimit = header.GasUsed <= header.GetGasTarget1559(spec) * 2 + header.GetGasTargetLegacy(spec); if (!gasUsedBelowLimit) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - gas used above gas limit"); } } var gasLimitInRange = ValidateGasLimitRange(header, parent, spec); // bool gasLimitAboveAbsoluteMinimum = header.GasLimit >= 125000; // described in the YellowPaper but not followed bool timestampMoreThanAtParent = header.Timestamp > parent.Timestamp; if (!timestampMoreThanAtParent) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - timestamp before parent"); } } bool numberIsParentPlusOne = header.Number == parent.Number + 1; if (!numberIsParentPlusOne) { if (_logger.IsWarn) { _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)}"); } bool baseFeeIsCorrect = true; if (spec.IsEip1559Enabled) { UInt256?expectedBaseFee = BlockHeader.CalculateBaseFee(parent, spec); baseFeeIsCorrect = expectedBaseFee == header.BaseFee; } return (totalDifficultyCorrect && gasUsedBelowLimit && gasLimitInRange && sealParamsCorrect && // gasLimitAboveAbsoluteMinimum && // described in the YellowPaper but not followed timestampMoreThanAtParent && numberIsParentPlusOne && hashAsExpected && extraDataValid && baseFeeIsCorrect); }
public IEnumerable <Transaction> GetTransactions(BlockHeader parent, long gasLimit) { T GetFromState <T>(Func <Keccak, Address, T> stateGetter, Address address, T defaultValue) { T value = defaultValue; try { value = stateGetter(parent.StateRoot, address); } catch (TrieException e) { if (_logger.IsDebug) { _logger.Debug($"Couldn't get state for address {address}.{Environment.NewLine}{e}"); } } catch (RlpException e) { if (_logger.IsError) { _logger.Error($"Couldn't deserialize state for address {address}.", e); } } return(value); } UInt256 GetCurrentNonce(IDictionary <Address, UInt256> noncesDictionary, Address address) { if (!noncesDictionary.TryGetValue(address, out UInt256 nonce)) { noncesDictionary[address] = nonce = GetFromState(_stateReader.GetNonce, address, UInt256.Zero); } return(nonce); } UInt256 GetRemainingBalance(IDictionary <Address, UInt256> balances, Address address) { if (!balances.TryGetValue(address, out UInt256 balance)) { balances[address] = balance = GetFromState(_stateReader.GetBalance, address, UInt256.Zero); } return(balance); } bool HasEnoughFounds(IDictionary <Address, UInt256> balances, Transaction transaction, bool isEip1559Enabled, UInt256 baseFee) { UInt256 balance = GetRemainingBalance(balances, transaction.SenderAddress !); UInt256 transactionPotentialCost = transaction.CalculateTransactionPotentialCost(isEip1559Enabled, baseFee); if (balance < transactionPotentialCost) { if (_logger.IsDebug) { _logger.Debug($"Rejecting transaction - transaction cost ({transactionPotentialCost}) is higher than sender balance ({balance})."); } return(false); } balances[transaction.SenderAddress] = balance - transactionPotentialCost; return(true); } long blockNumber = parent.Number + 1; IReleaseSpec releaseSpec = _specProvider.GetSpec(blockNumber); bool isEip1559Enabled = releaseSpec.IsEip1559Enabled; UInt256 baseFee = BlockHeader.CalculateBaseFee(parent, releaseSpec); IDictionary <Address, Transaction[]> pendingTransactions = _transactionPool.GetPendingTransactionsBySender(); IComparer <Transaction> comparer = GetComparer(parent, new BlockPreparationContext(baseFee, blockNumber)) .ThenBy(DistinctCompareTx.Instance); // in order to sort properly and not loose transactions we need to differentiate on their identity which provided comparer might not be doing IEnumerable <Transaction> transactions = GetOrderedTransactions(pendingTransactions, comparer); IDictionary <Address, UInt256> remainingBalance = new Dictionary <Address, UInt256>(); Dictionary <Address, UInt256> nonces = new(); List <Transaction> selected = new(); long gasRemaining = gasLimit; if (_logger.IsDebug) { _logger.Debug($"Collecting pending transactions at block gas limit {gasRemaining}."); } foreach (Transaction tx in transactions) { if (gasRemaining < Transaction.BaseTxGasCost) { break; } if (tx.GasLimit > gasRemaining) { if (_logger.IsDebug) { _logger.Debug($"Rejecting (tx gas limit {tx.GasLimit} above remaining block gas {gasRemaining}) {tx.ToShortString()}"); } continue; } if (tx.SenderAddress == null) { _transactionPool.RemoveTransaction(tx.Hash !); if (_logger.IsDebug) { _logger.Debug($"Rejecting (null sender) {tx.ToShortString()}"); } continue; } bool success = _txFilterPipeline.Execute(tx, parent); if (!success) { _transactionPool.RemoveTransaction(tx.Hash !); continue; } UInt256 expectedNonce = GetCurrentNonce(nonces, tx.SenderAddress); if (expectedNonce != tx.Nonce) { if (tx.Nonce < expectedNonce) { _transactionPool.RemoveTransaction(tx.Hash !, true); } if (tx.Nonce > expectedNonce + _transactionPool.FutureNonceRetention) { _transactionPool.RemoveTransaction(tx.Hash !); } if (_logger.IsDebug) { _logger.Debug($"Rejecting (invalid nonce - expected {expectedNonce}) {tx.ToShortString()}"); } continue; } if (!HasEnoughFounds(remainingBalance, tx, isEip1559Enabled, baseFee)) { _transactionPool.RemoveTransaction(tx.Hash !); if (_logger.IsDebug) { _logger.Debug($"Rejecting (sender balance too low) {tx.ToShortString()}"); } continue; } selected.Add(tx); if (_logger.IsTrace) { _logger.Trace($"Selected {tx.ToShortString()} to be included in block."); } nonces[tx.SenderAddress !] = tx.Nonce + 1;