diff --git a/core/txpool/custom_validator.go b/core/txpool/custom_validator.go new file mode 100644 index 0000000000..b9731621d9 --- /dev/null +++ b/core/txpool/custom_validator.go @@ -0,0 +1,42 @@ +package txpool + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type CustomValidationOptions struct { +} + +type CustomValidator interface { + Validate(tx *types.Transaction, head *types.Header, signer types.Signer, opts *CustomValidationOptions) error +} + +type CustomValidatorHypernative struct { + Config *CustomValidatorConfigHypernative +} + +type CustomValidatorConfigHypernative struct { + BannedAddresses []common.Address +} + +func NewHypernativeValidator(config *CustomValidatorConfigHypernative) CustomValidator { + return &CustomValidatorHypernative{ + Config: config, + } +} + +func (v *CustomValidatorHypernative) Validate(tx *types.Transaction, head *types.Header, signer types.Signer, opts *CustomValidationOptions) error { + // ban certain senders just for the fun of it + if address, err := signer.Sender(tx); err == nil { + for _, bannedAddress := range v.Config.BannedAddresses { + if address == bannedAddress { + return ErrCustomValidationFailed + } + } + } else { + return err + } + + return nil +} diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b40a48de6a..439fe65b6d 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -136,6 +136,7 @@ type Config struct { Lifetime time.Duration // Maximum amount of time non-executable transaction are queued CustomValidationEnabled bool + CustomValidator txpool.CustomValidator } // DefaultConfig contains the default configurations for the transaction pool. @@ -192,6 +193,9 @@ func (config *Config) sanitize() Config { log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) conf.Lifetime = DefaultConfig.Lifetime } + if config.CustomValidationEnabled && conf.CustomValidator == nil { + log.Warn("Custom transaction validator is enabled but not configured") + } return conf } @@ -613,7 +617,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro func (pool *LegacyPool) validateTxWithCustomValidator(tx *types.Transaction, local bool) error { if pool.config.CustomValidationEnabled { opts := &txpool.CustomValidationOptions{} - if err := txpool.ValidateTransactionWithCustomValidator(tx, pool.currentHead.Load(), pool.signer, opts); err != nil { + if err := pool.config.CustomValidator.Validate(tx, pool.currentHead.Load(), pool.signer, opts); err != nil { return err } } diff --git a/core/txpool/legacypool/legacypool_custom_validator_test.go b/core/txpool/legacypool/legacypool_custom_validator_test.go new file mode 100644 index 0000000000..d1abe187bf --- /dev/null +++ b/core/txpool/legacypool/legacypool_custom_validator_test.go @@ -0,0 +1,90 @@ +package legacypool + +import ( + "errors" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/event" +) + +// Max returns the larger of x or y. +func max(x, y int64) int64 { + if x < y { + return y + } + return x +} + +func min(x, y int64) int64 { + if x > y { + return y + } + return x +} + +func TestCustomTxValidationEnforced(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + blockchain := newTestBlockChain(eip1559Config, 10000000, statedb, new(event.Feed)) + + txPoolConfig := DefaultConfig + if txPoolConfig.CustomValidationEnabled { + t.Fatalf("Custom validation should be disabled by default") + } + + txPoolConfig.NoLocals = true + pool := New(txPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(txPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + key, _ := crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000)) + + tx := pricedTransaction(0, 100000, big.NewInt(3), key) + pool.SetGasTip(big.NewInt(tx.GasPrice().Int64())) + + if err := pool.Add([]*types.Transaction{tx}, true, false)[0]; err != nil { + t.Fatalf("TxValidation enforced in default config despite being disabled by default") + } + + // Modifying local config to enable custom validation + poolWithCustomValidationConfig := DefaultConfig + poolWithCustomValidationConfig.CustomValidationEnabled = true + poolWithCustomValidationConfig.NoLocals = true + + bannedKey, _ := crypto.GenerateKey() + bannedTx := pricedTransaction(0, 990000, big.NewInt(4), bannedKey) + + bannedAddresses := []common.Address{crypto.PubkeyToAddress(bannedKey.PublicKey)} + hypernativeCustomValidatorConfig := &txpool.CustomValidatorConfigHypernative{ + BannedAddresses: bannedAddresses, + } + poolWithCustomValidationConfig.CustomValidator = txpool.NewHypernativeValidator(hypernativeCustomValidatorConfig) + + poolWithCustomValidation := New(poolWithCustomValidationConfig, blockchain) + poolWithCustomValidation.Init(new(big.Int).SetUint64(poolWithCustomValidationConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer poolWithCustomValidation.Close() + + testAddBalance(pool, crypto.PubkeyToAddress(bannedKey.PublicKey), big.NewInt(1000000)) + testAddBalance(poolWithCustomValidation, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000)) + + gasTip := min(tx.GasPrice().Int64(), bannedTx.GasPrice().Int64()) + poolWithCustomValidation.SetGasTip(big.NewInt(gasTip)) + + if err := poolWithCustomValidation.Add([]*types.Transaction{tx}, true, false)[0]; err != nil { + t.Fatalf("Custom TxValidation enforced wrongly") + } + + if err := poolWithCustomValidation.Add([]*types.Transaction{bannedTx}, true, false)[0]; !errors.Is(err, txpool.ErrCustomValidationFailed) { + t.Fatalf("Custom TxValidation not enforced for banned address") + } +} diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 75e47b4d68..0366a58d61 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1536,52 +1536,6 @@ func TestMinGasPriceEnforced(t *testing.T) { } } -func TestCustomTxValidationEnforced(t *testing.T) { - t.Parallel() - - // Create the pool to test the pricing enforcement with - statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(eip1559Config, 10000000, statedb, new(event.Feed)) - - txPoolConfig := DefaultConfig - if txPoolConfig.CustomValidationEnabled { - t.Fatalf("Custom validation should be disabled by default") - } - - txPoolConfig.NoLocals = true - pool := New(txPoolConfig, blockchain) - pool.Init(new(big.Int).SetUint64(txPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) - defer pool.Close() - - key, _ := crypto.GenerateKey() - testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000)) - - tx := pricedTransaction(0, 100000, big.NewInt(3), key) - pool.SetGasTip(big.NewInt(tx.GasPrice().Int64())) - - // yo := pool.Add([]*types.Transaction{tx}, true, false)[0] - // t.Fatal(yo) - if err := pool.Add([]*types.Transaction{tx}, true, false)[0]; err != nil { - t.Fatalf("TxValidation enforced in default config despite being disabled by default") - } - - // Modifying local config to enable custom validation - poolWithCustomValidationConfig := DefaultConfig - poolWithCustomValidationConfig.CustomValidationEnabled = true - poolWithCustomValidationConfig.NoLocals = true - - poolWithCustomValidation := New(poolWithCustomValidationConfig, blockchain) - poolWithCustomValidation.Init(new(big.Int).SetUint64(poolWithCustomValidationConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) - defer poolWithCustomValidation.Close() - - testAddBalance(poolWithCustomValidation, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000)) - poolWithCustomValidation.SetGasTip(big.NewInt(tx.GasPrice().Int64())) - - if err := poolWithCustomValidation.Add([]*types.Transaction{tx}, true, false)[0]; !errors.Is(err, txpool.ErrCustomValidationFailed) { - t.Fatalf("Custom TxValidation not enforced") - } -} - // Tests that setting the transaction pool gas price to a higher value correctly // discards everything cheaper (legacy & dynamic fee) than that and moves any // gapped transactions back from the pending pool to the queue. diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 90d0fc6b3b..0df363d81d 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -40,9 +40,6 @@ type ValidationOptions struct { MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool } -type CustomValidationOptions struct { -} - // 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). @@ -248,10 +245,3 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op } return nil } - -// FIXME add proper explanation (Kirill) -func ValidateTransactionWithCustomValidator(tx *types.Transaction, head *types.Header, signer types.Signer, opts *CustomValidationOptions) error { - // Ensure transactions not implemented by the calling pool are rejected - return ErrCustomValidationFailed - // return nil -}