Skip to content

Merge v1.1.0, bump reth to 2.3.0, evm-alloy to 0.36.0-fh#12

Merged
sduchesneau merged 144 commits into
firehose/1.xfrom
bump_1_1_0
Jun 15, 2026
Merged

Merge v1.1.0, bump reth to 2.3.0, evm-alloy to 0.36.0-fh#12
sduchesneau merged 144 commits into
firehose/1.xfrom
bump_1_1_0

Conversation

@sduchesneau

@sduchesneau sduchesneau commented Jun 15, 2026

Copy link
Copy Markdown

https://github.com/base/node/releases/tag/v1.1.0 + firehose instrumentation

Will build it and use test-mode over base-mainnet to validate behavior

danyalprout and others added 30 commits May 27, 2026 22:32
* feat(proof): add L1 header prefetcher with shared cache

Introduces L1HeaderPrefetcher and a shared L1HeaderCache that proactively
fetches nearby L1 headers by number (lookbehind) and stores each header
under its actual hash so reorged or non-ancestor blocks cannot satisfy a
different guest request. The cache is threaded through ProverService,
Host, and OnlineHostBackend so it is shared across proof requests.

* fix(proof): address l1 header prefetch review

* fix(proof): validate prefetched L1 headers

* test(proof): cover L1 header cache eviction

* address pr feedback

* address pr feedback
* feat(devnet): support zk prover cluster mode

Load non-secret cluster settings from local YAML so devnet can run the ZK prover against SP1 cluster while keeping dry-run as the default.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(devnet): harden cluster config rendering

Reject unsupported YAML constructs before rendering compose env files and avoid unsafe Just interpolation in cluster-mode errors.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(devnet): avoid raw just argument expansion

Route devnet compose selection through controlled profile keys and only strip matched YAML quotes when rendering cluster env files.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(devnet): handle YAML mapping comments

Treat comment-only YAML values as empty mappings so denied nested keys stay scoped correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(devnet): harden cluster env passthrough

Pass RUST_LOG into the ZK prover container and reject Compose interpolation tokens in rendered cluster env values.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(devnet): quote zk entrypoint arguments

Ensure public devnet up recipes shell-quote zk mode before invoking the private implementation.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(devnet): strip YAML inline comments

Handle standard unquoted inline comments before rendering cluster env values.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…P-160) (base#2978)

* fix(b20_security): use checked_sub for total supply in burn paths (BOP-160)

security_redeem_inner and batch_burn were using saturating_sub when
decrementing total supply, which would silently clamp to zero on underflow
instead of reverting. Use checked_sub with an under_overflow panic to match
the behavior of the base B20 burnable implementation.

* test(b20_security): regression tests for supply underflow in burn paths (BOP-160)

Add unit tests verifying that checked_sub in security_redeem_burn and
batch_burn returns an arithmetic under/overflow panic when total supply
underflows, rather than silently clamping to zero as saturating_sub did.
…ase#2980)

* fix(b20_security): use checked_mul for share calculations (BOP-161)

to_shares and security_redeem_inner were using saturating_mul when
computing balance * sharesToTokensRatio, which silently caps at U256::MAX
on overflow and then divides by WAD to produce a wildly incorrect share
count. Use checked_mul with an under_overflow panic to match Solidity
semantics and revert on overflow.

* fix(b20_security): remove section header comment from tests

* fix(b20_security): rustfmt and doc-markdown CI fixes (BOP-161)

* fix(b20_security): rustfmt formatting for checked_mul shares line
Adds --boundless-offer-lock-timeout-secs to the prover-registrar CLI,
threading the value through BoundlessConfig and BoundlessProver into
OfferParams::lock_timeout. Mirrors the wiring introduced in base#2949 for
offer pricing fields.

lockTimeout bounds both the window in which any prover may lock the
request and the deadline by which the locking prover must deliver the
proof before forfeiting its stake bond and the request opens to
permissionless secondary fulfillment. With Offer.timeout left unset,
the Boundless SDK derives Offer.timeout = 2 * lockTimeout, so a
600 s lock timeout yields a 1200 s total on-chain request lifetime.

Also bumps --boundless-timeout-secs default from 600 to 1260. That
flag is a purely client-side poll budget on wait_for_request_fulfillment
and must exceed the on-chain Offer.timeout plus headroom for clock
skew, RPC latency, and indexer lag past the on-chain Fulfilled event.
1260 s covers a 600 s lockTimeout deployment (1200 s SDK-derived
on-chain timeout) with ~60 s of headroom.
…spawn-and-reap pipeline (base#2799)

* feat(registrar): add proof-task lifecycle metrics and tokio rt/sync features

Adds four metrics (spawned/cancelled/completed/pending) to observe the
upcoming spawn-and-reap run loop where discovery is decoupled from
proof generation. Enables tokio rt+sync features required for JoinSet
and CancellationToken child-token creation in the runtime crate.

The cancelled counter records cancel *intent* (vanished/ineligible
instance or shutdown); the task still terminates as a completed
outcome and is double-counted there.

* feat(registrar): decouple discovery from proof generation via spawn-and-reap loop

Replaces the synchronous `step()`-driven `run()` with a JoinSet-backed
pipeline so newly-discovered enclaves are reconciled within one poll
interval instead of being blocked behind in-flight Boundless proofs for
older instances (production `boundless_timeout_secs` is ~70 min).

Per-cycle flow:

1. `reap_finished_tasks` drains any task that finished since last cycle.
2. `discover_and_resolve` produces a `DiscoveryResolution` snapshot
   (registerable instances + per-instance attestations, active signers
   for orphan protection, `ok_to_dereg` baking in the majority guard +
   cancellation policy).
3. `reconcile_proof_tasks` cancels in-flight tasks for signers that
   are no longer registerable and spawns fresh tasks for new ones.
4. `run_orphan_dereg` runs the orphan pass when `ok_to_dereg` is set.
5. Sleep `poll_interval` or wake on cancel.

Spawned proof tasks use a per-task `CancellationToken` child of the
driver's global cancel — so a process shutdown or a reconcile-time
"signer no longer registerable" decision both reach the proof task
through the same checkpoint. `try_register` gains a `signer_cancel`
parameter and wraps the long-running `generate_proof_for_signer` in a
biased `select!` against that token. The `tx_manager.send()` call is
never wrapped in a `select!` — dropping send() after nonce acquisition
but before broadcast leaves a permanent nonce gap (see
`NonceGuard::Drop`).

Shutdown drains via cooperative cancel + `abort_all` backstop, then
`join_next_with_id` so each terminal outcome flows through
`apply_join_outcome` (keeps `pending` and the proof-task metrics
consistent).

`step()` and `process_instance()` are retained as `#[cfg(test)]`
helpers; the synchronous-cycle test suite (calldata accounting,
majority guard, draining/unhealthy) continues to exercise the same
underlying `try_register` / `deregister_orphans` code paths via them.

Adds `+ 'static` to the impl generics so `Arc<Self>` clones can be
spawned. CLI: wraps construction in `Arc::new(...)?` so `run` can
consume the handle.

* test(registrar): add spawn-and-reap pipeline tests with gated proof harness

Adds 17 new tests (138 total, up from 121) covering the
decoupled discovery/proof pipeline introduced in the previous
commit:

- 6 `reconcile_proof_tasks` parametrized cases asserting
  spawn/cancel correctness across pre-existing + resolved
  signer combinations, plus an idempotency test.
- 3 `reap_finished_tasks` cases (ok/err outcomes plus
  in-flight survival) verifying `pending` eviction matches
  JoinSet drain state.
- 8 end-to-end `run()` loop tests driven by a new
  GatedProofProvider that latches a CancellationToken so the
  test deterministically observes parked proof tasks before
  releasing them.

Test infrastructure (also in this commit) introduces:
- `GatedProofState`/`InFlightGuard`/`GatedProofProvider` —
  cancel-safe Notify replacement that eliminates the
  late-arrival wakeup race.
- `MutableDiscovery` — discovery whose instance list can
  be swapped mid-run to simulate ASG scale-up/down.
- `GatedRunHarness` — drives `Arc::clone(&driver).run(...)`
  on a JoinHandle with a 25ms poll interval and exposes
  `spawn_run`/handles.
- `wait_for` / `wait_for_run_to_stop` polling helpers
  bounded by a 5s timeout, plus calldata-frame selector
  counters (`count_register_calls` /
  `count_deregister_calls`).

Tests + infra are committed together so the helpers are used
on first introduction, avoiding any `#[allow(dead_code)]`
escape hatch (per AGENTS.md).

* docs(registrar): fix unresolved intra-doc links across crate

Resolves 13 `cargo doc --document-private-items` warnings:

- README.md: drop the non-existent `SigningConfig` link
  (signing is delegated to the `base-tx-manager` crate) and
  reframe `AttestationProofProvider` as the external trait
  from `base-proof-tee-nitro-attestation-prover` it actually
  is, since it is not re-exported from this crate.
- `traits.rs`: `AwsTargetGroupDiscovery` lives in the
  `base-proof-tee-discovery` crate, not in this one — drop
  the broken `[`AwsTargetGroupDiscovery`]` link and name
  it in prose with the owning crate.
- `driver.rs`: the per-cycle helpers (`reap_finished_tasks`,
  `discover_and_resolve`, `reconcile_proof_tasks`,
  `run_orphan_dereg`, `apply_join_outcome`, `resolve_instance`)
  remain private implementation details of the spawn-and-reap
  loop, so swap their intra-doc `[`Self::...`]` references on
  public items (`PendingTask`, `DiscoveryResolution`,
  `RegistrationDriver::run`) for plain backtick code spans.
  Also drops two stale `[`Self::step`]` /
  `[`Self::process_instance`]` links that broke when those
  helpers were narrowed to `#[cfg(test)]` in the previous
  commit.

All warnings cleared on both feature configurations of
`cargo doc --no-deps [--all-features] [--document-private-items]`.

* test(registrar): DRY pipeline tests, parametrize over fan-out/scale-up

Audit-driven refactor of the spawn-and-reap test suite:

- Extract repeated boilerplate into named helpers:
  `drain_test_tasks` (3× copy-pasted teardown),
  `single_healthy_harness` / `multi_healthy_harness` (5×
  copy-pasted (EP1, HARDHAT_KEY_0) wiring), and
  `reap_until_pending_empty` (replaces a magic `for _ in 0..16
  { ... sleep(1ms) }` loop with a `GATED_WAIT_TIMEOUT`-bounded
  poll built on the constants already defined for the same
  purpose). Move the per-cycle shutdown wait onto
  `GatedRunHarness::shutdown` so every pipeline test ends with
  exactly one shutdown call.
- Pin every `PendingTask` instance ID built by tests to a new
  `TEST_PENDING_INSTANCE_ID` const, eliminating ad-hoc
  "i-test" / "i-x" / `format!("i-{ep}")` magic strings.
- Replace the long
  `RegistrationDriver::<MutableDiscovery, GatedProofProvider,
  ...>::reap_finished_tasks` qualifier with the existing
  `RunDriver` type alias.
- Parametrize three tests that were previously hard-coded to a
  single shape:
  * `run_spawns_one_task_per_enclave...` over
    `num_instances ∈ {1, 2, 3, 4}`.
  * `run_cancels_in_flight_tasks_when_instances_vanish_mid_proof`
    (renamed from `..._when_instance_vanishes_mid_proof`) over
    (initial_count, drop_indices) pairs covering vanish-first /
    vanish-last in a 2-instance setup, and two cases of
    vanish-two-of-three with the kept index at either end.
  * `run_continues_discovery_while_proof_tasks_are_in_flight`
    over (initial_count → final_count): 1→2, 1→3, 2→4 — proving
    the discovery-unblocked guarantee survives multi-instance
    scale-ups, not just +1.
  This required widening `multi_healthy_harness` to seed the
  signer client with every Hardhat key (not just the initial
  subset) so mid-run scale-ups can resolve public keys for
  endpoints that weren't in the initial discovery snapshot.
- Fold the standalone
  `run_does_not_generate_proof_when_signer_already_registered`
  test into the new
  `run_does_not_register_when` rstest as a third
  `NoRegisterReason::SignerAlreadyRegistered` case, alongside
  the existing `InstanceDraining` / `InstanceUnhealthy` cases
  it already shared the invariant with ("no proof, no tx").
- Add `reap_finished_tasks_is_noop_when_pending_is_empty` as a
  sanity test the production loop relies on but that wasn't
  previously asserted.

`cargo test -p base-proof-tee-registrar --lib [--all-features]`:
147/147 pass (was 138 — net +9 from rstest expansion).
`cargo clippy ... -- -D warnings` and
`cargo doc ... --document-private-items` clean on both feature
configurations.

* test(registrar): cover panic / proof-failure / mixed-cycle pipeline arms

Closes three gaps identified in the test-quality audit of the
spawn-and-reap suite:

1. `apply_join_outcome_drops_pending_entry_when_task_panics`
   exercises the `Err(JoinError)` arm of `apply_join_outcome`,
   previously uncovered: a spawned proof task panics, and
   `reap_finished_tasks` (which the production loop calls
   every cycle) must still evict the corresponding
   `PendingTask` via `JoinError::id()` so the per-task cancel
   handle drops and the proof-tasks-completed metric still
   fires. Drives the panic through the full reap path rather
   than calling `apply_join_outcome` directly so the routing
   is also covered.

2. `run_isolates_proof_failure_and_continues_pipeline_for_other_signers`
   covers the proof-error path through `try_register` from
   the perspective of the run loop. Extends
   `GatedProofProvider` with a per-signer
   `fail_for_signers([...])` knob that overrides
   `generate_proof_for_signer` to return a synthetic
   `ProverError::Boundless` before reaching the gate, so the
   failing signer's task never parks (asserting in_flight ==
   1) and never reaches the `tx_manager.send()` call. Since
   `registerSigner` calldata frames cannot originate from the
   failing signer's task under this routing,
   `count_register_calls` is unambiguously attributable to
   the surviving signer — this avoids needing a registry mock
   that observes registrations (`MockRegistry` is static).

3. `run_handles_orphan_dereg_and_active_registration_in_same_cycle`
   covers a single cycle that fans out to both the orphan
   deregistration pass and the spawn-and-reap registration
   pass: ORPHAN_A is preloaded into the registry and EP1 is
   discovered as healthy + unregistered. The dereg pass runs
   in the foreground (no proof gate) and must land
   immediately; EP1's proof parks in the gate and registers
   when released. Asserts exactly one of each tx selector.

Also adds an explanatory comment block at the end of the
pipeline-tests section documenting why real-cert-chain
fixtures (the `test_utils` canonical chain) are intentionally
not used in these orchestration tests — CRL pre-checks are
disabled in `default_config` and `MockRegistry` does not
verify calldata, so swapping in real attestation bytes would
not exercise additional production paths. Real cert parsing
is covered separately by the OnchainRevocationCheck suite
that follows.

`cargo test -p base-proof-tee-registrar --lib [--all-features]`:
150/150 pass (was 147; net +3 from the three new tests).
`cargo clippy ... -- -D warnings` and
`cargo doc ... --document-private-items` clean on both
feature configurations.

* fix(prover-registrar): close nonce-gap hazards on shutdown / discovery cancel

`drain_proof_tasks` previously called `JoinSet::abort_all` as a
"backstop" on top of cooperative cancellation. Abort drops tasks at
arbitrary await points — including inside `tx_manager.send()` after a
`NonceGuard` has been acquired but before broadcast, which leaves a
permanent nonce gap (`NonceGuard::Drop` does not roll back). The careful
cancel-checkpoint pattern in `try_register` that specifically avoids
wrapping `send()` in a `select!` was being defeated by abort during
shutdown.

`discover_and_resolve` had a symmetric hazard: its
`tokio::select! { cancelled => break, futs.next() => ... }` dropped the
`buffer_unordered` stream on cancel, including any `resolve_instance`
future currently inside `check_and_revoke_crls` ->
`submit_revoke_cert` -> `tx_manager.send()`.

Fix:

- Remove `abort_all` from `drain_proof_tasks`; rely solely on
  cooperative cancellation. Shutdown latency is now bounded by the
  slowest in-flight `send()`, which is exactly what we want.
- Remove the cancel-select around `futs.next()` in
  `discover_and_resolve`; drain the stream to natural completion.
- Add cooperative `self.config.cancel.is_cancelled()` checks at the
  start of `resolve_instance` and before the CRL pass, so cancellation
  short-circuits *new* work but never abandons an in-flight send.
- Document the cancellation contract on `resolve_instance`,
  `discover_and_resolve`, and `drain_proof_tasks`.
- Fix the `ok_to_dereg` doc on `DiscoveryResolution` to acknowledge
  the cancellation case (was: "always true when total_count is zero";
  actually false on cancel).

* fix(prover-registrar): bound proof concurrency, drop duplicate-signer race, thread cancel through provider trait

Four HIGH-severity findings from the audit:

**#3 Proof concurrency was unbounded.** `max_concurrency` only bounded
the `buffer_unordered` stream in `discover_and_resolve`. Once
resolution completed, `reconcile_proof_tasks` spawned one proof task
per registerable signer with no global cap, so a large ASG scale-up
could fan out an arbitrary number of concurrent Boundless requests and
drain the prover wallet. Added an `Arc<Semaphore>` sized from
`max_concurrency` and acquired inside `run_proof_task` (not at spawn
time, so reconcile stays synchronous), reusing the existing config
parameter to bound *both* phases.

**#4 Duplicate-signer race in spawn pass.** `in_flight` was built once
from `pending` and only read inside the spawn loop, so a signer
appearing in two registerable entries within the same cycle (misconfig
or discovery glitch) spawned duplicate proof tasks. Fixed by switching
to a mutable `HashSet` and `insert()`-as-we-go.

**#5 Provider trait did not encode cancel-safety.** `try_register`
wrapped `generate_proof_for_signer` in a `select!` and assumed the
returned future was droppable, but the trait made no such guarantee:
`DirectProver::generate_proof` does `spawn_blocking` (no abort
signal) and `BoundlessProver` may have already submitted an on-chain
request. Added a `CancellationToken` parameter to both
`generate_proof` and `generate_proof_for_signer` and documented the
contract at trait level: cancellation is cooperative, implementors
must tolerate drop, and any side effects that survive drop must be
idempotent or recoverable. `DirectProver` and `BoundlessProver` both
honor the token at phase boundaries; the recovery probe loop in
`BoundlessProver::generate_proof_for_signer` now short-circuits
between attempts.

**#6 Shutdown-time metric drift.** The retry-delay cancel branch in
`try_register` returned `Err` despite the `PendingTask` doc claiming
cancellation always returns `Ok(())`, which made every retry-during-
shutdown bump `processing_errors_total`. Switched to `Ok(())` with
the underlying tx error logged for context. Also surfaced shutdown
cancellations in `proof_tasks_cancelled` (only when not already
cancelled by reconcile, to avoid double counting) so the metric
matches its documented "includes shutdown" semantics.

Also updated the `run()` cancellation docstring (still mentioned the
removed `abort_all` backstop) and added a `tokio-util` dep to
`base-proof-tee-nitro-attestation-prover` for the trait-level
`CancellationToken`.

* fix(prover-registrar): cycle hygiene fixes for reap, reconcile, and try_register

Four MEDIUM findings from the audit:

**#7 `reconcile_proof_tasks` could spawn after global cancel.** The
`run()` loop went straight from a successful `discover_and_resolve`
into `reconcile_proof_tasks` with no cancellation check between the
two, so a shutdown that landed during discovery could still cause
fresh proof tasks to be spawned (and the first registry RPC + nonce
acquisition to happen) before being observed. Skip reconcile entirely
when the cancel token is set; the orphan dereg pass was already
gated the same way.

**#8 `try_register` did a registry RPC before checking cancel.** A
task cancelled by reconcile (its signer just vanished) would still
hit `registry.is_registered` before returning. Added an
`is_cancelled` check at the top so cancellation is observed before
any new RPC work.

**#9 Reap-once-per-cycle race.** Tasks that finished during the
discovery RPC sat in `pending` until the next cycle's reap, which
caused two bad outcomes: (a) reconcile re-cancelled an already-done
task and bumped `proof_tasks_cancelled` for it, or (b) reconcile
suppressed a needed respawn after a fast failure for a whole cycle.
Reap again immediately after `discover_and_resolve` returns so
reconcile always sees current state.

**#10 `proof_tasks_pending` gauge went stale on discovery error.** The
gauge was only set on the Ok branch of `discover_and_resolve`, so
repeated discovery failures left monitors looking at a frozen value.
Set it after every reap and on both branches so the gauge always
tracks the live `pending.len()`.

* refactor(prover-registrar): named RegisterableSigner, run(self) value-API, doc fixes

Three audit findings, all API-surface hygiene:

**#11 `DiscoveryResolution.registerable` was an unnamed 3-tuple.** The
field was `Vec<(ProverInstance, Vec<Address>, Vec<Vec<u8>>)>` whose
positional structure hid the `attestations[idx]` indexing contract
from both the call site and the reader. Replace with a named
`RegisterableSigner { instance, addresses, attestations }` struct
exported from `lib.rs`, plus a pub `new` constructor that validates
the two invariants the spawn pass relies on:

  - `addresses` non-empty (zero-signer instances are filtered out
    upstream, never registerable).
  - `attestations.len() >= addresses.len()` so `attestations[idx]`
    is defined for every `idx in 0..addresses.len()`.

`discover_and_resolve` now constructs via `RegisterableSigner::new`
and demotes constructor failure to a warn + processing-errors metric
(the upstream check in `resolve_instance` already returns a richer
`ProverClient` error first, so a failure here would only indicate a
caller regression). `reconcile_proof_tasks` and the
`dr_from_kept` test helper consume named fields instead of tuple
destructuring.

Also promotes `ResolveOutcome` to pub per the AGENTS.md rule that
all module-scope types should be pub and re-exported from `lib.rs`.

**#12 `run` took `self: Arc<Self>`, forcing every caller to wrap.** The
arc receiver leaked an implementation detail (proof tasks need a
cloneable handle while the loop continues) into every call site,
including the production CLI and every test. Restore a value-API
`run(self)` that wraps in `Arc` and delegates to a public
`run_arc(self: Arc<Self>)` containing the actual loop. Production
CLI drops the manual `Arc::new(...)` wrap; the gated-pipeline test
harness keeps its own `Arc<RegistrationDriver>` for state
inspection and calls `run_arc` directly. Both methods are pub so
external test/integration code can pick the right entry point.

**base#16 `GatedProofProvider` doc claimed it tracked "peak counts".** No
peak counter exists on `GatedProofState` — only `call_count` and
`in_flight`. Update the doc to match reality and link the actual
accessors.

**base#17 not applicable.** The original audit asked to move `tokio/sync`
to dev-deps. Commit 8 added a production `tokio::sync::Semaphore`
to bound concurrent proof generation, so `sync` is now legitimately
required in production deps. Documenting here so the audit item
isn't relitigated.

* test(prover-registrar): cover dedupe, attestation indexing, unhealthy-window via run()

Three test gaps from audit finding base#13:

**Duplicate-signer dedupe.** When two `RegisterableSigner` entries
land in the same `DiscoveryResolution` for the same signer address
(e.g. two prover instances misprovisioned with identical enclave
keys), `reconcile_proof_tasks` must spawn exactly one task — a
double-spawn would later trigger two `tx_manager.send()` calls for
the same signer and waste a nonce. Verified by the
`in_flight.insert(signer)` short-circuit added in Commit 8.

**Attestation-index pairing.** Spawn pass uses
`entry.attestations[idx]` for each enumerated `(idx, signer)`. To
catch any regression that decouples those two vectors (e.g. wrong
zip source, stale per-cycle cache), added a parametric rstest with
`forward_order` and `reversed_order` cases that verifies the
recorded `signer → attestation` mapping is identical regardless of
how the vectors are ordered inside the `RegisterableSigner`.

Introduces a `RecordingProofProvider` test fixture that captures
the `(signer, attestation_bytes)` arguments and returns `Err` so
the spawned `try_register` task exits before reaching the (un-wired)
tx-manager path. The recording lets us assert pairing without
running the full proof + send pipeline.

**`unhealthy_registration_window` via `run()`.** The existing
process_instance tests cover the eligibility decision via the
legacy cfg(test) helper; this rstest exercises the same property
end-to-end through the production `run()` loop with the
`GatedRunHarness`. Three cases: recent launch should register,
old launch must not, missing `launch_time` must default to the
safe path.

* docs(nitro-attestation-prover): fix unresolved rustdoc link in trait

The Commit 8 trait-level cancellation contract referenced
`[block_recovery_for_signer]` without a path, but the method lives on
`BoundlessProver` in a sibling module and rustdoc cannot resolve a
bare-method link across module boundaries from the trait definition.
Replace with prose so the doc renders cleanly and does not lie about
which impl performs the recovery probe.

* refactor(prover-registrar): single gauge update, assert RegisterableSigner invariants, Cargo waterfall

- Collapse three RegistrarMetrics::proof_tasks_pending().set(...) calls in
  run_arc into a single end-of-iteration publish. pending.len() is stable
  across the cycle's sleep (only reap removes, only reconcile adds), so a
  single update is observationally equivalent and removes a per-branch
  obligation that would silently drift on future edits.

- Replace the match-with-warn+metric on RegisterableSigner::new in
  discover_and_resolve with an .expect(). resolve_instance has already
  enforced the same invariants (non-empty addresses, attestations >=
  addresses) with richer per-instance errors, so a failure here can only
  be an upstream regression. The previous branch was unreachable in
  production and would have silently dropped a valid registerable on
  trip — the assertion documents the upstream contract honestly.

- Re-order the nitro-attestation-prover Cargo.toml 'Types / traits' block
  to waterfall ascending: thiserror (24), tokio-util (27), async-trait
  (28), alloy-primitives (33). Aligns with workspace dependency-sorting
  convention.

* style(prover-registrar): cargo fmt

* style(nitro-attestation-prover): nightly cargo fmt

* docs(prover-registrar): clarify ok_to_dereg covers the majority-quorum branch

Heimdall AI review flagged that the doc on DiscoveryResolution::ok_to_dereg
read as if the field was only true when total_count == 0, contradicting the
implementation which also sets it true when total_count > 0 and the strict
majority guard (reachable * 2 > total) passes. Rewrite the doc to enumerate
both true branches and both false branches so the contract matches the
code at line 1223.

* revert(registrar): out-of-scope doc-link fixes for traits.rs and README

Per PR base#2799 review (jackchuma): the intra-doc-link cleanups touching
`traits.rs` (`AwsTargetGroupDiscovery`) and `README.md` (modules section)
landed in commit 42d610a were unrelated to the spawn-and-reap pipeline
work and belong in a separate docs-only PR. Revert them here so the PR
diff stays focused; they can be re-applied as a small follow-up.

Reintroduces the pre-existing `cargo doc --document-private-items`
warnings for those two files. The driver.rs intra-doc fixes from the
same commit are retained because they touch public-API doc rendering
for items this PR introduces (`PendingTask`, `DiscoveryResolution`,
`RegistrationDriver::run`).

* fix(registrar): honour PendingTask Ok contract on cancel-race in try_register

Per PR base#2799 review (Heimdall AI bot, boundless.rs:441): the proof
provider's internal `cancel.is_cancelled()` check can return `Err`
synchronously *after* try_register's biased `select!` has already
polled the cancel branch as Pending. When that happens the select
commits to the provider arm's `Err` rather than the (now-fired)
cancel, violating the `PendingTask` documented contract that a
cancelled task always returns `Ok(())` — and bumping
`processing_errors_total` for what is functionally a clean cancel.

Add an `Err(_) if signer_cancel.is_cancelled()` guard to the select
arm that maps that narrow race to `Ok(())` with a debug log.

Adds `CancelThenErrorProofProvider` test fixture and
`try_register_provider_err_after_cancel_returns_ok` which exercises
the exact synchronous-cancel-then-Err path inside a single poll.

* refactor(registrar): flatten RegisterableSigner to one entry per signer

Per PR base#2799 review (jackchuma, driver.rs:135): make each spawned proof
task correspond to a single `RegisterableSigner` rather than a multi-
signer instance bundle.

Before: `{ instance, addresses: Vec<Address>, attestations: Vec<Vec<u8>> }`
with an `attestations[idx]` index-pairing invariant enforced by
`RegisterableSigner::new` and asserted with `.expect(...)` at the call
site (which the Heimdall AI bot independently flagged on driver.rs:1289).

After: `{ instance, signer, attestation, enclave_index }` — a flat
(signer, attestation) pair plus the source enclave index retained for
per-task log attribution.

Effects:
- The name `RegisterableSigner` now matches the shape (singular signer)
  rather than describing a multi-signer instance bundle.
- The constructor and its invariant checks disappear; the parallel-vector
  index-pairing bug class is structurally impossible.
- `discover_and_resolve` flattens at construction time via
  `addresses.into_iter().zip(attestations).enumerate()`. The zip
  truncates at the shorter side, mirroring the upstream invariant.
- `reconcile_proof_tasks` becomes a flat `for entry in registerable`
  loop; the previous nested `for entry / for (idx, signer)` and the
  `.expect(...)` panic path are removed.
- The cycle-internal `in_flight: HashSet<Address>` dedupe at spawn time
  still collapses duplicate signers across entries (e.g. two instances
  briefly backing the same enclave key) — covered by the existing
  `reconcile_proof_tasks_dedupes_signer_across_registerable_entries` test.

The previously-parametrized `reconcile_proof_tasks_pairs_attestation_with_signer_by_index`
rstest case becomes `reconcile_proof_tasks_pairs_attestation_with_signer`:
since each entry now owns its own (signer, attestation), the test exists
purely as a regression guard that the spawn pass forwards entry fields
consistently regardless of vector ordering.

Cost: `instance.clone()` per signer instead of per instance — typical
N=1 enclaves per host; cloning a small struct is microseconds against
~20 minutes of downstream proof generation.

No public-API breakage: `RegisterableSigner` was introduced in this PR
(commit b645378) and has no external consumers.

160/160 tests pass.

* fix(registrar): converge in one cycle when a signer vanishes and reappears

Cancelled-but-not-yet-reaped tasks no longer block a fresh spawn for
the same signer in the very next reconcile cycle. Without this filter
a rolling deploy that briefly drops a signer from registerable and
then re-adds it had to wait two full poll intervals (~60s at 30s
polls) before the resurrected signer got a live task.

Also tightens the cancel-race comment in try_register to call out
that the guard relies on the signer-scoped token being the SAME one
forwarded to the provider, and reorders a let-binding for clarity.

Adds a regression test that drives N+1 (cancel) and N+2 (respawn)
in-process and asserts a fresh task appears alongside the
still-pending cancelled entry.

* style(registrar): apply nightly rustfmt to driver.rs

* fix(registrar): tighten Some(attestations) invariant in resolve_instance

When cancel fires after fetching attestations but before the CRL gate,
return attestations: None instead of Some(all_attestations). The outer
run_arc loop re-checks cancel before reconcile_proof_tasks so today's
behavior is unchanged, but the local invariant — Some(..) iff every
eligibility + security gate (including CRL) passed — no longer depends
on the caller's redundant cancel check. Matches the CRL-revoked branch
a few lines down.

* fix(registrar): preserve in-flight proofs when resolve_instance fails transiently

A transient signer_public_key / signer_attestation / CRL hiccup during
discovery would absent the affected signers from `registerable` even
though the driver hasn't proven they're gone or ineligible. The
reconcile cancel-pass would then cancel their in-flight proof tasks —
abandoning up to ~70 min of Boundless work on a single RPC blip.

Track failed-to-resolve instance IDs explicitly on DiscoveryResolution
and skip the cancel-pass for any in-flight task whose instance_id is
in that set. Cancel still fires for tasks tied to instances that
returned Ok with attestations: None (legitimate ineligibility) or
vanished from discovery output entirely.

Adds a regression test exercising both the guard (preserved) and a
sanity contrast (cancelled when the guard is absent).

* refactor(registrar): propagate both error paths uniformly in run_orphan_dereg

Drop the inline match-and-swallow on deregister_orphans so both
failure modes (registry load and per-orphan dereg) bubble up to the
single caller site in run_arc, which already logs + increments
processing_errors_total. Removes the registry-vs-dereg asymmetry where
one path used ? and the other was caught inline.

* fix(registrar): count every proof-task cancellation exactly once at shutdown

`drain_proof_tasks` previously gated its `proof_tasks_cancelled`
increment on `!task.cancel.is_cancelled()` to avoid double-counting
tasks already cancelled by `reconcile_proof_tasks`. Each per-task
`signer_cancel` is a child of `DriverConfig::cancel`, so on
shutdown the parent's fire auto-cancels every child token before
drain runs — `is_cancelled()` is then `true` for every task and
the metric silently misses the entire shutdown-driven cancel path.

Add a `cancelled_by_reconcile: bool` flag on `PendingTask` that
`reconcile_proof_tasks` sets alongside its cancel intent.
`drain_proof_tasks` now gates on that flag instead, so every
cancellation increments the counter exactly once across the
intent-time and shutdown paths without double-counting.

* fix(registrar): make in-flight registry RPCs cancel-aware

Wrap the three production registry awaits in the spawned-task path
(`try_register` pre-proof-gen `is_registered`, `try_register`
post-tx-error `is_registered`, and `run_orphan_dereg`
`get_registered_signers`) in `select!` against their owning
cancel token. Each RPC is a side-effect-free read, so dropping it
on cancel is safe (no nonce-gap risk, no on-chain state mutation).

Previously a stalled registry RPC during shutdown would extend
drain latency by an entire round-trip per pending task — far above
the cooperative bound `drain_proof_tasks` already accepts for the
intentionally non-cancel-aware `tx_manager.send()` (where dropping
mid-send leaves a permanent nonce gap). The only remaining
non-cancel-aware operation is now `tx_manager.send()` itself,
which is documented in the drain rustdoc.

Add two tests with a `StallingRegistry` mock that parks
`is_registered` / `get_registered_signers` on a never-completing
future and asserts that firing the cancel token aborts the call
within 1 s (vs the 5 s timeout backstop that would catch a
regression).

* refactor(registrar): replace sync step() with primitive-targeted tests

The synchronous `step()` helper duplicated production cycle logic
(discovery → process → orphan-dereg) but bypassed the spawn pipeline
that actually runs in production, so tests that drove it exercised
a synthetic path that no longer matched `run()`. Delete `step()` and
migrate each of its 16 test callsites to call the production
primitives directly:

  * `discover_and_resolve` for resolution-shape assertions
    (registerable set, active_signers, ok_to_dereg flag,
    unresolved_instance_ids, reachable_count).
  * `process_instance` for per-instance registration tx accounting.
  * `run_orphan_dereg` for orphan-deregistration behavior.

Rename the `step_driver` test constructor to `cycle_driver` and
return `Arc<...>` so callers can invoke `discover_and_resolve`
(which takes `&Arc<Self>`) without re-wrapping.

Remove `step_cancellation_breaks_immediately_without_waiting_for_blocked_futures`
and its `BlockingSignerClient` companion: `discover_and_resolve`
deliberately does NOT `select!` on cancel around its instance
resolve loop (cancelling a future holding an in-flight CRL
`revokeCert` send would leak a nonce). End-to-end shutdown latency
is covered by the pipeline tests in this module
(`run_drains_pending_proof_tasks_on_shutdown` etc.).

163/163 default + 164/164 metrics tests pass; clippy clean on both
feature configs.

* refactor(registrar): key pending proof tasks by signer address

Tasks return their signer (Ok(Address)) so the happy path cleans up in
O(1) via map keying; Err and panic paths recover the signer via an O(n)
scan over pending (bounded by max_concurrency, typically <20), avoiding
a second reverse task::Id index. The success arm guards cleanup with a
task_id match so a stale task whose pending entry was overwritten by a
same-cycle respawn cannot evict the fresh entry.

* style(registrar): drop defensive saturating_mul in ok_to_dereg quorum

reachable_count is bounded above by total_count = instances.len(), so
the doubling cannot overflow on any physically representable instance
list. Plain * 2 is clearer about intent.

* fix(registrar): protect in-flight signers from orphan-dereg TOCTOU

When an instance fails resolve_instance() transiently this cycle,
reconcile_proof_tasks() preserves its in-flight proof task via
unresolved_instance_ids, but the signer is absent from
resolution.active_signers (no fresh evidence this cycle). If that
preserved task completes and calls registerSigner() right as the
orphan-dereg pass runs, the orphan sweep was using only
active_signers as the protected set and would deregister the
freshly-registered signer.

Add RegistrationDriver::protected_signers(resolution, pending) ->
HashSet<Address> that unions active_signers with pending.keys(), and
use it at the orphan-dereg call site in run_arc(). Two focused tests
cover the union (blocks dereg) and its sanity contrast (empty pending
permits dereg).

Audit finding from jackchuma on PR base#2799.

* docs(registrar): trim three overlong run-loop comments

Drop the obituary block describing the removed test-only step()
helper — it documented a method that no longer exists. Condense
the gauge-publish and second reap() rationale to one sentence
each; the load-bearing details (cancellation contracts, nonce
safety, cancelled_by_reconcile flag) are kept verbatim.

Nit from jackchuma on PR base#2799.

* refactor(registrar): unify stale-task guard across apply_join_outcome arms

Extracted the address-keyed task_id-match check into
RegistrationDriver::remove_if_task_matches so all three
apply_join_outcome arms (Ok success, Ok(_,Err), JoinError) funnel
through one helper. The Err and JoinError arms previously relied on
find_signer_by_task_id's implicit guarantee that a stale id has no
matching pending entry — a fragile invariant under future refactors.
Now each arm gates removal locally on entry.task_id == id, matching
the explicit guard already present on the success arm.

Adds apply_join_outcome_err_arm_preserves_fresh_entry_when_stale_task_fails_for_same_signer
mirroring the existing success-arm fresh/stale test.

Also trims two over-verbose doc blocks flagged in review:
PendingRegistration::task_id (14→7 lines) and check_and_revoke_crls
(30→18 lines).

* docs(nitro-attestation-prover): correct generate_proof cancel doc

The previous wording referred to cancellation 'between recovery probe
attempts', but generate_proof has no probe loop — that lives in
generate_proof_for_signer. Clarified that generate_proof checks
cancel exactly once at the top and points readers to
generate_proof_for_signer for per-probe cancellation.

* refactor(registrar): replace two non-local invariant assumptions with local guards

Two unrelated sites where correctness depended on invariants enforced
tens of lines away, hardened to be locally panic/index-free:

1. run_proof_task: the proof-semaphore acquire previously called
   expect() on Semaphore::acquire_owned()'s Result, relying on the
   never-call-close() invariant. Switched to a match that warn-logs
   and returns Ok(()) on the unreachable Err arm, so a future refactor
   introducing close() (or a wrapper that closes on drop) cannot crash
   the registration driver loop.

2. resolve_instance: the CRL check previously indexed
   all_attestations[0], relying on the addresses.is_empty() early
   return (line 1073) and the all_attestations.len() < addresses.len()
   length check (line 1109) — both 30+ lines upstream. Switched to
   .first().ok_or_else(RegistrarError::ProverClient { .. }) so the
   non-empty invariant is locally visible at the use site.

Both sites are pre-existing PR-review feedback (github-actions bot);
no behavior change on the live paths, only the unreachable arms
differ.
base#2991)

* feat(proof): add TEE proof types and job queue RPCs to zk_prover proto

Extends the zk_prover gRPC service with the proto surface needed to
support TEE-based proofs alongside ZK proofs and to manage proof jobs
via a claim/heartbeat/complete/fail worker queue.

- Add PROOF_TYPE_TEE and structured ZkProofRequest / TeeProofRequest
  request variants, exposed via a new oneof on ProveBlockRequest and
  the new top-level ProofRequest message.
- Add ProofResult, ZkProofResult, TeeProofResult and TeeProposal
  messages, and surface the structured result on GetProofResponse via
  a new 'proof' field.
- Add SubmitProof, GetProofJob, ClaimProofJob, HeartbeatProofJob,
  CompleteProofJob and FailProofJob RPCs along with their request and
  response messages and the ProofJob payload.
- Re-export the new generated types from base_zk_client.
- Stub the new RPC handlers in ProverServiceServer as unimplemented
  for now; existing handlers and tests are updated to populate the
  new optional fields with None to preserve current behavior.

* address pr feedback

* address pr feedback

* address pr feedback

* re-work into new directory

* clean up proto file

* address pr feedback

* clean up readme

* improve tee + zk support

* address pr feedback
…ase#2967)

The recovery helper introduced in base#2698 had two latent failure modes:

  1. When the latest L2 block number was at or below the pruned block
     number, the while loop never executed and the post-loop hydrate
     re-fetched the known-pruned block, re-raising the very
     `MissingL1InfoDeposit` we were recovering from.
  2. If `latest` itself was pruned, every probed midpoint shifted `lo`
     upward, the search converged on `latest_number`, and the
     post-loop hydrate failed for the same reason.

Probe `latest` once up front to establish the upper-bound invariant
and surface a precise `NoUnprunedBlockAvailable` error instead of a
misleading `MissingL1InfoDeposit` when no unpruned block exists.
Cache the last successfully hydrated block at the upper bound during
the search so the post-loop return doesn't pay for a redundant RPC
round-trip.
* feat(consensus): recover EOA sender for EIP-8130 pool indexing

Adds Eip8130Signed::recover_eoa_sender, which returns Ok(None) for the
configured-owner path (sender field populated, no recovery needed) and
ecrecovers the 65-byte sender_auth blob against TxEip8130::sender_signature_hash
for the EOA path (sender field absent).

Wires the helper into BasePooledTransaction::SignerRecoverable so the
mempool can index EIP-8130 transactions by address through the existing
Recovered<_> machinery. Previously the SignerRecoverable impl for the
EIP-8130 variant returned the explicit sender or errored, blocking pool
admission for the EOA-path entirely.

* feat(consensus): add EIP-8130 mempool policy constants

Adds four constants used by the txpool validator for structural checks:

- ECRECOVER_VERIFIER (address(1)): the lower-bound verifier address; the
  native ECRECOVER verifier is fixed here per spec.
- REVOKED_VERIFIER (type(uint160).max): sentinel marking a revoked owner
  slot; authentication blobs prefixed with this verifier MUST be rejected.
- MAX_CONFIG_CHANGES_PER_TX = 10: per-tx ConfigChange cap recommended by
  the spec as a mempool DoS guard.
- NONCE_FREE_MAX_EXPIRY_WINDOW = 10 (seconds): tight expiry window for
  nonce-free-mode transactions to bound the replay surface absent nonce
  state.

* feat(txpool): accept EIP-8130 with structural validation

Replace the PR3 blanket hard-reject of type-byte 0x7D transactions in the
devp2p validator with the conservative-accept gate specified by EIP-8130
§ Validation and § Nonce-Free Mode.

Adds 'BasePooledTx::as_eip8130' (default 'None'), wired through the
concrete 'BasePooledTransaction<BaseTransactionSigned, _>' impl, so the
validator can inspect the inner 'Eip8130Signed' without dynamic
downcasts. RPC ingress paths and local-origin transactions remain
rejected pending PR15.

Structural checks enforced before delegating to the inner Eth
validator: chain id binding, fee-cap ordering, non-zero gas/fee,
nonce-free mode invariants (expiry window, zero sequence), sender_auth
and payer_auth well-formedness with revoked-verifier sentinel
filtering, account_changes shape (at most one Create, leading
position, at most one Delegation, config-change cap and chain
binding), and verifier address floor on initial owners.

No account state lookups, no verifier dispatch, no fork gating yet -
the latter lands in PR6 alongside the Beryl activation wiring.

* test(txpool): EIP-8130 structural validation

Adds 30 unit tests against the structural-acceptance gate introduced
in the previous commit. Coverage targets every reject branch reachable
without account state plus the happy path:

- envelope-level: minimum-shape accept, local/private origin reject,
  chain-id mismatch, tip-above-fee-cap, zero gas, zero fee
- nonce-free mode: zero-expiry reject, nonzero-sequence reject,
  expiry-beyond-window reject, expiry-at-window-edge accept
- sender_auth: empty reject, wrong length on EOA path, short on
  configured-owner path, revoked verifier sentinel reject
- payer_auth: presence/absence symmetry, short auth, revoked verifier
- account_changes: Create-not-at-index-0, multiple Creates, empty
  code, no initial owners, duplicate owner_ids, verifier below
  ECRECOVER floor, revoked verifier on owner; ConfigChange foreign
  chain id, short auth, MAX exceeded, exactly-MAX accept; multiple
  Delegation reject; mixed Create+Configs+Delegation accept

Tests target the private 'validate_eip8130_structural',
'validate_sender_auth', 'validate_payer_auth', and
'validate_account_changes' helpers directly via crate-internal
access. The MockEthProvider fixture seeds no accounts, which is
sufficient because the structural gate runs purely on tx bytes.

* fix(txpool): EIP-8130 already-expired and ConfigChange owner_changes

Address PR base#2926 review feedback:

1. Nonce-free transactions are now also rejected when expiry <= now
   (already expired). The previous branch only rejected expiry == 0
   and expiry beyond the policy window, silently accepting txs whose
   expiry sat in the past.

2. ConfigChange.owner_changes now goes through the same verifier-floor,
   revoked-sentinel, and duplicate-owner_id checks that Create.initial_owners
   already had. The shared logic is extracted into validate_owner_iter,
   eliminating the gap between the two account-change variants.

3. Hoist 'use std::collections::BTreeSet' to file scope per workspace
   convention (no use statements inside function bodies).

* fix(consensus): honor unchecked EIP-8130 sender recovery contract

The PR3-era 'BasePooledTransaction::SignerRecoverable' impl routed the
EIP-8130 variant through 'recover_eip8130_sender' from BOTH the
'recover_signer' (checked) and 'recover_signer_unchecked' /
'recover_unchecked_with_buf' (unchecked) entry points. The helper
only knew about the checked path, so the unchecked dispatchers were
silently applying EIP-2 low-s filtering despite the trait contract
allowing upper-half signatures.

This causes EOA-path EIP-8130 transactions whose signature happens to
land in the upper half of the curve order to be rejected by reth code
paths that legitimately call the unchecked dispatcher (block import,
historical-tx replay), where the contract is explicitly to skip
EIP-2 filtering.

Fix:
- Add 'Eip8130Signed::recover_eoa_sender_unchecked' mirroring the
  existing checked variant, sharing the parsing / configured-owner
  short-circuit via a new private 'recover_eoa_sender_with' closure
- Add 'recover_eip8130_sender_unchecked' helper in pooled.rs and wire
  it into both unchecked dispatcher arms
- 'recover_signer' continues to use the checked path (correct)
- Add a parity test that constructs a high-s signature via 'N - s'
  and asserts checked rejects + unchecked accepts the same address

Test count: 5 in 'eip8130::signed::tests' (+2 new).

* fix(consensus): EIP-8130 envelope honors EOA recovery + checked/unchecked split

Mirror the pool-side fix from 7d648e4 in the envelope-level
'SignerRecoverable' impl on 'BaseTxEnvelope'. The previous code (carried
over from PR3) short-circuited to 'explicit_sender()' and returned a
'RecoveryError' for EOA-path EIP-8130 transactions, which:

- Drops the sender on the block-import path: every node that decodes a
  block containing an EOA-path 8130 tx would reject it as malformed.
  Without PR4's gate this was unreachable; with PR4 the pool can now
  hold such txs and forward them to builders, opening a path to blocks
  that the consensus layer would refuse to verify.
- Re-introduces the unchecked/checked confusion the pool fix corrected:
  even after wiring real recovery, calling the checked variant from the
  unchecked dispatcher would silently apply EIP-2 low-s filtering.

Fix:
- Add 'recover_eip8130_envelope_sender' (checked) and
  'recover_eip8130_envelope_sender_unchecked' free functions
- Wire them through the matching arms in 'recover_signer',
  'recover_signer_unchecked', 'recover_unchecked_with_buf'
- Add envelope-level parity tests: one constructs a high-s signature
  via 's' = N - s, v' = !v' and asserts checked rejects + unchecked
  + buffered-unchecked both recover the original address; one verifies
  the configured-owner short-circuit on all three entry points

This closes the consensus-safety gap that PR4 opened between merging
PR4 and PR6's fork gating activating.

* fix(txpool): EIP-8130 cap owner entries + revoke-check on config auth

Address two PR review findings:

* The per-`ConfigChange` `auth` blob's 20-byte verifier prefix is now
  rejected when it equals `REVOKED_VERIFIER`, matching the existing
  rejection in `sender_auth` and `payer_auth` so a config-change scoped
  to a revoked authority never enters the mempool.

* `validate_owner_iter` now takes a slice and projection rather than an
  iterator so it can short-circuit on length before allocating the
  `BTreeSet`. Slice length is bounded by the new
  `Eip8130Constants::MAX_OWNERS_PER_ENTRY = 32` cap, applied to both
  `Create.initial_owners` and `ConfigChange.owner_changes`. Combined
  with `MAX_CONFIG_CHANGES_PER_TX` this bounds per-transaction
  duplicate-detection work and removes a cheap DoS vector at admission.

Adds four tests: revoked verifier in `cfg.auth`, over-cap
`initial_owners`, over-cap `owner_changes`, and acceptance at exactly
`MAX_OWNERS_PER_ENTRY` (boundary).

* fix(txpool,consensus): EIP-8130 unify auth verifier range + dedupe recovery

Three follow-ups to Claude review on fd34d06:

* `validate_sender_auth` and `validate_payer_auth` now reject verifiers
  below `ECRECOVER_VERIFIER` in addition to the existing
  `REVOKED_VERIFIER` rejection. The check is centralized in
  `verifier_out_of_range` and applied uniformly across the four auth
  surfaces (`sender_auth`, `payer_auth`, `cfg.auth`, per-owner
  verifiers) so a configured-owner tx whose `sender_auth` starts with
  `address(0)` or another reserved value is rejected at admission.

* Hoisted the single `block_timestamp()` read above the nonce-key branch
  in `validate_account_changes` so the nonce-free expiry-window check
  and the legacy expiry check observe the same head-block timestamp,
  removing a benign TOCTOU between concurrent `on_new_head_block`
  updates.

* Collapsed the byte-for-byte duplicated `recover_eip8130_sender` /
  `_unchecked` pairs from `pooled.rs` and `envelope.rs` into two
  `pub` methods on `Eip8130Signed` (`recover_sender`,
  `recover_sender_unchecked`) so the configured-owner short-circuit
  and the EOA ecrecover flattening are defined in exactly one place
  and cannot drift between the pool and block-import paths.

Adds two regression tests covering the reserved-verifier rejection on
`sender_auth` and `payer_auth`. The existing `make_valid_config_change`
fixture is updated to use `ok_verifier()` so it remains valid under the
broader range check.
* fix(chainspec): reject beryl without activation admin

* fix(chainspec): allow chain spec activation admin

* fix(chainspec): derive chainspec errors
* fix(common-evm): install Beryl precompiles in builder

* fix(common-evm): address builder review feedback

* fix(common-evm): centralize builder defaults
* refactor(consensus): move eip8130 admission helpers onto signed tx

Amp-Thread-ID: https://ampcode.com/threads/T-019e6f1e-1cd0-77d9-b249-dd18f8516cb4
Co-authored-by: Amp <amp@ampcode.com>

* refactor(txpool): reuse eip8130 signed validation helpers

Amp-Thread-ID: https://ampcode.com/threads/T-019e6f1e-1cd0-77d9-b249-dd18f8516cb4
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
* fix(common): reject last admin role revocation

* test(common): cover privileged last admin revoke
…ce crate (base#3005)

* copy zk prover outbox into new shared prover-service crate

* address pr feedback

* address pr feedback

* address pr feedback

* address pr feedback

* address pr feedback

* address pr feedback
* refactor(prover-service): migrate from gRPC to JSON-RPC

Replace tonic/prost protobuf definitions with jsonrpsee-based JSON-RPC
contract. Splits the API trait and types into dedicated modules and
gates client/server generation behind rpc-client and rpc-server features.

* address pr feedback

* address pr feedback
* feat(infra): add beryl upgrade checks

* fix(infra): show beryl checks after azul

* feat(basectl): select upgrade checks

* chore(prover-service): format zepter features

* fix(basectl): address upgrade review comments

* fix(basectl): avoid duplicate hardfork refreshes
* fix(infra): keep upgrade countdown below 100 before activation

* docs(guides): use local discv5 handler link
…ase#3023)

* fix(proposer): preserve proved queue across single-target failures

The proving pipeline previously called a blanket state.reset() on submit
panics, root mismatches, max-retries, and proof-task panics. Each reset
wiped the proved queue — minutes of compute and dollars of prover spend
per entry — even when the failure was confined to one block. Replace the
catch-all reset with per-target cleanup, catch panics inside the spawned
prove/submit futures so they surface as normal retry-able outcomes, and
drop only cached_recovery (not proved) on root-mismatch and max-retries
so the next tick re-walks the chain while sibling proofs survive.

Amp-Thread-ID: https://ampcode.com/threads/T-019e7027-1906-74bc-8048-ea333867221e
Co-authored-by: Amp <amp@ampcode.com>

* zepter fix

* review: inline forget_target into max-retries branch

Per PR review: forget_target had only one caller and redundantly removed
inflight[target] which was already cleared a few lines above. Inline the
two operations that actually matter (retry_counts.remove + cached_recovery
= None) and drop the helper.

Amp-Thread-ID: https://ampcode.com/threads/T-019e7027-1906-74bc-8048-ea333867221e
Co-authored-by: Amp <amp@ampcode.com>

* fix clippy

---------

Co-authored-by: Amp <amp@ampcode.com>
* feat(proof): configure boundless offer bidding-start delay

Adds --boundless-offer-bidding-start-delay-secs to the prover-registrar
CLI, threading the value through BoundlessConfig and BoundlessProver
into OfferParams::bidding_start. Mirrors the wiring introduced in
base#2949 and base#2996 for the other offer fields.

bidding_start (Offer.rampUpStart on-chain) is the moment a request
becomes biddable. Before this instant the request is published but
no prover can lock it. The Boundless SDK auto-derives this value as
roughly the guest program's executor time (cycle_count / 1 MHz,
capped at 1 hour), creating a 'discovery window' visible on the
Boundless explorer as the flat period preceding the price ramp.
For large workloads this delay alone accounts for several minutes
of submission-to-fulfillment latency.

Setting --boundless-offer-bidding-start-delay-secs=0 eliminates the
discovery window entirely: bidding opens at request submission and
the fastest prover can lock as soon as it finishes executing the
program. This minimises end-to-end latency at the cost of higher
prices (less time on the ramp-up) and a smaller set of provers
seeing the request before it is locked.

Together with the offer pricing knobs from base#2949 and the
lock-timeout knob from base#2996, operators can now run a 'fast lane'
configuration that skips the auction entirely (min_price ==
max_price, ramp_up_period == 0, bidding_start_delay == 0,
lock_timeout near proving_time), bringing total turn-around down
to roughly executor_time + proving_time.

* feat(proof): default boundless fast-lane offer knobs to 0

Flip the two latency-critical Boundless offer knobs to fast-lane
defaults so the registrar binary is latency-optimised out of the
box, with env vars still able to introduce non-zero values without
rebuilding:

  --boundless-offer-ramp-up-period-secs (BOUNDLESS_OFFER_RAMP_UP_PERIOD_SECS): 0
  --boundless-offer-bidding-start-delay-secs (BOUNDLESS_OFFER_BIDDING_START_DELAY_SECS): 0

With both at 0, bidding opens immediately at submission and the
max price is offered with no discount window, eliminating the
SDK's cycle-derived 'discovery flat period' (visible on the
Boundless explorer as the gap before the price ramp) that
otherwise accounts for several minutes of submit-to-fulfillment
latency on large workloads.

Price and lock-timeout knobs remain Option-typed with no default
because their correct values depend on the workload and market.

Convert the two flags from Option<T> to T with default_value_t = 0
(matching the codebase convention for default-bearing flags), drop
the now-dead all-None short-circuit in apply_offer_config, always
emit Offer.ramp_up_period and Offer.bidding_start from the
configured values (computing bidding_start as now + delay per
request), and update fixtures and tests.

* docs(proof): clarify now_unix_secs factoring rationale

Address review feedback: the previous doc said the helper was
factored out 'so tests can substitute a fixed clock', but it's a
private inherent method that tests can't actually substitute
without trait/cfg indirection. The tests in fact bracket calls
between before/after snapshots to assert the resulting timestamp
falls in the expected window. Reword the doc to match reality.

* fix(proof): anchor Offer.rampUpStart at submission time, not params build

Previously `apply_offer_config` captured `bidding_start = now() + delay`
at the time `RequestParams` was built (inside `build_client_and_params`).
The Boundless SDK computes `Offer.lockTimeout = rampUpStart + lock_timeout_secs`
and `Offer.timeout = rampUpStart + timeout_secs` as absolute deadlines,
so an old `rampUpStart` consumes the lock/expiry window before the
request is even submitted.

This was broken because between `build_client_and_params` and the
actual `client.submit_onchain` there is meaningful wall-clock time:
  - The recovery scan loop (up to `max_recovery_attempts` RPC probes)
  - `submit_lock` contention serialising concurrent submissions
  - Preflight work

