예제 #1
0
        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);
        }
예제 #2
0
        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}");
        }
예제 #3
0
        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);
        }
예제 #4
0
        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}");
        }
예제 #5
0
        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);
        }
예제 #6
0
        /// <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);
        }
예제 #7
0
        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;