mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +00:00
core/txpool: validate stateful options
This commit is contained in:
parent
a7d09cc14f
commit
87164a8553
3 changed files with 101 additions and 0 deletions
|
|
@ -28,6 +28,10 @@ var (
|
|||
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
||||
ErrInvalidSender = errors.New("invalid sender")
|
||||
|
||||
// ErrInvalidValidationOptions is returned if a public validation helper is
|
||||
// invoked without the required state or callbacks.
|
||||
ErrInvalidValidationOptions = errors.New("invalid validation options")
|
||||
|
||||
// ErrUnderpriced is returned if a transaction's gas price is too low to be
|
||||
// included in the pool. If the gas price is lower than the minimum configured
|
||||
// one for the transaction pool, use ErrTxGasPriceTooLow instead.
|
||||
|
|
|
|||
|
|
@ -246,12 +246,30 @@ type ValidationOptionsWithState struct {
|
|||
ExistingCost func(addr common.Address, nonce uint64) *big.Int
|
||||
}
|
||||
|
||||
func validateStatefulOptions(opts *ValidationOptionsWithState) error {
|
||||
switch {
|
||||
case opts == nil:
|
||||
return fmt.Errorf("%w: missing options", ErrInvalidValidationOptions)
|
||||
case opts.State == nil:
|
||||
return fmt.Errorf("%w: missing state", ErrInvalidValidationOptions)
|
||||
case opts.ExistingExpenditure == nil:
|
||||
return fmt.Errorf("%w: missing ExistingExpenditure callback", ErrInvalidValidationOptions)
|
||||
case opts.ExistingCost == nil:
|
||||
return fmt.Errorf("%w: missing ExistingCost callback", ErrInvalidValidationOptions)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if err := validateStatefulOptions(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
// Ensure the transaction adheres to nonce ordering
|
||||
from, err := types.Sender(signer, tx) // already validated (and cached), but cleaner to check
|
||||
if err != nil {
|
||||
|
|
@ -280,6 +298,9 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
|
|||
// Ensure the transactor has enough funds to cover for replacements or nonce
|
||||
// expansions without overdrafts
|
||||
spent := opts.ExistingExpenditure(from)
|
||||
if spent == nil {
|
||||
return fmt.Errorf("%w: ExistingExpenditure returned nil", ErrInvalidValidationOptions)
|
||||
}
|
||||
if prev := opts.ExistingCost(from, tx.Nonce()); prev != nil {
|
||||
bump := new(big.Int).Sub(cost, prev)
|
||||
need := new(big.Int).Add(spent, bump)
|
||||
|
|
|
|||
|
|
@ -21,13 +21,17 @@ import (
|
|||
"errors"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func TestValidateTransactionEIP2681(t *testing.T) {
|
||||
|
|
@ -96,6 +100,78 @@ func TestValidateTransactionEIP2681(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestValidateTransactionWithStateRejectsInvalidOptions(t *testing.T) {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tx := createTestTransaction(key, 0)
|
||||
from := crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
||||
statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
statedb.SetBalance(from, new(uint256.Int).SetUint64(params.Ether), tracing.BalanceChangeUnspecified)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *ValidationOptionsWithState
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "missing options",
|
||||
opts: nil,
|
||||
wantErr: "missing options",
|
||||
},
|
||||
{
|
||||
name: "missing state",
|
||||
opts: &ValidationOptionsWithState{
|
||||
ExistingExpenditure: func(common.Address) *big.Int { return new(big.Int) },
|
||||
ExistingCost: func(common.Address, uint64) *big.Int { return nil },
|
||||
},
|
||||
wantErr: "missing state",
|
||||
},
|
||||
{
|
||||
name: "missing existing expenditure callback",
|
||||
opts: &ValidationOptionsWithState{
|
||||
State: statedb,
|
||||
ExistingCost: func(common.Address, uint64) *big.Int { return nil },
|
||||
},
|
||||
wantErr: "missing ExistingExpenditure callback",
|
||||
},
|
||||
{
|
||||
name: "missing existing cost callback",
|
||||
opts: &ValidationOptionsWithState{
|
||||
State: statedb,
|
||||
ExistingExpenditure: func(common.Address) *big.Int { return new(big.Int) },
|
||||
},
|
||||
wantErr: "missing ExistingCost callback",
|
||||
},
|
||||
{
|
||||
name: "nil expenditure result",
|
||||
opts: &ValidationOptionsWithState{
|
||||
State: statedb,
|
||||
ExistingExpenditure: func(common.Address) *big.Int { return nil },
|
||||
ExistingCost: func(common.Address, uint64) *big.Int { return nil },
|
||||
},
|
||||
wantErr: "ExistingExpenditure returned nil",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateTransactionWithState(tx, types.HomesteadSigner{}, tt.opts)
|
||||
if !errors.Is(err, ErrInvalidValidationOptions) {
|
||||
t.Fatalf("expected ErrInvalidValidationOptions, got %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantErr) {
|
||||
t.Fatalf("expected error containing %q, got %v", tt.wantErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// createTestTransaction creates a basic transaction for testing
|
||||
func createTestTransaction(key *ecdsa.PrivateKey, nonce uint64) *types.Transaction {
|
||||
to := common.HexToAddress("0x0000000000000000000000000000000000000001")
|
||||
|
|
|
|||
Loading…
Reference in a new issue