Under load or after a slow recovery scan, a request with an explicit
600 s lock timeout could be submitted with only a few hundred seconds
of effective lock window — or, in the worst case, already expired.

Move the `bidding_start` computation out of `apply_offer_config` into
a new `apply_bidding_start` helper that mutates `params.offer.bidding_start`
in place (so the rest of the offer set by `apply_offer_config` is
preserved) and call it inside `submit_and_wait` immediately after
acquiring `submit_lock` and immediately before `client.submit_onchain`.
This guarantees the deadline anchor is fresh at the moment the request
actually hits the chain.

Update tests to cover both helpers separately:
  - `apply_offer_config_*` tests now assert `bidding_start.is_none()`
  - `apply_bidding_start_defaults_to_now` covers the default-0 delay
  - `apply_bidding_start_uses_current_clock` covers a non-zero delay
  - `apply_bidding_start_preserves_other_offer_fields` verifies in-place
    mutation does not wipe prior offer settings

* revert(proof): drop fast-lane default for ramp_up_period

Reverts the `offer_ramp_up_period_secs` half of the fast-lane default
flip (commit 146348a) per reviewer feedback: defaulting the ramp-up
period to 0 loses Dutch-auction price discovery entirely (max price
paid immediately on lock), which is a meaningful cost change rather
than a pure latency optimisation, and is better left as an explicit
deployment-time decision.

Restore `offer_ramp_up_period_secs` to `Option<u32>` with no default
across CLI, BoundlessConfig, and BoundlessProver. When unset, the
Boundless SDK derives a cycle-count-based ramp; operators that want
fast-lane behaviour can still set `BOUNDLESS_OFFER_RAMP_UP_PERIOD_SECS=0`
in their deployment config.

`offer_bidding_start_delay_secs` retains its `0` default — that knob
only affects the pre-bid "discovery window" before bidding opens,
not the price paid, so the fast-lane default is unambiguously a pure
latency win.

* style(proof): align now_unix_secs to fail-soft UNIX_EPOCH handling

Address review feedback: align `now_unix_secs` to use
`.unwrap_or_default()` (matching the existing pattern in
`effective_expiry` and `is_journal_fresh`) rather than `.expect(...)`.
A pre-epoch system clock is unrealistic; for a long-running registrar,
returning 0 here and letting the resulting bidding_start be rejected
downstream is preferable to panicking the whole process.
…e crate (base#3026)

* chore(prover-service): move zk prover service code into prover-service crate

Move the zk prover service implementation into crates/proof/prover-service/src/,
adapting it to coexist with legacy crates in the workspace and depend on the
other prover-service crates (db, outbox) rather than the legacy zk crates.

- Add backends, proxy, server, worker, snark_e2e, request, metrics, and proof
  request manager modules under src/
- Update Cargo.toml dependencies and drop the now-unused 'server' feature; the
  server bits are always built
- Update build.rs to always emit the file descriptor set
- Re-export new public API from lib.rs
- Adjust ProofRequestRepo::list_with_offset to accept a slice of statuses

* fix deps order

* just fix
…e-protocol crate (base#3030)

* chore(prover-service): extract protocol types into base-prover-service-protocol crate

Move shared protocol types from crates/proof/prover-service/src/types.rs into
a new dependency-light crate at crates/proof/prover-service/protocol. The new
crate contains only protocol types and generated JSON-RPC traits, with the
API split into two role-specific traits:

- ProverRequesterApi (client/server behind rpc-client / rpc-server features)
- ProverWorkerApi (client/server behind rpc-client / rpc-server features)

Existing wire format and serialization behavior are preserved.

* dedup
* chore(prover-service): extract protocol types into base-prover-service-protocol crate

Move shared protocol types from crates/proof/prover-service/src/types.rs into
a new dependency-light crate at crates/proof/prover-service/protocol. The new
crate contains only protocol types and generated JSON-RPC traits, with the
API split into two role-specific traits:

- ProverRequesterApi (client/server behind rpc-client / rpc-server features)
- ProverWorkerApi (client/server behind rpc-client / rpc-server features)

Existing wire format and serialization behavior are preserved.

* dedup

* chore(prover-service): add base-prover-service-client crate with role-specific JSON-RPC clients
…ase#3034)

* chore(prover-service): extract protocol types into base-prover-service-protocol crate

Move shared protocol types from crates/proof/prover-service/src/types.rs into
a new dependency-light crate at crates/proof/prover-service/protocol. The new
crate contains only protocol types and generated JSON-RPC traits, with the
API split into two role-specific traits:

- ProverRequesterApi (client/server behind rpc-client / rpc-server features)
- ProverWorkerApi (client/server behind rpc-client / rpc-server features)

Existing wire format and serialization behavior are preserved.

* dedup

* chore(prover-service): add base-prover-service-client crate with role-specific JSON-RPC clients

* chore(prover-service-client): add error type and config validation

Introduce ProverServiceClientError covering invalid config, RPC/transport
failures, terminal proof failures, worker lease rejections, timeouts, and
unexpected result payloads, with retry classification helpers.

Extend ProverServiceClientConfig with default request timeout, poll
interval, and max wait fields plus a validate() method that checks the
endpoint URL (scheme, host) and non-zero durations.

Switch ProverRequesterClient and ProverWorkerClient APIs to return
ProverServiceClientError instead of jsonrpsee::core::client::Error.

* address pr feedback

* clean tests

* address pr feedback

* address pr feedback
mw2000 and others added 25 commits June 3, 2026 16:02
The range elf is updated because of the beryl timestamp.
…#3315)

* Backport flashblocks pending-state fast path to v1.1.0

Amp-Thread-ID: https://ampcode.com/threads/T-019e945c-9d54-72be-b15a-4b731d10145e
Co-authored-by: Amp <amp@ampcode.com>

* Fix release metering pending state arc mismatch

Amp-Thread-ID: https://ampcode.com/threads/T-019e945c-9d54-72be-b15a-4b731d10145e
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
* fix(b20-asset): reject zero multiplier in updateMultiplier (BOP-328) (base#3367)

Storing zero via updateMultiplier(0) created an event/read inconsistency:
the emitted MultiplierUpdated event showed 0 while onchain reads returned
WAD (1e18) because zero is the uninitialized storage sentinel. Add a guard
that reverts with InvalidMultiplier before writing to storage, preventing
the spurious event.

Add tests asserting updateMultiplier(0) reverts and updateMultiplier(WAD)
emits MultiplierUpdated with WAD.

* fix(ci): use release crates in base-std tests

---------

Co-authored-by: Eric Liu <ericliu121187@gmail.com>
Backports the selected BOP follow-up fixes into releases/v1.1.0 as one release-branch patch:

- base#3252 / BOP-287: reject set_code in static context

- base#3281 / BOP-286: return structured errors for Set transient methods

- base#3257 / BOP-285: clarify StorageKey::mapping_slot rustdoc

- base#3280 / BOP-284: fix stale packing module rustdoc

- base#3304 / BOP-294: clarify generated packed store comments

- base#3305 / BOP-295: support mapping-only Storable structs

- base#3307 / BOP-296: use named B20 asset decimals default

- base#3306 / BOP-297: simplify role-admin default fallback read

- base#3308 / BOP-298: remove unreachable namespace macro reorder branch

- base#3309 / BOP-299: improve unsupported install option diagnostics
* chore(common): schedule Sepolia Beryl upgrade

* test(proof): keep oracle rollup fixture pre-Beryl
…ase#3409)

* Bundle extra binaries into node-reth-dev image

Build the real base-reth-node binary for the published client image and keep base-client as a compatibility symlinked entrypoint. Also include base-consensus, basectl, and snapshotter in the same runtime image.

Amp-Thread-ID: https://ampcode.com/threads/T-019ea8bf-5485-75da-a350-d7630db409d0
Co-authored-by: Amp <amp@ampcode.com>

* Refine bundled node-reth-dev image

Align the Docker Rust toolchain with the workspace, keep the published client entrypoint as base-client by copying the base-reth-node binary under that name, and preserve the extra bundled binaries in the image.

Amp-Thread-ID: https://ampcode.com/threads/T-019ea8bf-5485-75da-a350-d7630db409d0
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
Backports the selected BOP fixes into releases/v1.1.0 as one release-branch patch:

- base#3207 / BOP-273: bounds-check VecHandler-backed SetHandler writes

- base#3230 / BOP-276: fix misleading EIP-3541 set_code comment

- base#3253 / BOP-292: remove unreachable B20 announcement guard and redundant activation error

- base#3285 / BOP-291: enforce EIP-2200 SSTORE stipend guard

- base#3374 / BOP-290: charge code_deposit_state_gas for balance-only set_code accounts

- base#3258 / BOP-289: propagate SSTORE refunds through precompile outputs
* fix(policy-registry): classify calldata before activation gate (BOP-378/PSRC-26)

Unknown selectors, short calldata, and write calls with malformed arguments
previously returned FeatureNotActivated when the policy registry was inactive,
masking the real error and making dispatch behavior activation-state-dependent.

Replace the is_view_selector / write-gate two-branch split with a single match
on the first 4 calldata bytes. Short calldata and unknown selectors return
UnknownFunctionSelector immediately; view selectors bypass the activation gate
and reach inner directly; known write selectors are ABI-validated before
ensure_activated is called so AbiDecodeFailed is always surfaced regardless of
activation state.

Add regression tests for all three cases: inactive unknown selector, malformed
view selector, and malformed write selector.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(precompile-storage): deduct gas before sload warms slot in journal (BOP-380)

`internals.sload` marks the slot warm in the revm journal before gas is
deducted. If either `deduct_gas` call then returns `OutOfGas` the spurious
warm entry persists, causing a subsequent cold read of the same slot to be
billed at warm cost instead of cold cost, breaking EIP-2929 accounting.

Apply the same checkpoint/revert pattern already used by `sstore`: take a
journal checkpoint before the read and revert it on any error so that a
failed OOG sload leaves the slot's cold/warm state unchanged.

Add a regression test that verifies an OOG sload does not warm the slot:
a second unlimited-gas read of the same slot must still pay the full cold
penalty.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* remove comment

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
* fix(precompile-macros): track args presence with a flag instead of emptiness check (base#3405)

The `args` option in `#[precompile]` used `!args.is_empty()` to detect
duplicates, which fails to catch `args(), args()` or `args(), args(x: u8)`
because an empty first occurrence leaves the vec empty. Replace the check
with an `args_seen` boolean flag, matching the `reject_duplicate` pattern
used by every other option (`id`, `storage`, `macro_path`, `install`).

Adds two regression tests covering both previously-undetected cases.

* fix(precompile-macros): forward all struct-level attrs through #[contract] (BOP-360) (base#3407)

* fix(precompile-macros): forward all struct-level attrs through #[contract] (BOP-360)

Previously gen_output kept only #[derive] attributes and silently
discarded doc comments, #[cfg], #[cfg_attr], #[allow], #[serde], etc.
Now all outer attributes except #[namespace] (the macro's own consumed
attribute) are threaded into the generated layout struct. The generic
fallback doc string is suppressed when the user supplies their own doc
comment, preventing silent replacement.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(precompile-storage): wrap offset_bytes in backticks in test doc comments

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix(precompile-storage): replace saturating/unchecked slot arithmetic with checked_add (BOP-356) (base#3415)

* fix(precompile-storage): replace saturating/unchecked slot arithmetic with checked_add (BOP-356)

Cantina finding base#33: slot offset arithmetic used saturating_add (which
silently aliases overflowing offsets to U256::MAX) and unchecked + (which
panics in debug builds). Replace all slot additions with checked_add,
returning BasePrecompileError::SlotOverflow in Result contexts and using
expect in constructors/Index impls where returning Result would break
trait contracts.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(precompile-storage): apply nightly rustfmt

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix(precompile-storage): restore all mutable fields in checkpoint_revert (BOP-359)

HashMapStorageProvider::checkpoint_revert was only restoring `internals`
and `events`, silently leaving `transient`, `accounts`, `gas_refunded`,
and `state_gas_used` in their post-mutation state after a revert. This
caused the test mock to diverge from production EVM semantics, where all
state is fully rolled back on revert.

Expand `Snapshot` to capture all four missing fields and restore them in
`checkpoint_revert`. Add per-field regression tests to pin the behavior.

* fix(precompile-storage): complete checked-add migration for BOP-356

Fixes three remaining sites missed by the initial BOP-356 PR (base#3415):

- VecHandler::truncate: two bare + in packed-element branch now use
  checked_add().ok_or(SlotOverflow)?

- VecHandler: rename compute_handler to try_compute_handler returning
  Result<T::Handler> so callers can propagate SlotOverflow via ?.
  Adds get_or_try_insert / get_or_try_insert_mut to HandlerCache to
  support fallible initialization in cache-closure call sites.

- SetHandler::new: drops const fn, returns Result<Self> so the
  positions-slot arithmetic uses ok_or(SlotOverflow)? instead of
  expect(). StorableType::handle for Set<T> retains .expect() at the
  trait boundary since StorableType::handle is infallible by design.

- ArrayHandler::compute_handler: Index/IndexMut are infallible traits
  and cannot propagate Result; documents the invariant that base_slot
  is a keccak256 output (never U256::MAX) and N <= 32.

* fix(precompile-storage): bubble SlotOverflow from ArrayHandler instead of panicking

Remove Index/IndexMut impls and change compute_handler to try_compute_handler
returning Result, with at() and at_mut() returning Result<Option<>>. No
external callers used the Index operator on ArrayHandler.

* fix(precompile-storage): make SetHandler::new infallible via wrapping_add

SetHandler::new was checking for U256::MAX overflow via checked_add and
returning Result<Self>, requiring the StorableType::handle impl to call
.expect(). Changing handle() to propagate a Result would require modifying
the StorableType trait and all proc-macro-generated code, which is
disproportionate.

Use wrapping_add instead: slot addresses are keccak256 outputs uniformly
distributed over [0, 2^256), so U256::MAX is unreachable in practice.
This makes new() infallible, removing the expect() entirely.

* fix(precompile-storage): update tests to use at()/at_mut() instead of Index

* fix(precompile-storage): pass usize by value to at()/at_mut() in tests

* style: fix rustfmt in contract tests

* fix(precompile-storage): make SetHandler::new const fn

* fix(precompile-storage): replace wrapping_add with checked_add in SetHandler::new

* fix(precompile-storage): add missing import and remove duplicate test after cherry-pick

---------

Co-authored-by: Rayyan Alam <rayyan.alam@coinbase.com>
Co-authored-by: Claude <noreply@anthropic.com>
…it (BOP-350) (base#3406) (base#3454)

* fix(precompile-storage): remove unused token arg from checkpoint_commit (BOP-350)

alloy-evm's checkpoint_commit takes no token; the JournalCheckpoint arg
was silently discarded in EvmPrecompileStorageProvider, making the trait
API misleading. Drop the parameter from the trait and both implementations.
HashMapStorageProvider now asserts non-empty snapshots internally instead.

Generated with Claude Code



* fix(precompile-storage): drop unused cp binding in checkpoint test

Generated with Claude Code



---------

Co-authored-by: Claude <noreply@anthropic.com>
…e Sepolia and Mainnet (BOP-382) (base#3463)

* fix(chains): update B20 activation admin addresses for Base Sepolia and Mainnet

Replaces the old admin addresses with the newly provisioned coreKMS keys
for activation testing and smoke tests on Base Sepolia and Base Mainnet.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(chainspec): update activation_admin_matches_chain_config test addresses

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
* fix(precompile-macros): improve #[contract] macro diagnostic for unrecognized arguments (base#3388)

The error for unrecognized argument keys now names the unknown key and
lists the only supported argument, addr = "0x...". The previous message
"only `addr` attribute is supported" also silently accepted `address` as
an alias; that alias is removed so the canonical form is enforced.

Fixes BOP-346 (PSRC base#25).

* refactor(b20-asset, activation): remove unreachable announcement guard and redundant AlreadyDeactivated error (base#3253)

* refactor(b20-asset): remove unreachable in_announcement guard

The is_announcement_active() check in announce() could never fire:
begin_announcement() is called after the check (so the flag is always
false when the guard runs), and the only re-entry vector — the inner
call loop — already rejects the announce selector before dispatch.

The flag also had no reset path, leaving the token permanently stuck
with in_announcement=true after announce() completed. The selector
check in the inner loop is the sole recursion defense, matching the
MockB20Asset reference implementation which carries no flag at all.

Remove in_announcement, is_announcement_active(), and begin_announcement()
from B20AssetToken, and drop the dead guard and call-site from dispatch.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(activation): remove redundant AlreadyDeactivated error

AlreadyDeactivated and FeatureNotActivated both represent the same
underlying state (features[feature] == false). The intended distinction
between mutation context (deactivate) and query context (checkActivated)
was never implemented: the mock collapses both into FeatureNotActivated.

Remove AlreadyDeactivated from the ABI to eliminate the dead declaration
and bring the Rust interface in line with the updated IActivationRegistry
spec and MockActivationRegistry behavior. Also clarifies set_activated
variable names to make the activate/deactivate branch intent explicit.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* style: apply rustfmt to activation/storage.rs

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: clippy

* style: apply rustfmt to activation/storage.rs

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* fix(chainspec,activation): reject zero activation admin address (base#3344)

* fix(chainspec,precompiles): reject zero activation admin address

Closes a backdoor where a chain misconfigured with
activation_admin_address == Some(Address::ZERO) would allow any deposit
transaction with msg.sender == Address::ZERO to toggle Beryl activation
state.

Layer 1 (config-time): add ZeroActivationAdminAddress error variant to
BaseChainSpecError and check for it in validate_beryl_activation_admin,
covering try_from_genesis, try_from_chainspec, and try_build paths.

Layer 2 (runtime defense-in-depth): add an unconditional zero-caller
guard at the top of set_activated in ActivationRegistryStorage so that
Address::ZERO callers are always rejected, even if a future code path
were to bypass config-time validation.

Adds tests for both layers.

* docs(activation): clarify admin() returns Address::ZERO to mean no admin, not a valid zero admin

Address::ZERO returned from admin() signals that no activation admin is
configured (activation_admin_address was None). Config-time validation
already rejects Some(Address::ZERO), so the zero return cannot mean a
configured admin in a valid chain spec. Add a doc comment making this
semantic explicit so future callers do not conflate the two cases.

* style(chainspec): move Address import to mod-level in tests

Two new test functions placed `use alloy_primitives::Address` inside
their function bodies. Move it to the existing module-level
alloy_primitives import block per project import placement rules.

* refactor(chainspec): extract beryl_scheduled local in validate_beryl_activation_admin

The Beryl fork condition was evaluated twice, once for the missing-admin
check and once for the zero-admin check. Extract it into a single
beryl_scheduled binding so both guards share the same expression and
cannot drift independently.

* fix(chainspec,precompiles): run cargo fmt, fix em dash, update stale doc comment

- Run cargo +nightly fmt to fix the ci/Format failure (the beryl_scheduled
  refactor commit was not formatted before pushing)
- Replace em dash with colon in admin() doc comment per project convention
- Update validate_beryl_activation_admin doc to describe both guards (missing
  and zero address) rather than only the original missing-address check

* refactor(chainspec): make ChainConfig.activation_admin_address required

All Base chains have Beryl scheduled and need an activation admin.
Using Address instead of Option<Address> lets the type reflect this;
Address::ZERO is still rejected by validate_beryl_activation_admin.

The missing-admin test for ChainConfig is removed since that code path
is now impossible. The genesis and builder paths retain Option<Address>
to handle JSON deserialization where the field may be absent.

* fix(chainspec,proof): update callsites for ChainConfig.activation_admin_address: Address

Three callsites that treated activation_admin_address as Option<Address>:
- boot.rs: and_then -> map to produce Option<Address> from the now-bare Address
- boot.rs test assert: wrap chain_config value in Some() for comparison
- succinct client boot.rs: wrap in Some() when assigning to BootInfo field
- spec.rs test: assign Address::ZERO directly instead of Some(Address::ZERO)

* fix(chains): add backticks to Address::ZERO in doc comment

* fix(precompile-storage): remove redundant drop in with_caller and harden CallerGuard (base#3404)

* fix(precompile-storage): remove redundant drop in with_caller and harden CallerGuard::drop

The explicit `let result = f(); drop(guard); result` pattern in `with_caller`
was redundant since `CallerGuard`'s `Drop` impl already restores the previous
caller on scope exit. Simplify to `let _guard = ...; f()` so RAII is the
sole restore mechanism.

Additionally, `CallerGuard::drop` previously called `with_storage` which uses
`RefCell::borrow_mut()` and panics on a conflicting borrow. This could abort
the process if `drop` ran during unwinding from a panic inside a borrowed
storage scope. Switch to `try_borrow_mut` so a conflicting borrow silently
skips the restore instead of panicking inside `Drop`.

Fixes BOP-337.

* fix(precompile-storage): harden CheckpointGuard::drop against double-panic

Apply the same try_borrow_mut hardening from CallerGuard::drop to
CheckpointGuard::drop. Using with_storage (which calls borrow_mut
internally) risks a second panic during unwinding if the RefCell is
already mutably borrowed, causing a process abort. Silently skipping
the revert on a conflicting borrow is the safe alternative.

* style(precompile-storage): collapse nested if-let in CallerGuard and CheckpointGuard drop

* docs(precompile-storage): document commit vs drop borrow strategy in CheckpointGuard

* fix(precompile-storage): replace let chains with nested if-let for edition compat

* feat(precompile-storage): add tracing::warn in CallerGuard and CheckpointGuard drop failure paths

* chore: update Cargo.lock for tracing dependency

* fix(precompile-storage): propagate tracing/std through std feature

* [backport] straggler fixes for releases/v1.1.0

Applies remaining small fixes and doc/test cleanups that were present
on main but missing from releases/v1.1.0 after the batch backports:

- set.rs: em-dash in test doc comment
- vec.rs: truncate/clear tests from base#3384
- activation/storage.rs: test ordering alignment
- b20_asset/token.rs: batch_mint authorization boundary doc (base#3367)
- b20_stablecoin/dispatch.rs: BOP-349/PSRC-27 reference in comment
- metrics.rs: use ctx.result_output() instead of into_precompile_result()

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Eric Liu <ericliu121187@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
* chore: update reth to v2.3.0

Align the workspace with the reth v2.3.0 release and migrate the related alloy and revm API changes.

Also fix the follow-up clippy and test breakages surfaced by the requested Justfile validation targets.

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* Fix reth 2.3 CI fallout

Persist test-harness canonical blocks immediately so follow-up payloads can resolve parent headers under reth 2.3.0, and update Jovian fixture headers plus deny skips for the new dependency graph.

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* Drop dead BAL post-execution check

Reth 2.3.0 added a BAL hash argument to post-execution consensus validation for Amsterdam-capable Ethereum paths. Base does not have an execution-derived BAL hash to compare, so stop threading the header value through and remove the tautological check.

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* Keep BAL hash at the trait boundary only

Accept the reth 2.3.0 BAL hash argument in the Base consensus trait impl, but stop threading it through Base-only helpers and tests where it is unused.

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* Fix rustfmt fallout in engine-tree validator

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* Use branch-based base-anvil workflow checkouts

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* Align release-branch precompile tests with reth 2.3

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* Refresh backport lockfile

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* Fix Jovian blob gas mismatch test fixture

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* ci: use default base-anvil branch

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* test(runner): avoid finalizing harness blocks

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* test(runner): trim harness persistence workaround

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* docs(execution): clarify reth 2.3 migration notes

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* fix(consensus): drop post-exec requests hash check

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* refactor(reth): trim non-essential migration behavior

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* ci: revert workflow-only reth migration changes

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* refactor(engine-tree): inline overlay builder construction

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

* ci: bump base-anvil workflow pin

Amp-Thread-ID: https://ampcode.com/threads/T-019eb1f4-e95e-7346-b575-6af1be02f038
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
* Set Base EL peer defaults to 80

Apply Base-specific execution peer defaults in the execution CLI runtime config so EL nodes default to 80 inbound peers and 80 outbound peers when operators do not pass explicit overrides.

Add regression tests to verify the new defaults and preserve explicit CLI values.

Amp-Thread-ID: https://ampcode.com/threads/T-019eb8fc-371b-71ab-901d-499925b7f017
Co-authored-by: Amp <amp@ampcode.com>
(cherry picked from commit 41b523d)

* Fix rustfmt import order in backport

Amp-Thread-ID: https://ampcode.com/threads/T-019eb8fc-371b-71ab-901d-499925b7f017
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>
Conflicts resolved:
- Cargo.toml: version bumped to 1.1.0, reth v2.2.0→v2.3.0, revm/alloy
  version bumps; preserved firehose-only deps (reth-db-models,
  reth-tokio-util, alloy-rpc-types-trace)
- crates/common/chains/src/chain.rs: beryl activation test updated to
  upstream values (sepolia scheduled, zeronet timestamp updated)
- crates/common/chains/src/config.rs: zeronet beryl timestamp updated
  to 1_780_678_800, added cobalt_timestamp field
Upstream v1.1.0 includes a backport of reth v2.3.0, which is
binary-incompatible with reth-firehose (built against reth v2.2.0 /
revm-inspector v19). Reverted all reth-v2.3.0-specific API changes to
keep the workspace on reth v2.2.0 (streamingfast/reth firehose/2.x)
while still landing the v1.1.0 upstream features (Beryl/Cobalt
hardfork plumbing, chain config additions).

Also adapts the firehose-flashblocks streamer for the upstream
FlashblocksSubscriber API change (added ping_interval parameter).
Use the final streamingfast/reth v2.3.0-fh tag (reth v2.3.0, revm 40,
alloy-evm 0.36) instead of the intermediate alpha. Patch alloy-evm to the
SF fork so EVM system calls route through the Inspector, restoring Firehose
tracing of the EIP-4788 beacon-roots system call.
@sduchesneau sduchesneau requested a review from maoueh June 15, 2026 19:03
@sduchesneau sduchesneau merged commit e007744 into firehose/1.x Jun 15, 2026
6 checks passed
@sduchesneau sduchesneau deleted the bump_1_1_0 branch June 15, 2026 21:06
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.