@@ -21,6 +21,7 @@ import (
21
21
"errors"
22
22
"math"
23
23
"math/big"
24
+ "slices"
24
25
"sort"
25
26
"sync"
26
27
"sync/atomic"
@@ -196,6 +197,20 @@ func (config *Config) sanitize() Config {
196
197
// The pool separates processable transactions (which can be applied to the
197
198
// current state) and future transactions. Transactions move between those
198
199
// 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.
199
214
type LegacyPool struct {
200
215
config Config
201
216
chainconfig * params.ChainConfig
@@ -263,7 +278,7 @@ func New(config Config, chain BlockChain) *LegacyPool {
263
278
// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
264
279
func (pool * LegacyPool ) Filter (tx * types.Transaction ) bool {
265
280
switch tx .Type () {
266
- case types .LegacyTxType , types .AccessListTxType , types .DynamicFeeTxType :
281
+ case types .LegacyTxType , types .AccessListTxType , types .DynamicFeeTxType , types . SetCodeTxType :
267
282
return true
268
283
default :
269
284
return false
@@ -540,7 +555,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
540
555
Accept : 0 |
541
556
1 << types .LegacyTxType |
542
557
1 << types .AccessListTxType |
543
- 1 << types .DynamicFeeTxType ,
558
+ 1 << types .DynamicFeeTxType |
559
+ 1 << types .SetCodeTxType ,
544
560
MaxSize : txMaxSize ,
545
561
MinTip : pool .gasTip .Load ().ToBig (),
546
562
}
@@ -565,6 +581,11 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
565
581
if list := pool .queue [addr ]; list != nil {
566
582
have += list .Len ()
567
583
}
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
+ }
568
589
return have , math .MaxInt
569
590
},
570
591
ExistingExpenditure : func (addr common.Address ) * big.Int {
@@ -581,6 +602,18 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
581
602
}
582
603
return nil
583
604
},
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
+ },
584
617
}
585
618
if err := txpool .ValidateTransactionWithState (tx , pool .signer , opts ); err != nil {
586
619
return err
@@ -1334,15 +1367,13 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
1334
1367
// Drop all transactions that are deemed too old (low nonce)
1335
1368
forwards := list .Forward (pool .currentState .GetNonce (addr ))
1336
1369
for _ , tx := range forwards {
1337
- hash := tx .Hash ()
1338
- pool .all .Remove (hash )
1370
+ pool .all .Remove (tx .Hash ())
1339
1371
}
1340
1372
log .Trace ("Removed old queued transactions" , "count" , len (forwards ))
1341
1373
// Drop all transactions that are too costly (low balance or out of gas)
1342
1374
drops , _ := list .Filter (pool .currentState .GetBalance (addr ), gasLimit )
1343
1375
for _ , tx := range drops {
1344
- hash := tx .Hash ()
1345
- pool .all .Remove (hash )
1376
+ pool .all .Remove (tx .Hash ())
1346
1377
}
1347
1378
log .Trace ("Removed unpayable queued transactions" , "count" , len (drops ))
1348
1379
queuedNofundsMeter .Mark (int64 (len (drops )))
@@ -1531,8 +1562,8 @@ func (pool *LegacyPool) demoteUnexecutables() {
1531
1562
drops , invalids := list .Filter (pool .currentState .GetBalance (addr ), gasLimit )
1532
1563
for _ , tx := range drops {
1533
1564
hash := tx .Hash ()
1534
- log .Trace ("Removed unpayable pending transaction" , "hash" , hash )
1535
1565
pool .all .Remove (hash )
1566
+ log .Trace ("Removed unpayable pending transaction" , "hash" , hash )
1536
1567
}
1537
1568
pendingNofundsMeter .Mark (int64 (len (drops )))
1538
1569
@@ -1641,12 +1672,15 @@ type lookup struct {
1641
1672
slots int
1642
1673
lock sync.RWMutex
1643
1674
txs map [common.Hash ]* types.Transaction
1675
+
1676
+ auths map [common.Address ][]common.Hash // All accounts with a pooled authorization
1644
1677
}
1645
1678
1646
1679
// newLookup returns a new lookup structure.
1647
1680
func newLookup () * lookup {
1648
1681
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 ),
1650
1684
}
1651
1685
}
1652
1686
@@ -1697,13 +1731,15 @@ func (t *lookup) Add(tx *types.Transaction) {
1697
1731
slotsGauge .Update (int64 (t .slots ))
1698
1732
1699
1733
t .txs [tx .Hash ()] = tx
1734
+ t .addAuthorities (tx )
1700
1735
}
1701
1736
1702
1737
// Remove removes a transaction from the lookup.
1703
1738
func (t * lookup ) Remove (hash common.Hash ) {
1704
1739
t .lock .Lock ()
1705
1740
defer t .lock .Unlock ()
1706
1741
1742
+ t .removeAuthorities (hash )
1707
1743
tx , ok := t .txs [hash ]
1708
1744
if ! ok {
1709
1745
log .Error ("No transaction found to be deleted" , "hash" , hash )
@@ -1727,6 +1763,43 @@ func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions {
1727
1763
return found
1728
1764
}
1729
1765
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
+
1730
1803
// numSlots calculates the number of slots needed for a single transaction.
1731
1804
func numSlots (tx * types.Transaction ) int {
1732
1805
return int ((tx .Size () + txSlotSize - 1 ) / txSlotSize )
0 commit comments