forked from forks/go-ethereum
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 <lightclient@protonmail.com>
This commit is contained in:
parent
9eb610f0a9
commit
7fed9584b5
2 changed files with 38 additions and 23 deletions
|
|
@ -67,6 +67,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.
|
||||
|
|
@ -606,33 +610,39 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
|
|||
return pool.validateAuth(tx)
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
return ErrInflightTxLimitReached
|
||||
}
|
||||
|
||||
// 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.checkDelegationLimit(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
// Authorities cannot conflict with any pending or queued transactions.
|
||||
if auths := tx.SetCodeAuthorities(); len(auths) > 0 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
// 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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue