mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
Implements ethereum/go-ethereum PR #31202 and #31618. When local tracking is enabled: - EthAPIBackend.SendTx tracks transactions after pool submission and keeps tracking temporary rejects so they can be retried by the local tracker. - TxPool.AddLocal tracks accepted submissions and temporary rejects for local re-journal/re-submit flows, while preserving the original txpool error return to the caller. This avoids persisting permanently invalid transactions while preserving retry signals for transient failures without masking submission outcomes in caller workflows. Also included: - classify temporary rejection reasons in core/txpool/locals - expose SubPool.ValidateTxBasics and align LegacyPool implementation - split low-tip rejection into ErrTxGasPriceTooLow - simplify local tracker integration in txpool - update txpool and eth tests for accepted vs retryable local tracking behavior Refs: ethereum/go-ethereum#31202 Refs: ethereum/go-ethereum#31618
267 lines
11 KiB
Go
267 lines
11 KiB
Go
// Copyright 2023 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package txpool
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/XinFinOrg/XDPoSChain/common"
|
|
"github.com/XinFinOrg/XDPoSChain/core"
|
|
"github.com/XinFinOrg/XDPoSChain/core/state"
|
|
"github.com/XinFinOrg/XDPoSChain/core/types"
|
|
"github.com/XinFinOrg/XDPoSChain/log"
|
|
"github.com/XinFinOrg/XDPoSChain/params"
|
|
)
|
|
|
|
// ValidationOptions define certain differences between transaction validation
|
|
// across the different pools without having to duplicate those checks.
|
|
type ValidationOptions struct {
|
|
Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules
|
|
|
|
Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool
|
|
MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle
|
|
MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool
|
|
|
|
NotSigner func(addr common.Address) bool
|
|
}
|
|
|
|
// ValidateTransaction is a helper method to check whether a transaction is valid
|
|
// according to the consensus rules, but does not check state-dependent validation
|
|
// (balance, nonce, etc).
|
|
//
|
|
// This check is public to allow different transaction pools to check the basic
|
|
// rules without duplicating code and running the risk of missed updates.
|
|
func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types.Signer, opts *ValidationOptions) error {
|
|
// Ensure transactions not implemented by the calling pool are rejected
|
|
if opts.Accept&(1<<tx.Type()) == 0 {
|
|
return fmt.Errorf("%w: tx type %v not supported by this pool", core.ErrTxTypeNotSupported, tx.Type())
|
|
}
|
|
// Before performing any expensive validations, sanity check that the tx is
|
|
// smaller than the maximum limit the pool can meaningfully handle
|
|
if tx.Size() > opts.MaxSize {
|
|
return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize)
|
|
}
|
|
// Ensure only transactions that have been enabled are accepted
|
|
rules := opts.Config.Rules(head.Number)
|
|
if !rules.IsEIP1559 && tx.Type() != types.LegacyTxType {
|
|
return fmt.Errorf("%w: type %d rejected, pool not yet in EIP1559", core.ErrTxTypeNotSupported, tx.Type())
|
|
}
|
|
if !rules.IsPrague && tx.Type() == types.SetCodeTxType {
|
|
return fmt.Errorf("%w: type %d rejected, pool not yet in Prague", core.ErrTxTypeNotSupported, tx.Type())
|
|
}
|
|
// Check whether the init code size has been exceeded
|
|
if rules.IsEIP1559 && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
|
|
return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize)
|
|
}
|
|
// Transactions can't be negative. This may never happen using RLP decoded
|
|
// transactions but may occur for transactions created using the RPC.
|
|
if tx.Value().Sign() < 0 {
|
|
return ErrNegativeValue
|
|
}
|
|
// Ensure the transaction doesn't exceed the current block limit gas
|
|
if head.GasLimit < tx.Gas() {
|
|
return ErrGasLimit
|
|
}
|
|
// Sanity check for extremely large numbers (supported by RLP or RPC)
|
|
if tx.GasFeeCap().BitLen() > 256 {
|
|
return core.ErrFeeCapVeryHigh
|
|
}
|
|
if tx.GasTipCap().BitLen() > 256 {
|
|
return core.ErrTipVeryHigh
|
|
}
|
|
// Ensure gasFeeCap is greater than or equal to gasTipCap
|
|
if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
|
|
return core.ErrTipAboveFeeCap
|
|
}
|
|
// Make sure the transaction is signed properly
|
|
from, err := types.Sender(signer, tx)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %v", ErrInvalidSender, err)
|
|
}
|
|
// Limit nonce to 2^64-1 per EIP-2681
|
|
if tx.Nonce()+1 < tx.Nonce() {
|
|
return core.ErrNonceMax
|
|
}
|
|
// Skip further validation for special transactions
|
|
if tx.IsSpecialTransaction() {
|
|
if opts.NotSigner(from) {
|
|
return fmt.Errorf("%w: %s", ErrSpecialTxNotFromSigner, from.Hex())
|
|
}
|
|
return nil
|
|
}
|
|
// Check zero gas price.
|
|
if tx.GasPrice().Sign() == 0 {
|
|
return ErrZeroGasPrice
|
|
}
|
|
// Ensure the transaction has more gas than the bare minimum needed to
|
|
// cover the transaction metadata
|
|
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsEIP1559)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tx.Gas() < intrGas {
|
|
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
|
|
}
|
|
// Ensure the transaction can cover floor data gas.
|
|
if rules.IsPrague {
|
|
floorDataGas, err := core.FloorDataGas(tx.Data())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tx.Gas() < floorDataGas {
|
|
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
|
|
}
|
|
}
|
|
// Ensure the gas price is high enough to cover the requirement of the calling pool
|
|
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
|
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
|
}
|
|
if tx.Type() == types.SetCodeTxType {
|
|
if len(tx.SetCodeAuthorizations()) == 0 {
|
|
return fmt.Errorf("set code tx must have at least one authorization tuple")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidationOptionsWithState define certain differences between stateful transaction
|
|
// validation across the different pools without having to duplicate those checks.
|
|
type ValidationOptionsWithState struct {
|
|
State *state.StateDB // State database to check nonces and balances against
|
|
|
|
// FirstNonceGap is an optional callback to retrieve the first nonce gap in
|
|
// the list of pooled transactions of a specific account. If this method is
|
|
// set, nonce gaps will be checked and forbidden. If this method is not set,
|
|
// nonce gaps will be ignored and permitted.
|
|
FirstNonceGap func(addr common.Address) uint64
|
|
|
|
// UsedAndLeftSlots is an optional callback to retrieve the number of tx slots
|
|
// used and the number still permitted for an account. New transactions will
|
|
// be rejected once the number of remaining slots reaches zero.
|
|
UsedAndLeftSlots func(addr common.Address) (int, int)
|
|
|
|
// ExistingExpenditure is a mandatory callback to retrieve the cumulative
|
|
// cost of the already pooled transactions to check for overdrafts.
|
|
ExistingExpenditure func(addr common.Address) *big.Int
|
|
|
|
// ExistingCost is a mandatory callback to retrieve an already pooled
|
|
// transaction's cost with the given nonce to check for overdrafts.
|
|
ExistingCost func(addr common.Address, nonce uint64) *big.Int
|
|
|
|
Trc21FeeCapacity map[common.Address]*big.Int
|
|
|
|
PendingNonce func(addr common.Address) uint64
|
|
|
|
CurrentNumber func() *big.Int
|
|
}
|
|
|
|
// ValidateTransactionWithState is a helper method to check whether a transaction
|
|
// is valid according to the pool's internal state checks (balance, nonce, gaps).
|
|
//
|
|
// This check is public to allow different transaction pools to check the stateful
|
|
// rules without duplicating code and running the risk of missed updates.
|
|
func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, opts *ValidationOptionsWithState) error {
|
|
// Ensure the transaction adheres to nonce ordering
|
|
from, err := types.Sender(signer, tx) // already validated (and cached), but cleaner to check
|
|
if err != nil {
|
|
log.Error("Transaction sender recovery failed", "err", err)
|
|
return err
|
|
}
|
|
// Ensure the transaction nonce is in a valid range
|
|
next := opts.State.GetNonce(from)
|
|
if next > tx.Nonce() {
|
|
return fmt.Errorf("%w: next nonce %v, tx nonce %v", core.ErrNonceTooLow, next, tx.Nonce())
|
|
}
|
|
if opts.PendingNonce(from)+common.LimitThresholdNonceInQueue < tx.Nonce() {
|
|
return core.ErrNonceTooHigh
|
|
}
|
|
// Ensure the transaction doesn't produce a nonce gap in pools that do not
|
|
// support arbitrary orderings
|
|
if opts.FirstNonceGap != nil {
|
|
if gap := opts.FirstNonceGap(from); gap < tx.Nonce() {
|
|
return fmt.Errorf("%w: tx nonce %v, gapped nonce %v", core.ErrNonceTooHigh, tx.Nonce(), gap)
|
|
}
|
|
}
|
|
// Ensure the transactor has enough funds to cover the transaction costs
|
|
var (
|
|
balance = opts.State.GetBalance(from)
|
|
cost = tx.Cost()
|
|
feeCapacity = big.NewInt(0)
|
|
number = opts.CurrentNumber()
|
|
to = tx.To()
|
|
)
|
|
if to != nil {
|
|
if value, ok := opts.Trc21FeeCapacity[*to]; ok {
|
|
feeCapacity = value
|
|
if !opts.State.ValidateTRC21Tx(from, *to, tx.Data()) {
|
|
return core.ErrInsufficientFunds
|
|
}
|
|
cost = tx.TxCost(number)
|
|
}
|
|
}
|
|
newBalance := new(big.Int).Add(balance, feeCapacity)
|
|
if newBalance.Cmp(cost) < 0 {
|
|
return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, cost, new(big.Int).Sub(cost, balance))
|
|
}
|
|
|
|
// Ensure the transactor has enough funds to cover for replacements or nonce
|
|
// expansions without overdrafts
|
|
spent := opts.ExistingExpenditure(from)
|
|
if prev := opts.ExistingCost(from, tx.Nonce()); prev != nil {
|
|
bump := new(big.Int).Sub(cost, prev)
|
|
need := new(big.Int).Add(spent, bump)
|
|
if newBalance.Cmp(need) < 0 {
|
|
return fmt.Errorf("%w: balance %v, queued cost %v, tx bumped %v, overshot %v", core.ErrInsufficientFunds, balance, spent, bump, new(big.Int).Sub(need, newBalance))
|
|
}
|
|
} else {
|
|
need := new(big.Int).Add(spent, cost)
|
|
if newBalance.Cmp(need) < 0 {
|
|
return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, spent, cost, new(big.Int).Sub(need, newBalance))
|
|
}
|
|
// Transaction takes a new nonce value out of the pool. Ensure it doesn't
|
|
// overflow the number of permitted transactions from a single account
|
|
// (i.e. max cancellable via out-of-bound transaction).
|
|
if opts.UsedAndLeftSlots != nil {
|
|
if used, left := opts.UsedAndLeftSlots(from); left <= 0 {
|
|
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure sender and receiver are not in denylist
|
|
if number == nil || number.Cmp(new(big.Int).SetUint64(common.DenylistHFNumber)) >= 0 {
|
|
// check if sender is in denylist
|
|
if common.IsInDenylist(tx.From()) {
|
|
return fmt.Errorf("reject transaction with sender in denylist: %v", tx.From().Hex())
|
|
}
|
|
// check if receiver is in denylist
|
|
if common.IsInDenylist(to) {
|
|
return fmt.Errorf("reject transaction with receiver in denylist: %v", to.Hex())
|
|
}
|
|
}
|
|
|
|
// Validate gas price
|
|
if !tx.IsSpecialTransaction() {
|
|
minGasPrice := common.GetMinGasPrice(number)
|
|
if tx.GasPrice().Cmp(minGasPrice) < 0 {
|
|
return ErrUnderMinGasPrice
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|