Skip to content

Commit 6b16faa

Browse files
author
colinlyguo
committed
feat(tx-pool): migrate upstream setcode transaction changes
1 parent 94fcd7d commit 6b16faa

File tree

4 files changed

+182
-61
lines changed

4 files changed

+182
-61
lines changed

core/tx_pool.go

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,6 @@ var (
7979
// with a different one without the required price bump.
8080
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
8181

82-
// ErrAccountLimitExceeded is returned if a transaction would exceed the number
83-
// allowed by a pool for a single account.
84-
ErrAccountLimitExceeded = errors.New("account limit exceeded")
85-
8682
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
8783
// maximum allowance of the current block.
8884
ErrGasLimit = errors.New("exceeds block gas limit")
@@ -96,6 +92,10 @@ var (
9692
// making the transaction invalid, rather a DOS protection.
9793
ErrOversizedData = errors.New("oversized data")
9894

95+
// ErrInflightTxLimitReached is returned when the maximum number of in-flight
96+
// transactions is reached for specific accounts.
97+
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts")
98+
9999
// ErrAuthorityReserved is returned if a transaction has an authorization
100100
// signed by an address which already has in-flight transactions known to the
101101
// pool.
@@ -809,19 +809,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
809809
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
810810
return ErrInsufficientFunds
811811
}
812-
list := pool.pending[from]
813-
if list == nil || !list.Overlaps(tx) {
814-
// Transaction takes a new nonce value out of the pool. Ensure it doesn't
815-
// overflow the number of permitted transactions from a single account
816-
// (i.e. max cancellable via out-of-bound transaction).
817-
if used, left := usedAndLeftSlots(pool, from); left <= 0 {
818-
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
819-
}
820-
// Verify no authorizations will invalidate existing transactions known to
821-
// the pool.
822-
if conflicts := knownConflicts(pool, tx.SetCodeAuthorities()); len(conflicts) > 0 {
823-
return fmt.Errorf("%w: authorization conflicts with other known tx", ErrAuthorityReserved)
824-
}
812+
if err := pool.validateAuth(from, tx); err != nil {
813+
return err
825814
}
826815
if tx.Type() == types.SetCodeTxType {
827816
if len(tx.SetCodeAuthorizations()) == 0 {
@@ -1895,6 +1884,43 @@ func (pool *TxPool) calculateTxsLifecycle(txs types.Transactions, t time.Time) {
18951884
}
18961885
}
18971886

1887+
// validateAuth verifies that the transaction complies with code authorization
1888+
// restrictions brought by SetCode transaction type.
1889+
func (pool *TxPool) validateAuth(from common.Address, tx *types.Transaction) error {
1890+
// Allow at most one in-flight tx for delegated accounts or those with a
1891+
// pending authorization.
1892+
if pool.currentState.GetKeccakCodeHash(from) != codehash.EmptyKeccakCodeHash || len(pool.all.auths[from]) != 0 {
1893+
var (
1894+
count int
1895+
exists bool
1896+
)
1897+
pending := pool.pending[from]
1898+
if pending != nil {
1899+
count += pending.Len()
1900+
exists = pending.Overlaps(tx)
1901+
}
1902+
queue := pool.queue[from]
1903+
if queue != nil {
1904+
count += queue.Len()
1905+
exists = exists || queue.Overlaps(tx)
1906+
}
1907+
// Replace the existing in-flight transaction for delegated accounts
1908+
// are still supported
1909+
if count >= 1 && !exists {
1910+
return ErrInflightTxLimitReached
1911+
}
1912+
}
1913+
// Authorities cannot conflict with any pending or queued transactions.
1914+
if auths := tx.SetCodeAuthorities(); len(auths) > 0 {
1915+
for _, auth := range auths {
1916+
if pool.pending[auth] != nil || pool.queue[auth] != nil {
1917+
return ErrAuthorityReserved
1918+
}
1919+
}
1920+
}
1921+
return nil
1922+
}
1923+
18981924
// PauseReorgs stops any new reorg jobs to be started but doesn't interrupt any existing ones that are in flight
18991925
// Keep in mind this function might block, although it is not expected to block for any significant amount of time
19001926
func (pool *TxPool) PauseReorgs() {
@@ -2132,7 +2158,6 @@ func (t *txLookup) Remove(hash common.Hash) {
21322158
t.lock.Lock()
21332159
defer t.lock.Unlock()
21342160

2135-
t.removeAuthorities(hash)
21362161
tx, ok := t.locals[hash]
21372162
if !ok {
21382163
tx, ok = t.remotes[hash]
@@ -2141,6 +2166,7 @@ func (t *txLookup) Remove(hash common.Hash) {
21412166
log.Error("No transaction found to be deleted", "hash", hash)
21422167
return
21432168
}
2169+
t.removeAuthorities(tx)
21442170
t.slots -= numSlots(tx)
21452171
slotsGauge.Update(int64(t.slots))
21462172

@@ -2196,8 +2222,9 @@ func (t *txLookup) addAuthorities(tx *types.Transaction) {
21962222

21972223
// removeAuthorities stops tracking the supplied tx in relation to its
21982224
// authorities.
2199-
func (t *txLookup) removeAuthorities(hash common.Hash) {
2200-
for addr := range t.auths {
2225+
func (t *txLookup) removeAuthorities(tx *types.Transaction) {
2226+
hash := tx.Hash()
2227+
for _, addr := range tx.SetCodeAuthorities() {
22012228
list := t.auths[addr]
22022229
// Remove tx from tracker.
22032230
if i := slices.Index(list, hash); i >= 0 {
@@ -2218,34 +2245,3 @@ func (t *txLookup) removeAuthorities(hash common.Hash) {
22182245
func numSlots(tx *types.Transaction) int {
22192246
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
22202247
}
2221-
2222-
// usedAndLeftSlots returns the number of slots used and left for the given address.
2223-
func usedAndLeftSlots(pool *TxPool, addr common.Address) (int, int) {
2224-
var have int
2225-
if list := pool.pending[addr]; list != nil {
2226-
have += list.Len()
2227-
}
2228-
if list := pool.queue[addr]; list != nil {
2229-
have += list.Len()
2230-
}
2231-
if pool.currentState.GetKeccakCodeHash(addr) != codehash.EmptyKeccakCodeHash || len(pool.all.auths[addr]) != 0 {
2232-
// Allow at most one in-flight tx for delegated accounts or those with
2233-
// a pending authorization.
2234-
return have, max(0, 1-have)
2235-
}
2236-
return have, math.MaxInt
2237-
}
2238-
2239-
// knownConflicts returns a list of addresses that conflict with the given authorities.
2240-
func knownConflicts(pool *TxPool, auths []common.Address) []common.Address {
2241-
var conflicts []common.Address
2242-
// Authorities cannot conflict with any pending or queued transactions.
2243-
for _, addr := range auths {
2244-
if list := pool.pending[addr]; list != nil {
2245-
conflicts = append(conflicts, addr)
2246-
} else if list := pool.queue[addr]; list != nil {
2247-
conflicts = append(conflicts, addr)
2248-
}
2249-
}
2250-
return conflicts
2251-
}

core/tx_pool_test.go

Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"math/big"
2525
"math/rand"
2626
"os"
27+
"slices"
2728
"sync/atomic"
2829
"testing"
2930
"time"
@@ -156,6 +157,10 @@ func pricedSetCodeTx(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, ke
156157
})
157158
authList = append(authList, auth)
158159
}
160+
return pricedSetCodeTxWithAuth(nonce, gaslimit, gasFee, tip, key, authList)
161+
}
162+
163+
func pricedSetCodeTxWithAuth(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, authList []types.SetCodeAuthorization) *types.Transaction {
159164
return types.MustSignNewTx(key, types.LatestSignerForChainID(params.TestChainConfig.ChainID), &types.SetCodeTx{
160165
ChainID: uint256.MustFromBig(params.TestChainConfig.ChainID),
161166
Nonce: nonce,
@@ -214,6 +219,34 @@ func validateTxPoolInternals(pool *TxPool) error {
214219
return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
215220
}
216221
}
222+
// Ensure all auths in pool are tracked
223+
for _, tx := range pool.all.locals {
224+
for _, addr := range tx.SetCodeAuthorities() {
225+
list := pool.all.auths[addr]
226+
if i := slices.Index(list, tx.Hash()); i < 0 {
227+
return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash())
228+
}
229+
}
230+
}
231+
for _, tx := range pool.all.remotes {
232+
for _, addr := range tx.SetCodeAuthorities() {
233+
list := pool.all.auths[addr]
234+
if i := slices.Index(list, tx.Hash()); i < 0 {
235+
return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash())
236+
}
237+
}
238+
}
239+
// Ensure all auths in pool have an associated tx in locals or remotes.
240+
for addr, hashes := range pool.all.auths {
241+
for _, hash := range hashes {
242+
_, inLocals := pool.all.locals[hash]
243+
_, inRemotes := pool.all.remotes[hash]
244+
245+
if !inLocals && !inRemotes {
246+
return fmt.Errorf("dangling authority, missing originating tx: addr %s, hash %s", addr, hash.Hex())
247+
}
248+
}
249+
}
217250
return nil
218251
}
219252

@@ -2701,12 +2734,12 @@ func TestSetCodeTransactions(t *testing.T) {
27012734
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
27022735
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
27032736
}
2704-
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrAccountLimitExceeded) {
2705-
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err)
2737+
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
2738+
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
27062739
}
27072740
// Also check gapped transaction.
2708-
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrAccountLimitExceeded) {
2709-
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err)
2741+
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
2742+
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
27102743
}
27112744
// Replace by fee.
27122745
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keyA)); err != nil {
@@ -2740,8 +2773,8 @@ func TestSetCodeTransactions(t *testing.T) {
27402773
t.Fatalf("%s: failed to add with pending delegation: %v", name, err)
27412774
}
27422775
// Also check gapped transaction is rejected.
2743-
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, ErrAccountLimitExceeded) {
2744-
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrAccountLimitExceeded, err)
2776+
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyC)); !errors.Is(err, ErrInflightTxLimitReached) {
2777+
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
27452778
}
27462779
},
27472780
},
@@ -2815,7 +2848,7 @@ func TestSetCodeTransactions(t *testing.T) {
28152848
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keyC)); err != nil {
28162849
t.Fatalf("%s: failed to added single pooled for account with pending delegation: %v", name, err)
28172850
}
2818-
if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyC)), ErrAccountLimitExceeded; !errors.Is(err, want) {
2851+
if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1000), keyC)), ErrInflightTxLimitReached; !errors.Is(err, want) {
28192852
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
28202853
}
28212854
},
@@ -2846,6 +2879,32 @@ func TestSetCodeTransactions(t *testing.T) {
28462879
}
28472880
},
28482881
},
2882+
{
2883+
name: "remove-hash-from-authority-tracker",
2884+
pending: 10,
2885+
run: func(name string, pool *TxPool, statedb *state.StateDB) {
2886+
var keys []*ecdsa.PrivateKey
2887+
for i := 0; i < 30; i++ {
2888+
key, _ := crypto.GenerateKey()
2889+
keys = append(keys, key)
2890+
addr := crypto.PubkeyToAddress(key.PublicKey)
2891+
testAddBalance(pool, addr, big.NewInt(params.Ether))
2892+
}
2893+
// Create a transactions with 3 unique auths so the lookup's auth map is
2894+
// filled with addresses.
2895+
for i := 0; i < 30; i += 3 {
2896+
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keys[i], []unsignedAuth{{0, keys[i]}, {0, keys[i+1]}, {0, keys[i+2]}})); err != nil {
2897+
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
2898+
}
2899+
}
2900+
// Replace one of the transactions with a normal transaction so that the
2901+
// original hash is removed from the tracker. The hash should be
2902+
// associated with 3 different authorities.
2903+
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1000), keys[0])); err != nil {
2904+
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
2905+
}
2906+
},
2907+
},
28492908
} {
28502909
// Create the pool to test the status retrievals with
28512910
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
@@ -2875,6 +2934,64 @@ func TestSetCodeTransactions(t *testing.T) {
28752934
}
28762935
}
28772936

2937+
func TestSetCodeTransactionsReorg(t *testing.T) {
2938+
t.Parallel()
2939+
2940+
// Create the pool to test the status retrievals with
2941+
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
2942+
blockchain := &testBlockChain{1000000, statedb, new(event.Feed)}
2943+
2944+
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
2945+
defer pool.Stop()
2946+
2947+
// Create the test accounts
2948+
var (
2949+
keyA, _ = crypto.GenerateKey()
2950+
addrA = crypto.PubkeyToAddress(keyA.PublicKey)
2951+
)
2952+
testAddBalance(pool, addrA, big.NewInt(params.Ether))
2953+
// Send an authorization for 0x42
2954+
var authList []types.SetCodeAuthorization
2955+
auth, _ := types.SignSetCode(keyA, types.SetCodeAuthorization{
2956+
ChainID: *uint256.MustFromBig(params.TestChainConfig.ChainID),
2957+
Address: common.Address{0x42},
2958+
Nonce: 0,
2959+
})
2960+
authList = append(authList, auth)
2961+
if err := pool.addRemoteSync(pricedSetCodeTxWithAuth(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, authList)); err != nil {
2962+
t.Fatalf("failed to add with remote setcode transaction: %v", err)
2963+
}
2964+
// Simulate the chain moving
2965+
blockchain.statedb.SetNonce(addrA, 1)
2966+
blockchain.statedb.SetCode(addrA, types.AddressToDelegation(auth.Address))
2967+
<-pool.requestReset(nil, nil)
2968+
// Set an authorization for 0x00
2969+
auth, _ = types.SignSetCode(keyA, types.SetCodeAuthorization{
2970+
ChainID: *uint256.MustFromBig(params.TestChainConfig.ChainID),
2971+
Address: common.Address{},
2972+
Nonce: 0,
2973+
})
2974+
authList = append(authList, auth)
2975+
if err := pool.addRemoteSync(pricedSetCodeTxWithAuth(1, 250000, uint256.NewInt(10), uint256.NewInt(3), keyA, authList)); err != nil {
2976+
t.Fatalf("failed to add with remote setcode transaction: %v", err)
2977+
}
2978+
// Try to add a transactions in
2979+
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
2980+
t.Fatalf("unexpected error %v, expecting %v", err, ErrInflightTxLimitReached)
2981+
}
2982+
// Simulate the chain moving
2983+
blockchain.statedb.SetNonce(addrA, 2)
2984+
blockchain.statedb.SetCode(addrA, nil)
2985+
<-pool.requestReset(nil, nil)
2986+
// Now send two transactions from addrA
2987+
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1000), keyA)); err != nil {
2988+
t.Fatalf("failed to added single transaction: %v", err)
2989+
}
2990+
if err := pool.addRemoteSync(pricedTransaction(3, 100000, big.NewInt(1000), keyA)); err != nil {
2991+
t.Fatalf("failed to added single transaction: %v", err)
2992+
}
2993+
}
2994+
28782995
// Benchmarks the speed of validating the contents of the pending queue of the
28792996
// transaction pool.
28802997
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }

core/types/transaction.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,15 +477,23 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
477477
return setcodetx.AuthList
478478
}
479479

480-
// SetCodeAuthorities returns a list of each authorization's corresponding authority.
480+
// SetCodeAuthorities returns a list of unique authorities from the
481+
// authorization list.
481482
func (tx *Transaction) SetCodeAuthorities() []common.Address {
482483
setcodetx, ok := tx.inner.(*SetCodeTx)
483484
if !ok {
484485
return nil
485486
}
486-
auths := make([]common.Address, 0, len(setcodetx.AuthList))
487+
var (
488+
marks = make(map[common.Address]bool)
489+
auths = make([]common.Address, 0, len(setcodetx.AuthList))
490+
)
487491
for _, auth := range setcodetx.AuthList {
488492
if addr, err := auth.Authority(); err == nil {
493+
if marks[addr] {
494+
continue
495+
}
496+
marks[addr] = true
489497
auths = append(auths, addr)
490498
}
491499
}

params/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
const (
2525
VersionMajor = 5 // Major version component of the current release
2626
VersionMinor = 8 // Minor version component of the current release
27-
VersionPatch = 25 // Patch version component of the current release
27+
VersionPatch = 26 // Patch version component of the current release
2828
VersionMeta = "mainnet" // Version metadata to append to the version string
2929
)
3030

0 commit comments

Comments
 (0)