Skip to content

Commit e26ad1f

Browse files
laggurjl493456442
authored andcommitted
eth: do not add failed tx to localTxTracker (ethereum#31202)
In transaction-sending APIs such as `eth_sendRawTransaction`, a submitted transaction failing the configured txpool validation rules (i.e. fee too low) would cause an error to be returned, even though the transaction was successfully added into the locals tracker. Once added there, the transaction may even be included into the chain at a later time, when fee market conditions change. This change improves on this by performing the validation in the locals tracker, basically skipping some of the validation rules for local transactions. We still try to add the tx to the main pool immediately, but an error will only be returned for transactions which are fundamentally invalid. --------- Co-authored-by: Gary Rong <[email protected]>
1 parent bc8e014 commit e26ad1f

File tree

6 files changed

+68
-21
lines changed

6 files changed

+68
-21
lines changed

core/txpool/blobpool/blobpool.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,19 +1085,24 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
10851085
p.updateStorageMetrics()
10861086
}
10871087

1088-
// validateTx checks whether a transaction is valid according to the consensus
1089-
// rules and adheres to some heuristic limits of the local node (price and size).
1090-
func (p *BlobPool) validateTx(tx *types.Transaction) error {
1091-
// Ensure the transaction adheres to basic pool filters (type, size, tip) and
1092-
// consensus rules
1093-
baseOpts := &txpool.ValidationOptions{
1088+
// ValidateTxBasics checks whether a transaction is valid according to the consensus
1089+
// rules, but does not check state-dependent validation such as sufficient balance.
1090+
// This check is meant as an early check which only needs to be performed once,
1091+
// and does not require the pool mutex to be held.
1092+
func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error {
1093+
opts := &txpool.ValidationOptions{
10941094
Config: p.chain.Config(),
10951095
Accept: 1 << types.BlobTxType,
10961096
MaxSize: txMaxSize,
10971097
MinTip: p.gasTip.ToBig(),
10981098
}
1099+
return txpool.ValidateTransaction(tx, p.head, p.signer, opts)
1100+
}
10991101

1100-
if err := p.txValidationFn(tx, p.head, p.signer, baseOpts); err != nil {
1102+
// validateTx checks whether a transaction is valid according to the consensus
1103+
// rules and adheres to some heuristic limits of the local node (price and size).
1104+
func (p *BlobPool) validateTx(tx *types.Transaction) error {
1105+
if err := p.ValidateTxBasics(tx); err != nil {
11011106
return err
11021107
}
11031108
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)

core/txpool/legacypool/legacypool.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -558,11 +558,11 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
558558
return pending
559559
}
560560

561-
// validateTxBasics checks whether a transaction is valid according to the consensus
561+
// ValidateTxBasics checks whether a transaction is valid according to the consensus
562562
// rules, but does not check state-dependent validation such as sufficient balance.
563563
// This check is meant as an early check which only needs to be performed once,
564564
// and does not require the pool mutex to be held.
565-
func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
565+
func (pool *LegacyPool) ValidateTxBasics(tx *types.Transaction) error {
566566
opts := &txpool.ValidationOptions{
567567
Config: pool.chainconfig,
568568
Accept: 0 |
@@ -573,10 +573,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
573573
MaxSize: txMaxSize,
574574
MinTip: pool.gasTip.Load().ToBig(),
575575
}
576-
if err := txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts); err != nil {
577-
return err
578-
}
579-
return nil
576+
return txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts)
580577
}
581578

582579
// 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 {
929926
// Exclude transactions with basic errors, e.g invalid signatures and
930927
// insufficient intrinsic gas as soon as possible and cache senders
931928
// in transactions before obtaining lock
932-
if err := pool.validateTxBasics(tx); err != nil {
929+
if err := pool.ValidateTxBasics(tx); err != nil {
933930
errs[i] = err
934931
log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err)
935932
invalidTxMeter.Mark(1)

core/txpool/locals/tx_tracker.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,28 +74,45 @@ func New(journalPath string, journalTime time.Duration, chainConfig *params.Chai
7474

7575
// Track adds a transaction to the tracked set.
7676
// Note: blob-type transactions are ignored.
77-
func (tracker *TxTracker) Track(tx *types.Transaction) {
78-
tracker.TrackAll([]*types.Transaction{tx})
77+
func (tracker *TxTracker) Track(tx *types.Transaction) error {
78+
return tracker.TrackAll([]*types.Transaction{tx})[0]
7979
}
8080

8181
// TrackAll adds a list of transactions to the tracked set.
8282
// Note: blob-type transactions are ignored.
83-
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
83+
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
8484
tracker.mu.Lock()
8585
defer tracker.mu.Unlock()
8686

87+
var errors []error
8788
for _, tx := range txs {
8889
if tx.Type() == types.BlobTxType {
90+
errors = append(errors, nil)
91+
continue
92+
}
93+
// Ignore the transactions which are failed for fundamental
94+
// validation such as invalid parameters.
95+
if err := tracker.pool.ValidateTxBasics(tx); err != nil {
96+
log.Debug("Invalid transaction submitted", "hash", tx.Hash(), "err", err)
97+
errors = append(errors, err)
8998
continue
9099
}
91100
// If we're already tracking it, it's a no-op
92101
if _, ok := tracker.all[tx.Hash()]; ok {
102+
errors = append(errors, nil)
93103
continue
94104
}
105+
// Theoretically, checking the error here is unnecessary since sender recovery
106+
// is already part of basic validation. However, retrieving the sender address
107+
// from the transaction cache is effectively a no-op if it was previously verified.
108+
// Therefore, the error is still checked just in case.
95109
addr, err := types.Sender(tracker.signer, tx)
96-
if err != nil { // Ignore this tx
110+
if err != nil {
111+
errors = append(errors, err)
97112
continue
98113
}
114+
errors = append(errors, nil)
115+
99116
tracker.all[tx.Hash()] = tx
100117
if tracker.byAddr[addr] == nil {
101118
tracker.byAddr[addr] = legacypool.NewSortedMap()
@@ -107,6 +124,7 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
107124
}
108125
}
109126
localGauge.Update(int64(len(tracker.all)))
127+
return errors
110128
}
111129

112130
// recheck checks and returns any transactions that needs to be resubmitted.

core/txpool/subpool.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ type SubPool interface {
129129
// retrieve blobs from the pools directly instead of the network.
130130
GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof)
131131

132+
// ValidateTxBasics checks whether a transaction is valid according to the consensus
133+
// rules, but does not check state-dependent validation such as sufficient balance.
134+
// This check is meant as a static check which can be performed without holding the
135+
// pool mutex.
136+
ValidateTxBasics(tx *types.Transaction) error
137+
132138
// Add enqueues a batch of transactions into the pool if they are valid. Due
133139
// to the large transaction churn, add may postpone fully integrating the tx
134140
// to a later point to batch multiple ones together.

core/txpool/txpool.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,17 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
325325
return nil, nil
326326
}
327327

328+
// ValidateTxBasics checks whether a transaction is valid according to the consensus
329+
// rules, but does not check state-dependent validation such as sufficient balance.
330+
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
331+
for _, subpool := range p.subpools {
332+
if subpool.Filter(tx) {
333+
return subpool.ValidateTxBasics(tx)
334+
}
335+
}
336+
return fmt.Errorf("%w: received type %d", core.ErrTxTypeNotSupported, tx.Type())
337+
}
338+
328339
// Add enqueues a batch of transactions into the pool if they are valid. Due
329340
// to the large transaction churn, add may postpone fully integrating the tx
330341
// to a later point to batch multiple ones together.

eth/api_backend.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,20 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
272272
}
273273

274274
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
275-
if locals := b.eth.localTxTracker; locals != nil {
276-
locals.Track(signedTx)
275+
locals := b.eth.localTxTracker
276+
if locals != nil {
277+
if err := locals.Track(signedTx); err != nil {
278+
return err
279+
}
280+
}
281+
// No error will be returned to user if the transaction fails stateful
282+
// validation (e.g., no available slot), as the locally submitted transactions
283+
// may be resubmitted later via the local tracker.
284+
err := b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]
285+
if err != nil && locals == nil {
286+
return err
277287
}
278-
return b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]
288+
return nil
279289
}
280290

281291
func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {

0 commit comments

Comments
 (0)