mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
This commit is contained in:
parent
96a9c89aa6
commit
48b6063261
2 changed files with 60 additions and 36 deletions
|
|
@ -68,6 +68,10 @@ var (
|
|||
// another remote transaction.
|
||||
ErrTxPoolOverflow = errors.New("txpool is full")
|
||||
|
||||
// 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")
|
||||
|
||||
// ErrInflightTxLimitReached is returned when the maximum number of in-flight
|
||||
// transactions is reached for specific accounts.
|
||||
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts")
|
||||
|
|
@ -230,11 +234,11 @@ func (config *Config) sanitize() Config {
|
|||
// two states over time as they are received and processed.
|
||||
//
|
||||
// In addition to tracking transactions, the pool also tracks a set of pending SetCode
|
||||
// authorizations (EIP7702). This helps minimize number of transactions that can be
|
||||
// authorizations (EIP7702). This helps minimize the number of transactions that can be
|
||||
// trivially churned in the pool. As a standard rule, any account with a deployed
|
||||
// delegation or an in-flight authorization to deploy a delegation will only be allowed a
|
||||
// single transaction slot instead of the standard number. This is due to the possibility
|
||||
// of the account being sweeped by an unrelated account.
|
||||
// of the account being swept by an unrelated account.
|
||||
//
|
||||
// Because SetCode transactions can have many authorizations included, we avoid explicitly
|
||||
// checking their validity to save the state lookup. So long as the encompassing
|
||||
|
|
@ -723,33 +727,39 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) 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 && !pool.all.hasAuth(from) {
|
||||
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())
|
||||
}
|
||||
// Replacing the existing in-flight transaction for delegated accounts
|
||||
// is 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 {
|
||||
|
|
@ -1236,7 +1246,7 @@ func (pool *LegacyPool) Has(hash common.Hash) bool {
|
|||
// removeTx removes a single transaction from the queue, moving all subsequent
|
||||
// transactions back to the future queue.
|
||||
//
|
||||
// In unreserve is false, the account will not be relinquished to the main txpool
|
||||
// If unreserve is false, the account will not be relinquished to the main txpool
|
||||
// even if there are no more references to it. This is used to handle a race when
|
||||
// a tx being added, and it evicts a previously scheduled tx from the same account,
|
||||
// which could lead to a premature release of the lock.
|
||||
|
|
@ -2173,6 +2183,15 @@ func (t *lookup) removeAuthorities(hash common.Hash) {
|
|||
}
|
||||
}
|
||||
|
||||
// hasAuth returns a flag indicating whether there are pending authorizations
|
||||
// from the specified address.
|
||||
func (t *lookup) hasAuth(addr common.Address) bool {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return len(t.auths[addr]) > 0
|
||||
}
|
||||
|
||||
// numSlots calculates the number of slots needed for a single transaction.
|
||||
func numSlots(tx *types.Transaction) int {
|
||||
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
||||
|
|
|
|||
|
|
@ -2658,7 +2658,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
|||
minGasFee := uint256.MustFromBig(minGasPrice)
|
||||
doubleGasFee := new(uint256.Int).Mul(new(uint256.Int).Set(minGasFee), uint256.NewInt(2))
|
||||
tripleGasFee := new(uint256.Int).Mul(new(uint256.Int).Set(minGasFee), uint256.NewInt(3))
|
||||
legacyReplacePrice := new(big.Int).Mul(new(big.Int).Set(minGasPrice), big.NewInt(10))
|
||||
legacyReplacePrice := new(big.Int).Mul(minGasPrice, big.NewInt(10))
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
|
|
@ -2676,15 +2676,20 @@ 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, minGasPrice, 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, minGasPrice, keyA)); err != nil {
|
||||
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, new(big.Int).Set(minGasPrice), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
|
||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, minGasPrice, keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
|
||||
}
|
||||
// Also check gapped transaction.
|
||||
if err := pool.addRemoteSync(pricedTransaction(2, 100000, new(big.Int).Set(minGasPrice), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
|
||||
// Check gapped transaction again.
|
||||
if err := pool.addRemoteSync(pricedTransaction(2, 100000, minGasPrice, keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
|
||||
}
|
||||
// Replace by fee.
|
||||
|
|
@ -2715,11 +2720,11 @@ func TestSetCodeTransactions(t *testing.T) {
|
|||
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyC)); err != nil {
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, minGasPrice, keyC)); err != nil {
|
||||
t.Fatalf("%s: failed to add with pending delegatio: %v", name, err)
|
||||
}
|
||||
// Also check gapped transaction is rejected.
|
||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, new(big.Int).Set(minGasPrice), keyC)); !errors.Is(err, ErrInflightTxLimitReached) {
|
||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, minGasPrice, keyC)); !errors.Is(err, ErrInflightTxLimitReached) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
|
||||
}
|
||||
},
|
||||
|
|
@ -2749,7 +2754,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
|||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
// Now send a regular tx from B.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyB)); err != nil {
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, minGasPrice, keyB)); err != nil {
|
||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||
}
|
||||
},
|
||||
|
|
@ -2770,7 +2775,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
|||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||
}
|
||||
// Make sure we can still send from keyB.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyB)); err != nil {
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, minGasPrice, keyB)); err != nil {
|
||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||
}
|
||||
},
|
||||
|
|
@ -2792,10 +2797,10 @@ func TestSetCodeTransactions(t *testing.T) {
|
|||
}
|
||||
// Make sure we can only pool one tx from keyC since it is still a
|
||||
// pending authority.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyC)); err != nil {
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, minGasPrice, keyC)); err != nil {
|
||||
t.Fatalf("%s: failed to added single pooled for account with pending delegation: %v", name, err)
|
||||
}
|
||||
if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, new(big.Int).Set(minGasPrice), keyC)), ErrInflightTxLimitReached; !errors.Is(err, want) {
|
||||
if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, minGasPrice, keyC)), ErrInflightTxLimitReached; !errors.Is(err, want) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
|
||||
}
|
||||
},
|
||||
|
|
@ -2805,7 +2810,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
|||
pending: 1,
|
||||
run: func(name string) {
|
||||
// Attempt to submit a delegation from an account with a pending tx.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyC)); err != nil {
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, minGasPrice, keyC)); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err, want := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})), ErrAuthorityReserved; !errors.Is(err, want) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue