mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-09 14:34:27 +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.
|
// another remote transaction.
|
||||||
ErrTxPoolOverflow = errors.New("txpool is full")
|
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
|
// ErrInflightTxLimitReached is returned when the maximum number of in-flight
|
||||||
// transactions is reached for specific accounts.
|
// transactions is reached for specific accounts.
|
||||||
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated 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.
|
// two states over time as they are received and processed.
|
||||||
//
|
//
|
||||||
// In addition to tracking transactions, the pool also tracks a set of pending SetCode
|
// 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
|
// 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
|
// 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
|
// 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
|
// Because SetCode transactions can have many authorizations included, we avoid explicitly
|
||||||
// checking their validity to save the state lookup. So long as the encompassing
|
// 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)
|
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
|
// validateAuth verifies that the transaction complies with code authorization
|
||||||
// restrictions brought by SetCode transaction type.
|
// restrictions brought by SetCode transaction type.
|
||||||
func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
|
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
|
// Allow at most one in-flight tx for delegated accounts or those with a
|
||||||
// pending authorization.
|
// pending authorization.
|
||||||
if pool.currentState.GetCodeHash(from) != types.EmptyCodeHash || len(pool.all.auths[from]) != 0 {
|
if err := pool.checkDelegationLimit(tx); err != nil {
|
||||||
var (
|
return err
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Authorities cannot conflict with any pending or queued transactions.
|
// Authorities cannot conflict with any pending or queued transactions.
|
||||||
if auths := tx.SetCodeAuthorities(); len(auths) > 0 {
|
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
|
// removeTx removes a single transaction from the queue, moving all subsequent
|
||||||
// transactions back to the future queue.
|
// 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
|
// 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,
|
// 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.
|
// 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.
|
// numSlots calculates the number of slots needed for a single transaction.
|
||||||
func numSlots(tx *types.Transaction) int {
|
func numSlots(tx *types.Transaction) int {
|
||||||
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
||||||
|
|
|
||||||
|
|
@ -2658,7 +2658,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
minGasFee := uint256.MustFromBig(minGasPrice)
|
minGasFee := uint256.MustFromBig(minGasPrice)
|
||||||
doubleGasFee := new(uint256.Int).Mul(new(uint256.Int).Set(minGasFee), uint256.NewInt(2))
|
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))
|
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 {
|
for _, tt := range []struct {
|
||||||
name string
|
name string
|
||||||
|
|
@ -2676,15 +2676,20 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
aa := common.Address{0xaa, 0xaa}
|
aa := common.Address{0xaa, 0xaa}
|
||||||
statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...))
|
statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...))
|
||||||
statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)})
|
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.
|
// Send transactions. First is accepted, second is rejected.
|
||||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, minGasPrice, keyA)); err != nil {
|
if err := pool.addRemoteSync(pricedTransaction(0, 100000, minGasPrice, keyA)); err != nil {
|
||||||
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
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)
|
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, new(big.Int).Set(minGasPrice), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
|
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)
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
|
||||||
}
|
}
|
||||||
// Replace by fee.
|
// Replace by fee.
|
||||||
|
|
@ -2715,11 +2720,11 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyC}})); err != nil {
|
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)
|
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)
|
t.Fatalf("%s: failed to add with pending delegatio: %v", name, err)
|
||||||
}
|
}
|
||||||
// Also check gapped transaction is rejected.
|
// 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)
|
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)
|
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
// Now send a regular tx from B.
|
// 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)
|
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)
|
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||||
}
|
}
|
||||||
// Make sure we can still send from keyB.
|
// 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)
|
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
|
// Make sure we can only pool one tx from keyC since it is still a
|
||||||
// pending authority.
|
// 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)
|
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)
|
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2805,7 +2810,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
||||||
pending: 1,
|
pending: 1,
|
||||||
run: func(name string) {
|
run: func(name string) {
|
||||||
// Attempt to submit a delegation from an account with a pending tx.
|
// 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)
|
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) {
|
if err, want := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})), ErrAuthorityReserved; !errors.Is(err, want) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue