From 65e11e1ee1658b70c3b14bd98a0d72bcf1bea0de Mon Sep 17 00:00:00 2001 From: Kirill Maksimov Date: Thu, 21 Dec 2023 15:02:54 +0200 Subject: [PATCH] Custom tx validator logic WIP --- core/txpool/errors.go | 3 ++ core/txpool/legacypool/legacypool.go | 23 ++++++++++++ core/txpool/legacypool/legacypool_test.go | 46 +++++++++++++++++++++++ core/txpool/validation.go | 10 +++++ 4 files changed, 82 insertions(+) diff --git a/core/txpool/errors.go b/core/txpool/errors.go index 61daa999ff..fc048aef45 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -54,4 +54,7 @@ var ( // ErrFutureReplacePending is returned if a future transaction replaces a pending // one. Future transactions should only be able to replace other future transactions. ErrFutureReplacePending = errors.New("future transaction tries to replace pending") + + // FIXME add proper explanation (Kirill) + ErrCustomValidationFailed = errors.New("tranaction failed custom validation") ) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 959e328b9c..b40a48de6a 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -134,6 +134,8 @@ type Config struct { GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts Lifetime time.Duration // Maximum amount of time non-executable transaction are queued + + CustomValidationEnabled bool } // DefaultConfig contains the default configurations for the transaction pool. @@ -150,6 +152,8 @@ var DefaultConfig = Config{ GlobalQueue: 1024, Lifetime: 3 * time.Hour, + + CustomValidationEnabled: false, } // sanitize checks the provided user configurations and changes anything that's @@ -605,6 +609,17 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro return nil } +// FIXME add proper explanation (Kirill) +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 { + return err + } + } + return nil +} + // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { @@ -981,6 +996,14 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error invalidTxMeter.Mark(1) continue } + + // FIXME kirill + if err := pool.validateTxWithCustomValidator(tx, local); err != nil { + errs[i] = err + log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err) + invalidTxMeter.Mark(1) + continue + } // Accumulate all unknown transactions for deeper processing news = append(news, tx) } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index 0366a58d61..75e47b4d68 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1536,6 +1536,52 @@ 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 0df363d81d..90d0fc6b3b 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -40,6 +40,9 @@ 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). @@ -245,3 +248,10 @@ 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 +}