-
Notifications
You must be signed in to change notification settings - Fork 69
core/txpool: declare address already reserved error #29095
#1920
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
base: dev-upgrade
Are you sure you want to change the base?
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
address already reserved error #29095
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.
Pull request overview
This PR introduces an address reservation mechanism for the transaction pool system to prevent concurrent management of the same account across different subpools. The changes include declaring new errors (ErrAlreadyReserved and ErrAccountLimitExceeded), introducing a LazyTransaction type for deferred transaction loading, and relocating transaction ordering logic from the core/types package to the miner package.
Key Changes:
- Adds address reservation functionality with
AddressReservercallback to ensure exclusive subpool access to accounts - Introduces
LazyTransactionwrapper type for efficient transaction handling with deferred resolution - Moves transaction ordering logic (
transactionsByPriceAndNonce) fromcore/typestominerpackage to supportLazyTransaction
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| core/txpool/errors.go | Declares new error types ErrAlreadyReserved and ErrAccountLimitExceeded |
| core/txpool/txpool.go | Implements address reservation tracking with reserver method and reservation map |
| core/txpool/subpool.go | Defines LazyTransaction type and AddressReserver function signature |
| core/txpool/validation.go | Adds account slot limit validation using UsedAndLeftSlots callback |
| core/txpool/legacypool/legacypool.go | Integrates reservation mechanism into add/remove operations and implements slot tracking |
| miner/ordering.go | New file containing transaction ordering logic adapted for LazyTransaction |
| miner/ordering_test.go | New file with comprehensive tests for transaction ordering including EIP-1559 support |
| miner/worker.go | Updates to use LazyTransaction and new ordering functions |
| eth/protocol.go | Updates txPool interface to return LazyTransaction from Pending |
| eth/sync.go | Resolves lazy transactions when syncing |
| eth/api_backend.go | Resolves lazy transactions when fetching pool transactions |
| eth/helper_test.go | Updates test helper to return LazyTransaction from Pending |
| core/types/transaction.go | Removes old ordering logic, adds SetTime and Time methods |
| core/types/transaction_test.go | Removes tests moved to miner/ordering_test.go |
| core/txpool/legacypool/legacypool_test.go | Updates all test Init calls to include address reserver |
| core/txpool/legacypool/legacypool2_test.go | Updates test Init calls to include address reserver |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, spent, cost, new(big.Int).Sub(need, newBalance)) | ||
| } | ||
| // Transaction takes a new nonce value out of the pool. Ensure it doesn't | ||
| // overflow the number of permitted transactions from a single accoun |
Copilot
AI
Jan 6, 2026
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.
The comment contains a typo: "accoun" should be "account".
| // overflow the number of permitted transactions from a single accoun | |
| // overflow the number of permitted transactions from a single account |
| // price sorted transactions in a nonce-honouring way. | ||
| // | ||
| // Note, the input map is reowned so the caller should not interact any more with | ||
| // if after providing it to the constructor. |
Copilot
AI
Jan 6, 2026
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.
The comment has a grammatical error: "if after providing it to the constructor" should be "it after providing it to the constructor".
| // if after providing it to the constructor. | |
| // it after providing it to the constructor. |
| if tx := lazyTx.Resolve(); tx.Tx.IsSpecialTransaction() { | ||
| specialTxs = append(specialTxs, tx.Tx) | ||
| } else { | ||
| normalTxs = append(normalTxs, lazyTx) | ||
| } |
Copilot
AI
Jan 6, 2026
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.
There's a potential nil pointer dereference. If lazyTx.Resolve() returns nil, the code will panic when accessing tx.Tx.IsSpecialTransaction(). A nil check should be added before accessing tx.Tx.
| i_price := s.txs[i].fees | ||
| if tx := s.txs[i].tx.Resolve(); tx != nil && tx.Tx.To() != nil { | ||
| if _, ok := s.payersSwap[*tx.Tx.To()]; ok { | ||
| i_price = common.TRC21GasPrice | ||
| } | ||
| } | ||
|
|
||
| j_price := s.txs[j].fees | ||
| if tx := s.txs[j].tx.Resolve(); tx != nil && tx.Tx.To() != nil { | ||
| if _, ok := s.payersSwap[*tx.Tx.To()]; ok { | ||
| j_price = common.TRC21GasPrice | ||
| } | ||
| } | ||
|
|
||
| // If the prices are equal, use the time the transaction was first seen for | ||
| // deterministic sorting | ||
| cmp := i_price.Cmp(j_price) |
Copilot
AI
Jan 6, 2026
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.
Variable naming uses snake_case (i_price, j_price) which is inconsistent with Go naming conventions. These should be named using camelCase (iPrice, jPrice) to follow Go style guidelines.
| i_price := s.txs[i].fees | |
| if tx := s.txs[i].tx.Resolve(); tx != nil && tx.Tx.To() != nil { | |
| if _, ok := s.payersSwap[*tx.Tx.To()]; ok { | |
| i_price = common.TRC21GasPrice | |
| } | |
| } | |
| j_price := s.txs[j].fees | |
| if tx := s.txs[j].tx.Resolve(); tx != nil && tx.Tx.To() != nil { | |
| if _, ok := s.payersSwap[*tx.Tx.To()]; ok { | |
| j_price = common.TRC21GasPrice | |
| } | |
| } | |
| // If the prices are equal, use the time the transaction was first seen for | |
| // deterministic sorting | |
| cmp := i_price.Cmp(j_price) | |
| iPrice := s.txs[i].fees | |
| if tx := s.txs[i].tx.Resolve(); tx != nil && tx.Tx.To() != nil { | |
| if _, ok := s.payersSwap[*tx.Tx.To()]; ok { | |
| iPrice = common.TRC21GasPrice | |
| } | |
| } | |
| jPrice := s.txs[j].fees | |
| if tx := s.txs[j].tx.Resolve(); tx != nil && tx.Tx.To() != nil { | |
| if _, ok := s.payersSwap[*tx.Tx.To()]; ok { | |
| jPrice = common.TRC21GasPrice | |
| } | |
| } | |
| // If the prices are equal, use the time the transaction was first seen for | |
| // deterministic sorting | |
| cmp := iPrice.Cmp(jPrice) |
| if tx.IsSpecialTransaction() && pool.IsSigner(from) && pool.pendingNonces.get(from) == tx.Nonce() { | ||
| return pool.promoteSpecialTx(from, tx, isLocal) |
Copilot
AI
Jan 6, 2026
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.
The promoteSpecialTx function is called as an early return at line 722, bypassing the reservation logic that starts at line 731. This means special transactions from new addresses won't reserve the address, potentially causing issues when the account later adds normal transactions. The reservation logic should be moved before the special transaction check, or the special transaction path should also handle reservations.
| // ErrAlreadyReserved is returned if the sender address has a pending transaction | ||
| // in a different subpool. For example, this error is returned in response to any | ||
| // input transaction of non-blob type when a blob transaction from this sender | ||
| // remains pending (and vice-versa). | ||
| ErrAlreadyReserved = errors.New("address already reserved") |
Copilot
AI
Jan 6, 2026
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.
The new ErrAlreadyReserved error and address reservation mechanism lack test coverage. No tests verify the behavior when an address is already reserved by a different subpool, or when concurrent reservations are attempted. Tests should be added to verify this critical synchronization mechanism works correctly.
|
|
||
| txs := types.Transactions{} | ||
| for tx := txset.Peek(); tx != nil; tx = txset.Peek() { | ||
| txs = append(txs, tx.Tx.Tx) |
Copilot
AI
Jan 6, 2026
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.
The test is directly accessing tx.Tx.Tx at line 104 without checking if tx.Tx is nil. While the test sets up the LazyTransaction with Tx already populated, this doesn't match the actual usage pattern where LazyTransaction.Resolve() should be called to safely retrieve the underlying transaction. The test should call tx.Resolve() instead of directly accessing tx.Tx.
| txs = append(txs, tx.Tx.Tx) | |
| resolvedTx, err := tx.Resolve() | |
| if err != nil { | |
| t.Fatalf("failed to resolve lazy transaction: %v", err) | |
| } | |
| txs = append(txs, resolvedTx) |
| // 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 |
Copilot
AI
Jan 6, 2026
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.
The comment has inconsistent capitalization: "In unreserve is false" should be "If unreserve is false". Additionally, this appears to be a grammatical error where "In" should be "If".
| // 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 |
| addr, _ := types.Sender(pool.signer, tx) // already validated during insertion | ||
|
|
||
| // If after deletion there are no more transactions belonging to this account, | ||
| // relinquish the address reservation. It's a bit convoluted do this, via a |
Copilot
AI
Jan 6, 2026
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.
The comment has a grammatical error: "It's a bit convoluted do this" should be "It's a bit convoluted to do this".
| // relinquish the address reservation. It's a bit convoluted do this, via a | |
| // relinquish the address reservation. It's a bit convoluted to do this, via a |
| if list := pool.queue[addr]; list != nil { | ||
| have += list.Len() | ||
| } | ||
| return have, math.MaxInt |
Copilot
AI
Jan 6, 2026
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.
The UsedAndLeftSlots callback always returns math.MaxInt for the left slots, which means the account limit check in validation.go line 223 will never trigger. This makes the ErrAccountLimitExceeded error unreachable in the legacy pool implementation. If account limits should be enforced, this should return a proper limit value; otherwise, the validation check may be unnecessary for this pool type.
| return have, math.MaxInt | |
| // Enforce per-account slot limit using the shared txpool configuration. | |
| accountLimit := int(txpool.DefaultAccountSlots) | |
| if have >= accountLimit { | |
| return have, 0 | |
| } | |
| return have, accountLimit - have |
Proposed changes
Ref: ethereum#29095
Types of changes
What types of changes does your code introduce to XDC network?
Put an
✅in the boxes that applyImpacted Components
Which part of the codebase this PR will touch base on,
Put an
✅in the boxes that applyChecklist
Put an
✅in the boxes once you have confirmed below actions (or provide reasons on not doing so) that