Skip to content

feat(executor): EIP-1559 gas oracle + concurrent nonce manager#43

Merged
obchain merged 4 commits intomainfrom
feat/18-gas-and-nonce
Apr 24, 2026
Merged

feat(executor): EIP-1559 gas oracle + concurrent nonce manager#43
obchain merged 4 commits intomainfrom
feat/18-gas-and-nonce

Conversation

@obchain
Copy link
Copy Markdown
Owner

@obchain obchain commented Apr 21, 2026

Two bundled additions to charon-executor. Both sit in the pre-broadcast path and slot in between the tx builder and the (future) submit layer.

gas.rsGasOracle:

  • fetch_params(provider) — pulls baseFeePerGas from the latest block header, computes maxFeePerGas = base × 1.25, attaches the per-chain priority fee. Returns Ok(None) (skip the tx) when the resulting max_fee exceeds bot.max_gas_gwei
  • estimate_gas_units(provider, tx)eth_estimateGas wrapper
  • gas_cost_usd_cents(units, max_fee, native_price, native_decimals) — converts wei cost into integer USD cents using a Chainlink reading; U256 arithmetic throughout with saturating fallbacks
  • Three unit tests: BNB-priced happy path, zero-units early-out, overflow saturation
  • New ChainConfig.priority_fee_gwei field (#[serde(default = 1)]); BSC entry set to 1

nonce.rsNonceManager:

  • init(provider, signer) async — pulls eth_getTransactionCount on startup
  • next() — atomic fetch_add claim, sequential under any concurrency
  • current() — peek without consuming (logging)
  • resync(provider) — re-fetch on-chain nonce after a failed tx or long idle; drops gaps left by stuck/replaced transactions
  • Optimistic by default: bumps locally so multiple in-flight txs hold contiguous nonces without an extra round-trip
  • Two unit tests: sequential issue from single thread; 32 threads × 100 issues each asserts no duplicates and final counter matches start + total

Depends on feat/17-cli-e2e-pipeline.

obchain added 2 commits April 21, 2026 18:22
New `gas.rs`. Three responsibilities:

- `GasOracle::fetch_params(provider)` — pulls `baseFeePerGas` from the
  latest block header, computes `maxFeePerGas = base × 1.25`, attaches
  the per-chain priority fee. Returns `Ok(None)` (skip the tx) when
  the resulting `max_fee` exceeds `bot.max_gas_gwei`.
- `GasOracle::estimate_gas_units(provider, tx)` — `eth_estimateGas`
  wrapper.
- `gas_cost_usd_cents(units, max_fee, native_price, native_decimals)`
  — converts wei cost into integer USD cents using a Chainlink
  reading. Will replace `PLACEHOLDER_GAS_USD_CENTS` in CLI once
  nonce + submit land alongside.

Config:
- `ChainConfig.priority_fee_gwei` (`#[serde(default = 1)]`) — added
  per-chain because BSC's validator-friendly tip differs from L2s
- `config/default.toml` BSC entry now sets `priority_fee_gwei = 1`

Math:
- 25 % cushion over base fee matches PRD §5c
- USD-cents conversion uses U256 throughout to avoid u128 overflow
  on large-fee × large-price products; saturates to `u64::MAX` if
  asked for an absurd cost rather than panicking
- Three unit tests cover the BNB-priced happy path, zero-units
  early-out, and overflow saturation
`NonceManager` for one (chain × signer) pair.

- `init(provider, signer)` async — pulls `eth_getTransactionCount` on
  startup
- `next()` — atomic `fetch_add` claim, sequential under any
  concurrency
- `current()` — peek without consuming (logging)
- `resync(provider)` — re-fetch on-chain nonce after a failed tx or
  long idle, drops gaps left by stuck/replaced transactions
- Optimistic by default: bumps locally on every issue so multiple
  in-flight txs hold contiguous nonces without an extra round-trip

Two unit tests:
- sequential issue from a single thread
- 32 threads × 100 issues each, asserts no duplicates and final
  counter matches start + total
This was referenced Apr 22, 2026
obchain added a commit that referenced this pull request Apr 23, 2026
- Redact PrivateKeySigner in TxBuilder Debug to avoid leaking the
  k256 scalar into logs (#158).
- Pull nonce from the pending block tag so queued tx don't collide
  with newly built ones; TODO flags NonceManager in PR #43 (#159).
- Reject build_tx calls where priority tip exceeds max fee per gas
  before any RPC round-trip (#160).
- Require an explicit gas_limit on Simulator::simulate so the
  simulation burns the same gas ceiling as the real broadcast (#161).
- Add Simulator::from_builder so the simulated sender is always the
  builder's hot wallet (onlyOwner alignment), plus debug_assert on
  non-zero sender (#162).
- Replace anyhow on the public lib surface with BuilderError and
  SimulationError thiserror enums (#163).
- Pin PROTOCOL_VENUS to the Solidity constant with an ABI-level unit
  test and line-number comment referencing CharonLiquidator.sol:49
  (#164).
- Adopt workspace lints (unsafe_code=forbid, arithmetic_side_effects,
  cast_possible_truncation, unwrap_used) and opt charon-executor in
  via [lints] workspace = true (#165).
- Decode revert payload in Simulator::simulate into a 4-byte selector
  plus full hex body so logs are greppable cross-protocol (#166).
- Add #[ignore]d fork tests covering the happy path and the
  onlyOwner adversarial path to pin the sim gate's safety invariant
  (#167).

Closes #158
Closes #159
Closes #160
Closes #161
Closes #162
Closes #163
Closes #164
Closes #165
Closes #166
Closes #167
Gas oracle (gas.rs):
- fix ceiling unit mismatch: compare max_fee_per_gas against
  max_gas_gwei * 1e9 (wei), not gwei (#179).
- fall back to eth_gasPrice when header lacks baseFeePerGas so a
  flaky RPC or pre-EIP-1559 chain does not block pricing (#180).
- switch max_fee formula to canonical EIP-1559 `2 * base + priority`
  so the tx survives a single-block base-fee doubling (#181).
- apply a 20% safety buffer on eth_estimateGas output (#182).
- fix gas_cost_usd_cents to include the Chainlink 8-decimal factor
  explicitly and expose CHAINLINK_DECIMALS (#183).
- convert priority_fee_gwei -> wei via checked_mul (#186).
- introduce GasError (thiserror) and replace Ok(None) skip-signal
  with a typed GasDecision::{Proceed, SkipCeilingExceeded} enum
  (#187, #188).
- add per-block cache keyed on the caller-supplied block number to
  avoid duplicate fee-market lookups inside one tick (#189).
- add ignored live-BSC integration test for fetch_params (#191).

Nonce manager (nonce.rs):
- query eth_getTransactionCount on the `pending` block tag in both
  init and resync to count in-flight txs (#184).
- add high-water-mark guard so resync never rolls next backwards
  past an already-issued nonce (#185).
- introduce NonceError (thiserror) (#187).

Config:
- mark ChainConfig `#[non_exhaustive]` so new per-chain knobs stay
  non-breaking for downstream crates (#190).

Deps:
- add thiserror to the workspace and to charon-executor.
@obchain obchain changed the base branch from feat/17-cli-e2e-pipeline to main April 24, 2026 11:52
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	crates/charon-core/src/config.rs
#	crates/charon-executor/src/lib.rs
@obchain obchain merged commit 8f8221f into main Apr 24, 2026
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.

1 participant