From bdc3e5d72f423edbc6ba5098f87b84febb8d97c3 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 18 Mar 2025 19:49:30 +0800 Subject: [PATCH 1/4] core/txpool/legacypool: reject gapped tx from delegated account --- core/txpool/legacypool/legacypool.go | 55 ++++++++++++++--------- core/txpool/legacypool/legacypool_test.go | 7 ++- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 8c09d48695c1..9a1093554a58 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -66,6 +66,10 @@ var ( // transactions is reached for specific accounts. ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts") + // ErrOutOfOrderTxFromDelegated is returned when the transaction with gapped + // nonce received from the accounts with delegation or pending delegation. + ErrOutOfOrderTxFromDelegated = errors.New("gapped-nonce tx from delegated accounts") + // ErrAuthorityReserved is returned if a transaction has an authorization // signed by an address which already has in-flight transactions known to the // pool. @@ -605,33 +609,40 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error { return pool.validateAuth(tx) } +// limitTxFromDelegated ensures the account with either delegation or pending +// delegation can at most send one inflight **executable** transaction. +func (pool *LegacyPool) limitTxFromDelegated(tx *types.Transaction) error { + from, _ := types.Sender(pool.signer, tx) // validated + + // Short circuit if the sender has neither delegation nor pending delegation. + if pool.currentState.GetCodeHash(from) == types.EmptyCodeHash && len(pool.all.auths[from]) == 0 { + return nil + } + pending := pool.pending[from] + if pending == nil { + // Transaction with gapped nonce is not supported for delegated accounts + if pool.pendingNonces.get(from) != tx.Nonce() { + return ErrOutOfOrderTxFromDelegated + } + return nil + } + // Transaction replacement is supported + if pending.Contains(tx.Nonce()) { + return nil + } + if pending.Len() >= 1 { + return ErrInflightTxLimitReached + } + return nil +} + // validateAuth verifies that the transaction complies with code authorization // restrictions brought by SetCode transaction type. func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { - from, _ := types.Sender(pool.signer, tx) // validated - // Allow at most one in-flight tx for delegated accounts or those with a // pending authorization. - if pool.currentState.GetCodeHash(from) != types.EmptyCodeHash || len(pool.all.auths[from]) != 0 { - var ( - count int - exists bool - ) - pending := pool.pending[from] - if pending != nil { - count += pending.Len() - exists = pending.Contains(tx.Nonce()) - } - queue := pool.queue[from] - if queue != nil { - count += queue.Len() - exists = exists || queue.Contains(tx.Nonce()) - } - // Replace the existing in-flight transaction for delegated accounts - // are still supported - if count >= 1 && !exists { - return ErrInflightTxLimitReached - } + if err := pool.limitTxFromDelegated(tx); err != nil { + return err } // Authorities cannot conflict with any pending or queued transactions. if auths := tx.SetCodeAuthorities(); len(auths) > 0 { diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index ef887041add0..d78f84b64078 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2262,6 +2262,11 @@ func TestSetCodeTransactions(t *testing.T) { aa := common.Address{0xaa, 0xaa} statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...)) statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)}) + + // Send gapped transaction, it should be rejected + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) { + t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrOutOfOrderTxFromDelegated, err) + } // Send transactions. First is accepted, second is rejected. if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil { t.Fatalf("%s: failed to add remote transaction: %v", name, err) @@ -2269,7 +2274,7 @@ func TestSetCodeTransactions(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } - // Also check gapped transaction. + // Also check gapped transaction again if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } From 1b7228f63ec42c7e0bcb635f77d9df6b600a10a4 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Thu, 20 Mar 2025 13:14:46 +0800 Subject: [PATCH 2/4] core/txpool/legacypool: address comments from marius --- core/txpool/legacypool/legacypool.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 9a1093554a58..41c9a2fe50f3 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -609,9 +609,9 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error { return pool.validateAuth(tx) } -// limitTxFromDelegated ensures the account with either delegation or pending +// ensureDelegationLimit ensures the account with either delegation or pending // delegation can at most send one inflight **executable** transaction. -func (pool *LegacyPool) limitTxFromDelegated(tx *types.Transaction) error { +func (pool *LegacyPool) ensureDelegationLimit(tx *types.Transaction) error { from, _ := types.Sender(pool.signer, tx) // validated // Short circuit if the sender has neither delegation nor pending delegation. @@ -630,10 +630,7 @@ func (pool *LegacyPool) limitTxFromDelegated(tx *types.Transaction) error { if pending.Contains(tx.Nonce()) { return nil } - if pending.Len() >= 1 { - return ErrInflightTxLimitReached - } - return nil + return ErrInflightTxLimitReached } // validateAuth verifies that the transaction complies with code authorization @@ -641,7 +638,7 @@ func (pool *LegacyPool) limitTxFromDelegated(tx *types.Transaction) error { func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { // Allow at most one in-flight tx for delegated accounts or those with a // pending authorization. - if err := pool.limitTxFromDelegated(tx); err != nil { + if err := pool.ensureDelegationLimit(tx); err != nil { return err } // Authorities cannot conflict with any pending or queued transactions. From 7091d18045615aad8e0186e9083495df75d3717b Mon Sep 17 00:00:00 2001 From: lightclient Date: Thu, 20 Mar 2025 12:19:29 -0600 Subject: [PATCH 3/4] core/txpool/legacypool: comment fixes --- core/txpool/legacypool/legacypool_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index d78f84b64078..3f269bd69ec1 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -2263,7 +2263,7 @@ func TestSetCodeTransactions(t *testing.T) { statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...)) statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)}) - // Send gapped transaction, it should be rejected + // Send gapped transaction, it should be rejected. if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrOutOfOrderTxFromDelegated, err) } @@ -2274,7 +2274,7 @@ func TestSetCodeTransactions(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } - // Also check gapped transaction again + // Check gapped transaction again. if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) { t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err) } From ebfcc3bccb1d078371adb3a6dd3ec5e380b90ee0 Mon Sep 17 00:00:00 2001 From: lightclient Date: Thu, 20 Mar 2025 12:45:14 -0600 Subject: [PATCH 4/4] core/txpool/legacypool: rename to checkDelegationLimit --- core/txpool/legacypool/legacypool.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 41c9a2fe50f3..48e48913fbdc 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -609,9 +609,11 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error { return pool.validateAuth(tx) } -// ensureDelegationLimit ensures the account with either delegation or pending -// delegation can at most send one inflight **executable** transaction. -func (pool *LegacyPool) ensureDelegationLimit(tx *types.Transaction) error { +// checkDelegationLimit determines if the tx sender is delegated or has a +// pending delegation, and if so, ensures they have at most one in-flight +// **executable** transaction, e.g. disallow stacked and gapped transactions +// from the account. +func (pool *LegacyPool) checkDelegationLimit(tx *types.Transaction) error { from, _ := types.Sender(pool.signer, tx) // validated // Short circuit if the sender has neither delegation nor pending delegation. @@ -638,7 +640,7 @@ func (pool *LegacyPool) ensureDelegationLimit(tx *types.Transaction) error { func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { // Allow at most one in-flight tx for delegated accounts or those with a // pending authorization. - if err := pool.ensureDelegationLimit(tx); err != nil { + if err := pool.checkDelegationLimit(tx); err != nil { return err } // Authorities cannot conflict with any pending or queued transactions.