fix(l1): make the batch poster re-estimate and never escalate fees - #35
Open
GCdePaula wants to merge 2 commits into
Open
fix(l1): make the batch poster re-estimate and never escalate fees#35GCdePaula wants to merge 2 commits into
GCdePaula wants to merge 2 commits into
Conversation
Return the poster to stateless re-estimation: every tick re-broadcasts each pending batch at that tick's fresh market estimate and keeps no per-nonce fee state. Drop the in-flight floor map, the fee ceiling and its absolute floor, the escape valve, the hold state with its confirmation-cadence tick, and the insufficient-funds matcher. Keep the policy-independent fixes from the review rounds: the nonce-free padded gas estimate at an explicit Latest (Anvil rejects an estimate at an already-pending nonce before the node ever sees the re-broadcast) and the shared pad helper. Move the flusher's replacement bump back beside its only caller, keeping the symmetric tip-and-cap fix (the old tip ×2 gave tip > cap on zero-base chains, so no flush no-op was ever valid there), and price no-ops with a one-shot 3× headroom over the fresh estimate: a 1.1× bump of today's market loses to a poster tx priced when the market was any higher, and both pre-#34 main and the round-4 ceiling had headroom this branch had lost. Classify "replacement transaction underpriced" / "already known" as what they mean — the mempool already holds our tx at a price the market has not beaten — and report a quiet `Waiting` outcome the worker sleeps on instead of an error; a confirmation timeout reports the same, which closes the review register's pacing finding in full. Track how long each attempted nonce has been unresolved and warn past PENDING_WAIT_WARN_AFTER, so the decision stays falsifiable against production. The poster's module doc records the gap this leaves, why its expected cost is minutes against a safe three-hour fallback, and the four escalation designs that were built and refuted on #34, so the next reader who re-finds the gap meets the reasoning rather than a rule.
Replace the poster policy essay with a concise shared document. Distinguish safe shutdown from recovery completion, document independently moving fees, and characterize the flusher limitation with a focused test.
GCdePaula
force-pushed
the
fix/poster-stateless-fee-policy
branch
from
September 7, 2026 13:34
7aa16e3 to
f61a7e8
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Steps the batch poster back to stateless re-estimation and documents precisely why, so #34 lands as: the real fixes the review rounds found, a quiet handling of the mempool's "already holds your tx" replies, and a written record of the gap we knowingly leave — instead of a fee-escalation policy.
Targets
fix/poster-replacement-fee-bumpdeliberately, so the branch keeps its history: rounds 1–4 built and adversarially reviewed four escalation designs, and each closed the two-component replacement gap while opening a worse hole (an invalidtip > capwedge; a silent batch drop via skip-and-watch; a wallet exhausted via compounding floors against geth's cumulative per-sender reservation; a recovery crash-loop plus a 5 s already-known/hold fixed point under the ceiling). Those four commits are the evidence behind the note this PR adds, which is why they stay in the history rather than being squashed away.Stepping back: the gap needs the base fee to more than double after a broadcast while the 10-block tip median stays flat through those same full blocks — two conditions that pull against each other — so its expected cost is minutes of waiting, bounded by the danger detector (a safe, designed fallback at 900 blocks; the protocol requirement is 1200). None of the machinery earned its failure modes.
What changes
fees_for_nonce, the ceiling + absolute floor + escape valve, the hold state and its confirmation-cadence tick outcome, the insufficient-funds matcher, and the duplicated cadence config (BatchSubmitterConfigis back toidle_poll_interval_ms).Lateston every send — Anvil rejects an estimate at an already-pending nonce with "nonce too low" before the node ever sees the re-broadcast (@jplgarcia's repro; the pin now lives insubmit_batches_waits_when_mempool_already_holds_the_tx, whose doc comment says so); andpad_gas_estimateas a shared helper.bumped_replacement_feesmoved back next to its only caller. It keeps the symmetric tip-and-cap fix from round 2 because that fixed a real pre-existing flusher bug, unrelated to poster policy: the old tip ×2 / cap ×1.1 producedtip > capwhenever the estimate's cap sat near its tip — the normal shape on a zero-base-fee chain — so every no-op was an invalid tx and recovery could not complete on devnet. No-ops are now priced with a one-shot 3× headroom over the fresh estimate (NOOP_FEE_HEADROOM): a 1.1× bump of today's market loses to a poster tx priced when the market was any higher, and both pre-fix(l1): bump EIP-1559 fees on same-nonce poster retries #34main(tip ×2) and the round-4 ceiling (cap ×3) had headroom this branch had lost. One-shot and stateless on a 21k-gas transfer, so it cannot compound. The honest residual-gap comment is restored and corrected.SubmitBatchesOutcome::Waiting { broadcast }and keeps going, so new payloads behind a refused nonce still get their fresh sends (without watching them behind a refused head); a confirmation timeout reportsWaitingtoo. The worker sleepsidle_poll_intervaland re-estimates. This fully closes the review register's "confirmation-timeout defeats pacing" finding — every non-productive path now sleeps.PENDING_WAIT_WARN_AFTER(5 min), then once per interval. The contract: if production emits that line, bring the excerpt and the L1 block range to the register — not a floor.sequencer/src/l1/submitter/poster.rs— the gap's mechanics, the frequency × duration argument, the four refuted designs, what we do instead, and the revisit trigger.AGENTS.mdpoints at it from the submitter bullet;docs/recovery/README.mdrecords the flusher's sharpened residual. Register entries (settled decision, refuted proposal, closure of open finding 1) go to the ledger branch, wheredocs/review/lives.Net against the branch head: about −600 lines.