Skip to content

fix(journal): retry the gas-price read in prepare_payment - #18

Merged
jacderida merged 1 commit into
WithAutonomi:mainfrom
jacderida:chrisoneil/v2-1288-evmlib-retry-the-gas-price-read-in-the-journal-payment-path
Sep 23, 2026
Merged

jacderida merged 1 commit into
WithAutonomi:mainfrom
jacderida:chrisoneil/v2-1288-evmlib-retry-the-gas-price-read-in-the-journal-payment-path

Conversation

@jacderida

Copy link
Copy Markdown
Member

Summary

The journal payment path (prepare_payment) routed every RPC read through the retrying rpc(...) wrapper except the EIP-1559 fee estimate, which still called the single-shot retry::get_eip1559_fees. One 429 or -32000 context deadline exceeded from a public RPC therefore failed the upload outright with Could not get current gas price: … (seen on DEV-03 runs 591 and 593). The legacy send_transaction_with_retries path, used by the released client, retries this three times, so ant-client main had regressed.

  • retry.rs: get_eip1559_fees is split into needs_fee_estimate (false only for Unlimited) and a pure apply_fee_policy holding the unchanged per-mode logic. get_eip1559_fees remains a thin single-estimate wrapper, so the legacy path behaves exactly as before and does not nest retries.
  • journal.rs: new journal_fee_read reads the estimate via rpc("gas price", None, …) and then applies the policy. Only the RPC read is retried; a definitive GasPriceAboveLimit returns at once. An exhausted retry still surfaces as Could not get current gas price: ….

Linear issue

Closes V2-1288 — Linear issue

Risk tier

  • T0 — docs / tooling / CI / pure UX-output. Repo CI only.
  • T1 — client-only, no network-facing behavior change. CI + prod compat smoke.
  • T2 — node/client logic with behavioral surface, no protocol/format/economics change. Dev testnet + ADR.
  • T3 — protocol / storage format / payments / routing. T2 evidence + adversarial testing.

Proposed for human review: adds retries to one read-only RPC call on the client payment path; the fee policy, the signed transaction and the payment semantics are unchanged.

Compatibility

  • Wire: none
  • Storage: none
  • API: none (all changed items are pub(crate))

Semver impact

  • breaking
  • feature
  • fix

Test evidence

  • New unit tests for apply_fee_policy (Auto passthrough; LimitedAuto under and over the limit; Custom below and above the estimate; Unlimited → None) and needs_fee_estimate.
  • New journal_fee_read tests on alloy's mocked transport (Asserter + connect_mocked_client): a transient -32000 context deadline exceeded followed by a valid eth_feeHistory succeeds; a fee over a LimitedAuto limit fails with GasPriceAboveLimit without backoff.
  • cargo test --lib: 30 passed.
  • cargo test --lib --test cryptography --no-default-features --features rpc,external-signer: 22 + 4 passed.
  • cargo clippy --all-targets -- -D warnings, cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --check: passed.
  • cargo check --lib --no-default-features --target wasm32-unknown-unknown and … --features rpc,external-signer --target wasm32-unknown-unknown: passed.
  • The Anvil/live-RPC integration suite is left to CI (cargo test --release).

New dependency

none

ADR

n/a

Mitigation / rollback

Revert this commit and re-pin ant-protocol / ant-client / ant-node to the previous evmlib rev; the change only adds retries to a read.

🤖 Generated with Claude Code

The journal hardening routed every read in `prepare_payment` through the
retrying `rpc(...)` wrapper except the EIP-1559 fee estimate, which still
went through the single-shot `get_eip1559_fees`. One 429 or `-32000 context
deadline exceeded` from a public RPC therefore failed the upload with
"Could not get current gas price", a regression against the legacy
`send_transaction_with_retries` path.

Split `get_eip1559_fees` into `needs_fee_estimate` and a pure
`apply_fee_policy`, keeping the per-mode logic unchanged. The journal now
reads the estimate through `rpc("gas price", ...)` and applies the policy
afterwards, so only the RPC read is retried and a `GasPriceAboveLimit`
still returns at once. `get_eip1559_fees` stays a single-estimate wrapper
for the legacy path, which already retries at the outer level.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@dirvine dirvine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: no material blockers

Reviewed WithAutonomi/evmlib at 68d3bcbdf0319e1d853606c9d2f99dccf807a1c6 against main; rechecked that this remains the open PR head and all GitHub checks are successful.

The change puts the fee-estimate read inside the existing journal RPC retry wrapper, while keeping apply_fee_policy outside it. GasPriceAboveLimit therefore returns immediately after a successful estimate; all four fee modes preserve the previous behaviour. The legacy send path still performs one estimate per outer send attempt, so this does not introduce nested retries. Signing, nonce selection, broadcast and receipt handling are unchanged.

Verified locally

  • cargo test — passed, including the integration suites.
  • cargo test --lib --test cryptography --no-default-features --features rpc,external-signer — 22 library and 4 cryptography tests passed.
  • cargo clippy --all-targets --all-features -- -D warnings — passed.
  • cargo fmt --check and git diff --check — passed.
  • Both WASM library checks, without default features and with rpc,external-signer — passed.

Independent review and caveats

GLM-5.2 and Codex independently found no introduced blockers, consistent with the fee-policy review and my code/test verification. One reviewer flagged unfiltered RPC retries and their latency as blocking. I do not consider that a blocker here: this deliberately reuses the existing read-only preparation backoff, not the shorter observation-window backoff, and the policy rejection remains outside retries. The operational trade-off is real: a persistently failing fee estimate incurs 56 seconds of retry sleeps, plus RPC time; permanent RPC errors are also retried. A retry-exhaustion/error-prefix regression test would be a useful non-blocking addition.

The DS4 review attempt timed out and is not counted as an opinion. This is not a claim of unanimous six-seat approval.

No source changes made and no merge performed. This review does not replace any required human release/acceptance sign-off.

@dirvine dirvine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at Chris’s explicit request following the completed review: #18 (review) . Rechecked that the head is unchanged and all CI checks pass. No material blockers; the documented retry-latency caveat remains non-blocking. No merge performed.

@jacderida
jacderida merged commit 8ad5ec0 into WithAutonomi:main Sep 23, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants