forked from ronin-chain/ronin
-
Notifications
You must be signed in to change notification settings - Fork 0
core/txpool/legacypool: legacypool supports SetCode tx #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sonhv0212
wants to merge
6
commits into
base-eip-7702
Choose a base branch
from
eip-7702-tx-pool
base: base-eip-7702
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
9ccf4f2
core/txpool/legacypool: Remove unused function parameter
sonhv0212 e35ed51
core/txpool/legacypool: add method Contains for list
sonhv0212 c4b7dd0
core/txpool/legacypool: support setcode tx
sonhv0212 b81f6c2
core/txpool/legacypool: add test setcode tx
sonhv0212 6f4bb08
core/txpool/legacypool: add setCodeTx reorg test
sonhv0212 30f2d2f
core/txpool: fix error logs flood caused by removeAuthorities
sonhv0212 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,10 @@ package legacypool | |
|
|
||
| import ( | ||
| "container/heap" | ||
| "errors" | ||
| "math" | ||
| "math/big" | ||
| "slices" | ||
| "sort" | ||
| "sync" | ||
| "sync/atomic" | ||
|
|
@@ -32,6 +34,7 @@ import ( | |
| "github.com/ethereum/go-ethereum/core/state" | ||
| "github.com/ethereum/go-ethereum/core/txpool" | ||
| "github.com/ethereum/go-ethereum/core/types" | ||
| "github.com/ethereum/go-ethereum/crypto" | ||
| "github.com/ethereum/go-ethereum/event" | ||
| "github.com/ethereum/go-ethereum/log" | ||
| "github.com/ethereum/go-ethereum/metrics" | ||
|
|
@@ -53,6 +56,21 @@ const ( | |
| txMaxSize = 4 * txSlotSize // 128KB | ||
| ) | ||
|
|
||
| var ( | ||
| // 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") | ||
|
|
||
| // ErrAuthorityReserved is returned if a transaction has an authorization | ||
| // signed by an address which already has in-flight transactions known to the | ||
| // pool. | ||
| ErrAuthorityReserved = errors.New("authority already reserved") | ||
|
|
||
| // ErrFutureReplacePending is returned if a future transaction replaces a pending | ||
| // one. Future transactions should only be able to replace other future transactions. | ||
| ErrFutureReplacePending = errors.New("future transaction tries to replace pending") | ||
| ) | ||
|
|
||
| var ( | ||
| evictionInterval = time.Minute // Time interval to check for evictable transactions | ||
| statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats | ||
|
|
@@ -96,6 +114,8 @@ var ( | |
| reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil) | ||
| ) | ||
|
|
||
| var emptyCodeHash = crypto.Keccak256Hash(nil) | ||
|
|
||
| // blockChain provides the state of blockchain and current gas limit to do | ||
| // some pre checks in tx pool and event subscribers. | ||
| type blockChain interface { | ||
|
|
@@ -186,6 +206,20 @@ func (config *Config) sanitize() Config { | |
| // The pool separates processable transactions (which can be applied to the | ||
| // current state) and future transactions. Transactions move between those | ||
| // 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 | ||
| // 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. | ||
| // | ||
| // Because SetCode transactions can have many authorizations included, we avoid explicitly | ||
| // checking their validity to save the state lookup. So long as the encompassing | ||
| // transaction is valid, the authorization will be accepted and tracked by the pool. In | ||
| // case the pool is tracking a pending / queued transaction from a specific account, it | ||
| // will reject new transactions with delegations from that account with standard in-flight | ||
| // transactions. | ||
| type LegacyPool struct { | ||
| config Config | ||
| chainconfig *params.ChainConfig | ||
|
|
@@ -270,7 +304,7 @@ func New(config Config, chainconfig *params.ChainConfig, chain blockChain) *Lega | |
| // pool, specifically, whether it is a Legacy, AccessList, Dynamic or Sponsored transaction. | ||
| func (pool *LegacyPool) Filter(tx *types.Transaction) bool { | ||
| switch tx.Type() { | ||
| case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.SponsoredTxType: | ||
| case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.SetCodeTxType, types.SponsoredTxType: | ||
| return true | ||
| default: | ||
| return false | ||
|
|
@@ -596,7 +630,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro | |
| Accept: 0 | | ||
| 1<<types.LegacyTxType | | ||
| 1<<types.AccessListTxType | | ||
| 1<<types.DynamicFeeTxType, | ||
| 1<<types.DynamicFeeTxType | | ||
| 1<<types.SetCodeTxType, | ||
| MaxSize: txMaxSize, | ||
| MinTip: pool.gasTip.Load(), | ||
| AcceptSponsoredTx: true, | ||
|
|
@@ -612,23 +647,13 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro | |
|
|
||
| // validateTx checks whether a transaction is valid according to the consensus | ||
| // rules and adheres to some heuristic limits of the local node (price and size). | ||
| func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { | ||
| func (pool *LegacyPool) validateTx(tx *types.Transaction) error { | ||
| opts := &txpool.ValidationOptionsWithState{ | ||
| Config: pool.chainconfig, | ||
| State: pool.currentState, | ||
| Head: pool.currentHead.Load(), | ||
| FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps | ||
| // The global and account slot and queue are checked later | ||
| UsedAndLeftSlots: func(addr common.Address) (int, int) { | ||
| var have int | ||
| if list := pool.pending[addr]; list != nil { | ||
| have += list.Len() | ||
| } | ||
| if list := pool.queue[addr]; list != nil { | ||
| have += list.Len() | ||
| } | ||
| return have, math.MaxInt | ||
| }, | ||
| Config: pool.chainconfig, | ||
| State: pool.currentState, | ||
| Head: pool.currentHead.Load(), | ||
| FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps | ||
| UsedAndLeftSlots: nil, // Pool has own mechanism to limit the number of transactions | ||
| ExistingExpenditure: func(addr common.Address) *big.Int { | ||
| return pool.getAccountPendingCost(addr) | ||
| }, | ||
|
|
@@ -645,6 +670,45 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { | |
| return err | ||
| } | ||
|
|
||
| return pool.validateAuth(tx) | ||
| } | ||
|
|
||
| // 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) != 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 | ||
| } | ||
| } | ||
| // Authorities cannot conflict with any pending or queued transactions. | ||
| if auths := tx.SetCodeAuthorities(); len(auths) > 0 { | ||
| for _, auth := range auths { | ||
| if pool.pending[auth] != nil || pool.queue[auth] != nil { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should add |
||
| return ErrAuthorityReserved | ||
| } | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
|
|
@@ -665,7 +729,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e | |
| } | ||
|
|
||
| // If the transaction fails basic validation, discard it | ||
| if err := pool.validateTx(tx, local); err != nil { | ||
| if err := pool.validateTx(tx); err != nil { | ||
| log.Trace("Discarding invalid transaction", "hash", hash, "err", err) | ||
| invalidTxMeter.Mark(1) | ||
| return false, err | ||
|
|
@@ -1776,13 +1840,15 @@ type lookup struct { | |
| lock sync.RWMutex | ||
| locals map[common.Hash]*types.Transaction | ||
| remotes map[common.Hash]*types.Transaction | ||
| auths map[common.Address][]common.Hash // All accounts with a pooled authorization | ||
| } | ||
|
|
||
| // newLookup returns a new lookup structure. | ||
| func newLookup() *lookup { | ||
| return &lookup{ | ||
| locals: make(map[common.Hash]*types.Transaction), | ||
| remotes: make(map[common.Hash]*types.Transaction), | ||
| auths: make(map[common.Address][]common.Hash), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1881,6 +1947,7 @@ func (t *lookup) Add(tx *types.Transaction, local bool) { | |
| } else { | ||
| t.remotes[tx.Hash()] = tx | ||
| } | ||
| t.addAuthorities(tx) | ||
| } | ||
|
|
||
| // Remove removes a transaction from the lookup. | ||
|
|
@@ -1896,6 +1963,7 @@ func (t *lookup) Remove(hash common.Hash) { | |
| log.Error("No transaction found to be deleted", "hash", hash) | ||
| return | ||
| } | ||
| t.removeAuthorities(tx) | ||
| t.slots -= numSlots(tx) | ||
| slotsGauge.Update(int64(t.slots)) | ||
|
|
||
|
|
@@ -1939,7 +2007,81 @@ func (t *lookup) RemotesBelowTip(threshold *big.Int, isVenoki bool) types.Transa | |
| return found | ||
| } | ||
|
|
||
| // addAuthorities tracks the supplied tx in relation to each authority it | ||
| // specifies. | ||
| func (t *lookup) addAuthorities(tx *types.Transaction) { | ||
| for _, addr := range tx.SetCodeAuthorities() { | ||
| list, ok := t.auths[addr] | ||
| if !ok { | ||
| list = []common.Hash{} | ||
| } | ||
| if slices.Contains(list, tx.Hash()) { | ||
| // Don't add duplicates. | ||
| continue | ||
| } | ||
| list = append(list, tx.Hash()) | ||
| t.auths[addr] = list | ||
| } | ||
| } | ||
|
|
||
| // removeAuthorities stops tracking the supplied tx in relation to its | ||
| // authorities. | ||
| func (t *lookup) removeAuthorities(tx *types.Transaction) { | ||
| hash := tx.Hash() | ||
| for _, addr := range tx.SetCodeAuthorities() { | ||
| list := t.auths[addr] | ||
| // Remove tx from tracker. | ||
| if i := slices.Index(list, hash); i >= 0 { | ||
| list = append(list[:i], list[i+1:]...) | ||
| } else { | ||
| log.Error("Authority with untracked tx", "addr", addr, "hash", hash) | ||
| } | ||
| if len(list) == 0 { | ||
| // If list is newly empty, delete it entirely. | ||
| delete(t.auths, addr) | ||
| continue | ||
| } | ||
| t.auths[addr] = list | ||
| } | ||
| } | ||
|
|
||
| // numSlots calculates the number of slots needed for a single transaction. | ||
| func numSlots(tx *types.Transaction) int { | ||
| return int((tx.Size() + txSlotSize - 1) / txSlotSize) | ||
| } | ||
|
|
||
| // Clear implements txpool.SubPool, removing all tracked txs from the pool | ||
| // and rotating the journal. | ||
| // For testing purposes only. | ||
| func (pool *LegacyPool) Clear() { | ||
| pool.mu.Lock() | ||
| defer pool.mu.Unlock() | ||
|
|
||
| // unreserve each tracked account. Ideally, we could just clear the | ||
| // reservation map in the parent txpool context. However, if we clear in | ||
| // parent context, to avoid exposing the subpool lock, we have to lock the | ||
| // reservations and then lock each subpool. | ||
| // | ||
| // This creates the potential for a deadlock situation: | ||
| // | ||
| // * TxPool.Clear locks the reservations | ||
| // * a new transaction is received which locks the subpool mutex | ||
| // * TxPool.Clear attempts to lock subpool mutex | ||
| // | ||
| // The transaction addition may attempt to reserve the sender addr which | ||
| // can't happen until Clear releases the reservation lock. Clear cannot | ||
| // acquire the subpool lock until the transaction addition is completed. | ||
| for _, tx := range pool.all.locals { | ||
| senderAddr, _ := types.Sender(pool.signer, tx) | ||
| pool.reserve(senderAddr, false) | ||
| } | ||
| for _, tx := range pool.all.remotes { | ||
| senderAddr, _ := types.Sender(pool.signer, tx) | ||
| pool.reserve(senderAddr, false) | ||
| } | ||
| pool.all = newLookup() | ||
| pool.priced = newPricedList(pool.all) | ||
| pool.pending = make(map[common.Address]*list) | ||
| pool.queue = make(map[common.Address]*list) | ||
| pool.pendingNonces = newNoncer(pool.currentState) | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add this to reject sponsored transaction with pending authorization