Revalidate the txpool across fork boundaries - #12755
Conversation
|
Claude finished @flcl42's task in 9m 3s —— View job Review: Revalidate the txpool across fork boundaries
The gap being closed is real and worth fixing: nothing today re-checks spec-dependent tx validity when the head crosses a fork, so txs that were fine under fork N can linger and be selected under fork N+1. The two-sided approach (producer-side check for the boundary block, pool-side purge for everything after) is the right shape, and the regression tests target exactly the right behaviours — in particular The concerns below are all about how much work the new paths do at the boundary, plus two pool-hygiene issues. Findings
Smaller notes (not filed inline)
Not verifiedI could not build or run the test suite in this environment (build commands weren't permitted), so findings are from static analysis of the call paths. The author reports Recommendation: address #1 (the KZG/DB storm is the one with production impact at a mainnet fork) and #2–#5, or reply with rationale where you disagree. |
|
Claude finished @flcl42's task in 9m 26s —— View job Review: Revalidate the txpool across fork boundaries
Good progress since the last round — five of the previous findings are genuinely fixed, and the fixes are the right ones:
The new findings below are all in the code that was added to fix those, and two of them are more serious than anything in the first round. Findings
Verified while reviewing (not findings)
Not verifiedBuild and test commands were not permitted in this environment, so all of the above is static analysis. The one claim I'd most like executed is H1: delete the two Recommendation: C1 and H1 should land before merge — C1 is an OOM/latency regression that triggers on every restart rather than only at forks, and H1 means the blob path of the fix is currently inert. H2 and M1 are contained but real. |
|
Claude finished @flcl42's task in 8m 53s —— View job Review: Revalidate the txpool across fork boundaries
The design has changed substantially since the last round and mostly for the better. The producer no longer guesses at the next block's spec — it receives the actual
Findings
Verified while reviewing (not findings)
Not verifiedBuild and Recommendation: C1 is the one that should block — it is an OOM/latency regression under default config that now fires on every restart, and it has survived two review rounds while growing in scope. M1 and M2 are cheap to address. Everything else is polish. |
Replace the four interdependent fields tracking revalidation state, and the instance field used to smuggle blob verdicts into the pool walk, with one _validatedSpec: the release spec every pooled transaction has been validated against. Only the head walk publishes it, under the write lock that excludes inserts; accepting a transaction may only clear it. Between two head updates the value therefore moves at most from a spec to null, so a lock-free reader either sees a spec covering the whole pool or nothing. Capture the chain head spec once per submission in TxFilteringState so the incoming filters and the pool agree on which rules a transaction was accepted under even when the head moves while it travels the pipeline. Without that, a transaction could be validated under one spec and recorded under another. Hand the producer the pending transactions and that mark as one PendingTransactionsView taken under the pool lock. Asking and snapshotting separately cannot be made safe by ordering: asking first lets a divergent transaction slip in afterwards, snapshotting first lets the head walk evict an invalid transaction the producer already holds. Also document why light blob transactions are resolved before the walk rather than during it: UpdatePool holds the blob pool lock, which throws on reentry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pool takes SpecChangeTxValidator instead, leaving the keyed registration, its key, and the API property it backed without a consumer. The HeadTxValidator class stays; the producer filter pipeline still uses it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LukaszRozmej
left a comment
There was a problem hiding this comment.
Review of the txpool fork-boundary revalidation.
The core _validatedSpec design holds up: I traced SubmitTx/AddCore (read lock) against UpdateBuckets (write lock), the snapshot-then-flag ordering in GetPendingForProduction, the reorg re-add path and the throw-mid-walk retry path. The mark is only ever cleared outside the write lock, UpdatePool visits every bucket, buckets dumped early are fully removed, and TxValidator's per-type composites are a superset of SpecChangeTxValidator on Ethereum, so a tx admitted under spec S really is S-validated. The ChainHeadSpecProvider switch to ForkActivation equality is behaviour-preserving (ISpecProvider.GetSpec(header) is exactly GetSpec(new ForkActivation(Number, Timestamp))), every spec provider in the repo returns cached instances so ReferenceEquals is sound, and the new IntrinsicGasTxValidator LightTransaction guard doesn't break any existing IsWellFormed caller.
Seven comments inline: one significant startup/fork-boundary cost, one blob-selection behaviour change that can cost the fork block its blobs, and five smaller correctness/consistency items.
Changes
TxPoolconstruction path.Related to #12599.
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Notes on testing
Documentation
Requires documentation update
Requires explanation in Release Notes