@@ -21,6 +21,7 @@ import (
2121 "errors"
2222 "math"
2323 "math/big"
24+ "slices"
2425 "sort"
2526 "sync"
2627 "sync/atomic"
@@ -196,6 +197,20 @@ func (config *Config) sanitize() Config {
196197// The pool separates processable transactions (which can be applied to the
197198// current state) and future transactions. Transactions move between those
198199// two states over time as they are received and processed.
200+ //
201+ // In addition to tracking transactions, the pool also tracks a set of pending SetCode
202+ // authorizations (EIP7702). This helps minimize number of transactions that can be
203+ // trivially churned in the pool. As a standard rule, any account with a deployed
204+ // delegation or an in-flight authorization to deploy a delegation will only be allowed a
205+ // single transaction slot instead of the standard number. This is due to the possibility
206+ // of the account being sweeped by an unrelated account.
207+ //
208+ // Because SetCode transactions can have many authorizations included, we avoid explicitly
209+ // checking their validity to save the state lookup. So long as the encompassing
210+ // transaction is valid, the authorization will be accepted and tracked by the pool. In
211+ // case the pool is tracking a pending / queued transaction from a specific account, it
212+ // will reject new transactions with delegations from that account with standard in-flight
213+ // transactions.
199214type LegacyPool struct {
200215 config Config
201216 chainconfig * params.ChainConfig
@@ -263,7 +278,7 @@ func New(config Config, chain BlockChain) *LegacyPool {
263278// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
264279func (pool * LegacyPool ) Filter (tx * types.Transaction ) bool {
265280 switch tx .Type () {
266- case types .LegacyTxType , types .AccessListTxType , types .DynamicFeeTxType :
281+ case types .LegacyTxType , types .AccessListTxType , types .DynamicFeeTxType , types . SetCodeTxType :
267282 return true
268283 default :
269284 return false
@@ -540,7 +555,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
540555 Accept : 0 |
541556 1 << types .LegacyTxType |
542557 1 << types .AccessListTxType |
543- 1 << types .DynamicFeeTxType ,
558+ 1 << types .DynamicFeeTxType |
559+ 1 << types .SetCodeTxType ,
544560 MaxSize : txMaxSize ,
545561 MinTip : pool .gasTip .Load ().ToBig (),
546562 }
@@ -565,6 +581,11 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
565581 if list := pool .queue [addr ]; list != nil {
566582 have += list .Len ()
567583 }
584+ if pool .currentState .GetCodeHash (addr ) != types .EmptyCodeHash || len (pool .all .auths [addr ]) != 0 {
585+ // Allow at most one in-flight tx for delegated accounts or those with
586+ // a pending authorization.
587+ return have , max (0 , 1 - have )
588+ }
568589 return have , math .MaxInt
569590 },
570591 ExistingExpenditure : func (addr common.Address ) * big.Int {
@@ -581,6 +602,18 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
581602 }
582603 return nil
583604 },
605+ KnownConflicts : func (from common.Address , auths []common.Address ) []common.Address {
606+ var conflicts []common.Address
607+ // Authorities cannot conflict with any pending or queued transactions.
608+ for _ , addr := range auths {
609+ if list := pool .pending [addr ]; list != nil {
610+ conflicts = append (conflicts , addr )
611+ } else if list := pool .queue [addr ]; list != nil {
612+ conflicts = append (conflicts , addr )
613+ }
614+ }
615+ return conflicts
616+ },
584617 }
585618 if err := txpool .ValidateTransactionWithState (tx , pool .signer , opts ); err != nil {
586619 return err
@@ -1334,15 +1367,13 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
13341367 // Drop all transactions that are deemed too old (low nonce)
13351368 forwards := list .Forward (pool .currentState .GetNonce (addr ))
13361369 for _ , tx := range forwards {
1337- hash := tx .Hash ()
1338- pool .all .Remove (hash )
1370+ pool .all .Remove (tx .Hash ())
13391371 }
13401372 log .Trace ("Removed old queued transactions" , "count" , len (forwards ))
13411373 // Drop all transactions that are too costly (low balance or out of gas)
13421374 drops , _ := list .Filter (pool .currentState .GetBalance (addr ), gasLimit )
13431375 for _ , tx := range drops {
1344- hash := tx .Hash ()
1345- pool .all .Remove (hash )
1376+ pool .all .Remove (tx .Hash ())
13461377 }
13471378 log .Trace ("Removed unpayable queued transactions" , "count" , len (drops ))
13481379 queuedNofundsMeter .Mark (int64 (len (drops )))
@@ -1531,8 +1562,8 @@ func (pool *LegacyPool) demoteUnexecutables() {
15311562 drops , invalids := list .Filter (pool .currentState .GetBalance (addr ), gasLimit )
15321563 for _ , tx := range drops {
15331564 hash := tx .Hash ()
1534- log .Trace ("Removed unpayable pending transaction" , "hash" , hash )
15351565 pool .all .Remove (hash )
1566+ log .Trace ("Removed unpayable pending transaction" , "hash" , hash )
15361567 }
15371568 pendingNofundsMeter .Mark (int64 (len (drops )))
15381569
@@ -1641,12 +1672,15 @@ type lookup struct {
16411672 slots int
16421673 lock sync.RWMutex
16431674 txs map [common.Hash ]* types.Transaction
1675+
1676+ auths map [common.Address ][]common.Hash // All accounts with a pooled authorization
16441677}
16451678
16461679// newLookup returns a new lookup structure.
16471680func newLookup () * lookup {
16481681 return & lookup {
1649- txs : make (map [common.Hash ]* types.Transaction ),
1682+ txs : make (map [common.Hash ]* types.Transaction ),
1683+ auths : make (map [common.Address ][]common.Hash ),
16501684 }
16511685}
16521686
@@ -1697,13 +1731,15 @@ func (t *lookup) Add(tx *types.Transaction) {
16971731 slotsGauge .Update (int64 (t .slots ))
16981732
16991733 t .txs [tx .Hash ()] = tx
1734+ t .addAuthorities (tx )
17001735}
17011736
17021737// Remove removes a transaction from the lookup.
17031738func (t * lookup ) Remove (hash common.Hash ) {
17041739 t .lock .Lock ()
17051740 defer t .lock .Unlock ()
17061741
1742+ t .removeAuthorities (hash )
17071743 tx , ok := t .txs [hash ]
17081744 if ! ok {
17091745 log .Error ("No transaction found to be deleted" , "hash" , hash )
@@ -1727,6 +1763,43 @@ func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions {
17271763 return found
17281764}
17291765
1766+ // addAuthorities tracks the supplied tx in relation to each authority it
1767+ // specifies.
1768+ func (t * lookup ) addAuthorities (tx * types.Transaction ) {
1769+ for _ , addr := range tx .SetCodeAuthorities () {
1770+ list , ok := t .auths [addr ]
1771+ if ! ok {
1772+ list = []common.Hash {}
1773+ }
1774+ if slices .Contains (list , tx .Hash ()) {
1775+ // Don't add duplicates.
1776+ continue
1777+ }
1778+ list = append (list , tx .Hash ())
1779+ t .auths [addr ] = list
1780+ }
1781+ }
1782+
1783+ // removeAuthorities stops tracking the supplied tx in relation to its
1784+ // authorities.
1785+ func (t * lookup ) removeAuthorities (hash common.Hash ) {
1786+ for addr := range t .auths {
1787+ list := t .auths [addr ]
1788+ // Remove tx from tracker.
1789+ if i := slices .Index (list , hash ); i >= 0 {
1790+ list = append (list [:i ], list [i + 1 :]... )
1791+ } else {
1792+ log .Error ("Authority with untracked tx" , "addr" , addr , "hash" , hash )
1793+ }
1794+ if len (list ) == 0 {
1795+ // If list is newly empty, delete it entirely.
1796+ delete (t .auths , addr )
1797+ continue
1798+ }
1799+ t .auths [addr ] = list
1800+ }
1801+ }
1802+
17301803// numSlots calculates the number of slots needed for a single transaction.
17311804func numSlots (tx * types.Transaction ) int {
17321805 return int ((tx .Size () + txSlotSize - 1 ) / txSlotSize )
0 commit comments