Skip to content

Commit 7fed958

Browse files
core/txpool/legacypool: reject gapped tx from delegated account (#31430)
This pull request improves the protection mechanism in the txpool for senders with delegation. A sender with either delegation or pending delegation is now limited to a maximum of one in-flight executable transaction, while gapped transactions will be rejected. Reason: If nonce-gapped transaction from delegated/pending-delegated senders can be acceptable, then it's no-longer possible to send another "executable" transaction with correct nonce due to the policy of at most one inflight tx. The gapped transaction will be stuck in the txpool, with no meaningful way to unlock the sender. --------- Co-authored-by: lightclient <[email protected]>
1 parent 9eb610f commit 7fed958

File tree

2 files changed

+38
-23
lines changed

2 files changed

+38
-23
lines changed

core/txpool/legacypool/legacypool.go

+32-22
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ var (
6767
// transactions is reached for specific accounts.
6868
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts")
6969

70+
// ErrOutOfOrderTxFromDelegated is returned when the transaction with gapped
71+
// nonce received from the accounts with delegation or pending delegation.
72+
ErrOutOfOrderTxFromDelegated = errors.New("gapped-nonce tx from delegated accounts")
73+
7074
// ErrAuthorityReserved is returned if a transaction has an authorization
7175
// signed by an address which already has in-flight transactions known to the
7276
// pool.
@@ -606,33 +610,39 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
606610
return pool.validateAuth(tx)
607611
}
608612

613+
// checkDelegationLimit determines if the tx sender is delegated or has a
614+
// pending delegation, and if so, ensures they have at most one in-flight
615+
// **executable** transaction, e.g. disallow stacked and gapped transactions
616+
// from the account.
617+
func (pool *LegacyPool) checkDelegationLimit(tx *types.Transaction) error {
618+
from, _ := types.Sender(pool.signer, tx) // validated
619+
620+
// Short circuit if the sender has neither delegation nor pending delegation.
621+
if pool.currentState.GetCodeHash(from) == types.EmptyCodeHash && len(pool.all.auths[from]) == 0 {
622+
return nil
623+
}
624+
pending := pool.pending[from]
625+
if pending == nil {
626+
// Transaction with gapped nonce is not supported for delegated accounts
627+
if pool.pendingNonces.get(from) != tx.Nonce() {
628+
return ErrOutOfOrderTxFromDelegated
629+
}
630+
return nil
631+
}
632+
// Transaction replacement is supported
633+
if pending.Contains(tx.Nonce()) {
634+
return nil
635+
}
636+
return ErrInflightTxLimitReached
637+
}
638+
609639
// validateAuth verifies that the transaction complies with code authorization
610640
// restrictions brought by SetCode transaction type.
611641
func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
612-
from, _ := types.Sender(pool.signer, tx) // validated
613-
614642
// Allow at most one in-flight tx for delegated accounts or those with a
615643
// pending authorization.
616-
if pool.currentState.GetCodeHash(from) != types.EmptyCodeHash || len(pool.all.auths[from]) != 0 {
617-
var (
618-
count int
619-
exists bool
620-
)
621-
pending := pool.pending[from]
622-
if pending != nil {
623-
count += pending.Len()
624-
exists = pending.Contains(tx.Nonce())
625-
}
626-
queue := pool.queue[from]
627-
if queue != nil {
628-
count += queue.Len()
629-
exists = exists || queue.Contains(tx.Nonce())
630-
}
631-
// Replace the existing in-flight transaction for delegated accounts
632-
// are still supported
633-
if count >= 1 && !exists {
634-
return ErrInflightTxLimitReached
635-
}
644+
if err := pool.checkDelegationLimit(tx); err != nil {
645+
return err
636646
}
637647
// Authorities cannot conflict with any pending or queued transactions.
638648
if auths := tx.SetCodeAuthorities(); len(auths) > 0 {

core/txpool/legacypool/legacypool_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -2262,14 +2262,19 @@ func TestSetCodeTransactions(t *testing.T) {
22622262
aa := common.Address{0xaa, 0xaa}
22632263
statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...))
22642264
statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)})
2265+
2266+
// Send gapped transaction, it should be rejected.
2267+
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) {
2268+
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrOutOfOrderTxFromDelegated, err)
2269+
}
22652270
// Send transactions. First is accepted, second is rejected.
22662271
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
22672272
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
22682273
}
22692274
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
22702275
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
22712276
}
2272-
// Also check gapped transaction.
2277+
// Check gapped transaction again.
22732278
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
22742279
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
22752280
}

0 commit comments

Comments
 (0)