diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 51b8b67c61..7ad95612bf 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1085,19 +1085,24 @@ func (p *BlobPool) SetGasTip(tip *big.Int) { p.updateStorageMetrics() } -// 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 (p *BlobPool) validateTx(tx *types.Transaction) error { - // Ensure the transaction adheres to basic pool filters (type, size, tip) and - // consensus rules - baseOpts := &txpool.ValidationOptions{ +// ValidateTxBasics checks whether a transaction is valid according to the consensus +// rules, but does not check state-dependent validation such as sufficient balance. +// This check is meant as an early check which only needs to be performed once, +// and does not require the pool mutex to be held. +func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error { + opts := &txpool.ValidationOptions{ Config: p.chain.Config(), Accept: 1 << types.BlobTxType, MaxSize: txMaxSize, MinTip: p.gasTip.ToBig(), } + return txpool.ValidateTransaction(tx, p.head, p.signer, opts) +} - if err := p.txValidationFn(tx, p.head, p.signer, baseOpts); err != 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 (p *BlobPool) validateTx(tx *types.Transaction) error { + if err := p.ValidateTxBasics(tx); err != nil { return err } // Ensure the transaction adheres to the stateful pool filters (nonce, balance) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 4250283b03..a5ff5f8e21 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -558,11 +558,11 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address] return pending } -// validateTxBasics checks whether a transaction is valid according to the consensus +// ValidateTxBasics checks whether a transaction is valid according to the consensus // rules, but does not check state-dependent validation such as sufficient balance. // This check is meant as an early check which only needs to be performed once, // and does not require the pool mutex to be held. -func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error { +func (pool *LegacyPool) ValidateTxBasics(tx *types.Transaction) error { opts := &txpool.ValidationOptions{ Config: pool.chainconfig, Accept: 0 | @@ -573,10 +573,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error { MaxSize: txMaxSize, MinTip: pool.gasTip.Load().ToBig(), } - if err := txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts); err != nil { - return err - } - return nil + return txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts) } // validateTx checks whether a transaction is valid according to the consensus @@ -929,7 +926,7 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error { // Exclude transactions with basic errors, e.g invalid signatures and // insufficient intrinsic gas as soon as possible and cache senders // in transactions before obtaining lock - if err := pool.validateTxBasics(tx); err != nil { + if err := pool.ValidateTxBasics(tx); err != nil { errs[i] = err log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err) invalidTxMeter.Mark(1) diff --git a/core/txpool/locals/tx_tracker.go b/core/txpool/locals/tx_tracker.go index a24fcb1f4e..f46cb38f8a 100644 --- a/core/txpool/locals/tx_tracker.go +++ b/core/txpool/locals/tx_tracker.go @@ -88,6 +88,11 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) { if tx.Type() == types.BlobTxType { continue } + // Ignore the transactions which are failed for fundamental + // validation such as invalid parameters. + if tracker.pool.ValidateTxBasics(tx) != nil { + continue + } // If we're already tracking it, it's a no-op if _, ok := tracker.all[tx.Hash()]; ok { continue diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 5ad0f5b0e0..242af02136 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -129,6 +129,12 @@ type SubPool interface { // retrieve blobs from the pools directly instead of the network. GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) + // ValidateTxBasics checks whether a transaction is valid according to the consensus + // rules, but does not check state-dependent validation such as sufficient balance. + // This check is meant as a static check which can be performed without holding the + // pool mutex. + ValidateTxBasics(tx *types.Transaction) error + // Add enqueues a batch of transactions into the pool if they are valid. Due // to the large transaction churn, add may postpone fully integrating the tx // to a later point to batch multiple ones together. diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index 0ebf4c7e4b..55812415f9 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -325,6 +325,17 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr return nil, nil } +// ValidateTxBasics checks whether a transaction is valid according to the consensus +// rules, but does not check state-dependent validation such as sufficient balance. +func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error { + for _, subpool := range p.subpools { + if subpool.Filter(tx) { + return subpool.ValidateTxBasics(tx) + } + } + return fmt.Errorf("%w: received type %d", core.ErrTxTypeNotSupported, tx.Type()) +} + // Add enqueues a batch of transactions into the pool if they are valid. Due // to the large transaction churn, add may postpone fully integrating the tx // to a later point to batch multiple ones together. diff --git a/eth/api_backend.go b/eth/api_backend.go index 7cfdeec778..3aa90f0a12 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -272,13 +272,15 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri } func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - if err := b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]; err != nil { - return err - } if locals := b.eth.localTxTracker; locals != nil { locals.Track(signedTx) } - return nil + // TODO(rjl493456442): If a transaction fails stateful validation (e.g., no + // available slot), an error will be returned to the user. However, locally + // submitted transactions may be resubmitted later via the local tracker, which + // could cause confusion that a previously "rejected" transaction is later + // accepted. + return b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0] } func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {