Skip to content

feat!(ethexe): malachite#5419

Merged
grishasobol merged 119 commits into
masterfrom
gsobol/ethexe/malachite-new
May 25, 2026
Merged

feat!(ethexe): malachite#5419
grishasobol merged 119 commits into
masterfrom
gsobol/ethexe/malachite-new

Conversation

@grishasobol
Copy link
Copy Markdown
Member

@grishasobol grishasobol commented May 11, 2026

Closes #5434

grishasobol and others added 20 commits May 11, 2026 10:12
…oundation)

Restores the foundational types from commit 4138374:
- ReplyInfo::to_hash() in gear-core
- PromiseEmissionMode in ethexe-common primitives
- CompactPromise / SignedCompactPromise + helpers in ethexe-common
- Mock impls for Promise
- InjectedStorage{RO,RW} extended with promise/compact_promise getters and setters
- Database/RawDatabase impls for the new storage methods

This is the foundation that downstream pieces (processor BoundPromiseSink,
compute PromisePolicy wiring, modular RPC injected API, gossipsub compact-promise
topic) build on. The marker comments for those follow-ups remain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apply +_+_+ rename markers across Rust + Solidity sources:
- `AnnouncesCommitted{,Event}` → `MBCommitted{,Event}` (Rust struct, enum variant, Solidity event)
- `LastAdvancedEthBlockCommitted{,Event}` → `EBCommitted{,Event}` (Rust struct, enum variant, Solidity event)
- `ANNOUNCES_COMMITTED` → `MB_COMMITTED`, `LAST_ADVANCED_ETH_BLOCK_COMMITTED` → `EB_COMMITTED` (constants)
- `announces_committed` → `mb_committed`, `last_advanced_eth_block_committed` → `eb_committed` (methods)
- `last_committed_advanced_eth_block` → `last_committed_eb` (field in BlockMeta / PreparedBlockData)
- `last_advanced_block` → `last_advanced_eb` (field in MbMeta)
- `latest_synced_block` → `latest_synced_eb`, `latest_prepared_block_hash` → `latest_prepared_eb_hash` (DBGlobals)
- `CompactBlock` → `CompactMB` (struct)
- camelCase counterparts in Solidity

Forge build regenerated ABI JSON for Router/Mirror/Middleware/POAMiddleware/WrappedVara.
`ensure_types_unchanged` hash bumped to reflect the type-name changes (SCALE encoding unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The module owns Malachite-sequencer application types (`Transaction`,
`Transactions`, etc.) so `malachite` is the more accurate name. Updates
all import paths and the `ensure_types_unchanged` hash for the new
module path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The validator state more accurately is the "idle" state (waiting for
chain head, then sync, then prepare before electing a role). Renames:
- module `wait_for_eth_block` → `idle`
- struct `WaitForEthBlock` → `Idle`
- display tag `WAIT_FOR_ETH_BLOCK` → `IDLE`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The rate-stepping promise-latency experiment runner lives on a separate
branch now; drop it from the main branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a `Migration` trait and `migrate()` driver that walks ascending
schema versions in `migrations()`. The vector is empty for now — the
old v1/v2/... entries had no live consumers and were removed earlier.

Wire `migrate()` into `initialize_db()` so an on-disk version below
`LATEST_VERSION` runs the framework instead of bailing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The handler now reads `mb_program_states(at)` and returns the set of
`ActorId`s present there, instead of warning and returning an empty
response. Added `MbStorageRO` to the `DbSyncDatabase` trait so the
network layer can reach the MB stores.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The MB-driven path has no active consumers of db_sync responses: every
incoming response was being logged-and-dropped, and every failure was
just re-issued forever. Drop the `network_fetcher` future, the
`Event::Fetching` variant, and their imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renumbers the test list to mirror master's ordering for tests common to
both branches; new MB-specific tests (`multiple_validators_ping`,
`reorg_within_quarantine`, `reorg_deeper_than_quarantine`) slot in
where their thematic neighbors live.

Also drop the now-stale `Event::Fetching` variant from the
`TestingEvent` mirror — it was already gone from the runtime enum.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`MalachiteService::new` now takes `Option<PublicKey>`:
- `Some(pk)` → `NodeRole::Validator`, validator key extracted from the signer.
- `None` → `NodeRole::FullNode`, ephemeral secret used only as the libp2p peer identity.

The service's call site no longer skips Malachite when no validator key
is configured, so connect/full nodes also join the gossip mesh and
receive proposals + sync responses. Test harness call sites updated to
pass `Some(pub_key)`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move `db.set_injected_transaction(tx)` into `InjectedTxMempool::insert`
so the network and RPC handlers don't have to remember to do it
themselves. The two service-side call sites lose the duplicated
persistence calls.

Persistence happens before pool insertion so a producer that picks the
tx on the very next round is guaranteed to find it via
`injected_getTransactions`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`ethexe check --computation` now walks `latest_finalized_mb_hash`
back through `CompactMB.parent` and asserts that every MB has its
cached `mb_program_states` / `mb_outcome` / `mb_schedule` records
plus `MbMeta { computed: true }`.

Re-execution through the processor (asserting cached records match
fresh execution) is intentionally left out — it requires loading
every code blob and reconstructing the runtime, which the CLI doesn't
have the context for.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…k entry points

Add `StateDump::collect_from_mb_storage(storage, mb_hash, block_hash)`
as the malachite-native entry point — state lives per-MB, so callers
that already know which MB to dump shouldn't have to round-trip through
`BlockMeta::last_committed_mb`. The existing
`collect_from_storage(block_hash)` becomes a thin wrapper that derives
the MB and forwards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`WaitForUploadCode`, `WaitForProgramCreation`, and `WaitForReplyTo`
each had an inline `select! { sleep => evm_mine, … = wait => break }`
loop that duplicated the `KickingStream::find_map` kick mechanism.

Each `wait_for` now hands the `(provider, block_time*3)` kick to the
receiver via `set_kicks` before consuming it with
`filter_map_block_synced`; the resulting `KickingStream` triggers
`evm_mine` on idle automatically.

`KickExt` retains its `EventReceiver` impl so `WaitFor*` can mutate
kicks pre-conversion. Replaced the marker on `extend_malachite_endpoints`
with a TODO note — pulling endpoint setup forward into `TestEnv::new`
needs a wider tests refactor and is left for a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promotes `apis/injected.rs` to `apis/injected/` and splits it into:

- `mod.rs` — module entry, public re-exports
- `trait.rs` — JSON-RPC trait definition (`Injected`)
- `promise_manager.rs` — `PromiseSubscriptionManager` owns the
  `tx_hash → oneshot::Sender` map plus `try_register_subscriber`,
  `cancel_registration`, and `dispatch_promise`
- `relay.rs` — `TransactionsRelayer` does the per-validator fan-out
  and the single-recipient fallback when the era's validator vector
  isn't known yet
- `spawner.rs` — `spawn_pending_subscriber` bridges a registered
  subscriber to a jsonrpsee subscription sink with timeout / cleanup
- `server.rs` — `InjectedApi` orchestrates all of the above

Adds the `injected_getTransactionPromise` method that reads
`db.promise(tx_hash)` and `db.compact_promise(tx_hash)` and
reconstructs the `SignedPromise` via `SignedCompactPromise::restore`.
This is the user-facing payoff of the CompactPromise primitives added
in the foundation commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces every `Option<mpsc::UnboundedSender<Promise>>` field /
parameter in the processor with `Option<BoundPromiseSink>`. The sink
wraps an `UnboundedSender<(H256, Promise)>` and pre-binds each send to
the MB hash the executor is currently working on, so worker threads
no longer have to thread the binding through manually.

Ripples:
- `host::threads::ThreadParams.promise_out_tx` → `promise_sink`
- `clear_promise_out_tx` → `clear_promise_sink`
- `CommonRunContext.promise_out_tx` → `promise_sink`
- compute's per-MB channel now carries `(H256, Promise)`; the
  `MbPromisesStream` reads the hash from each message instead of
  storing it in a field
- compute constructs `BoundPromiseSink::new(sender, target_hash)` for
  the target MB and passes `None` for predecessor MBs (unchanged
  semantics)
- processor unit tests upgrade their channel to `(H256, Promise)` and
  wrap senders in `BoundPromiseSink::new(_, H256::zero())`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…h compute_mb

Closes the remaining `+_+_+` marker on `MbComputeRequest`:
- `MbComputeRequest` gains a `promise_policy: PromisePolicy` field
- `ComputeSubService::receive_mb(mb_hash, policy)` records it
- `compute()` constructs a `BoundPromiseSink` for the target MB only
  when `policy == Enabled`, preserving the rule that ancestor MBs
  always replay with promises disabled
- `ComputeService` carries a `PromiseEmissionMode` (default
  `ConsensusDriven`) and offers `with_promise_mode(...)`. In
  `AlwaysEmit` it overrides the per-call policy to `Enabled`
- service-side `compute_mb` call passes `PromisePolicy::Enabled`
  for `MalachiteEvent::BlockProposal` (consensus may still narrow
  it via the emission mode)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `cargo fmt --all` across all touched files (line-wrap
  reorganisation only).
- `Migration::from_version` → `source_version` (clippy:
  `wrong_self_convention`).
- ProgramIds db-sync handler uses `BTreeMap::into_keys()` instead of
  `into_iter().map(|(k, _)| k)` (clippy: `iter_kv_map`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@semanticdiff-com
Copy link
Copy Markdown

semanticdiff-com Bot commented May 11, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  ethexe/ethereum/abi/WrappedVara.json  100% smaller
  ethexe/ethereum/abi/BatchMulticall.json  100% smaller
  ethexe/ethereum/abi/POAMiddleware.json  99% smaller
  ethexe/ethereum/abi/Middleware.json  99% smaller
  ethexe/ethereum/abi/DemoCaller.json  98% smaller
  ethexe/ethereum/abi/Gear.json  96% smaller
  ethexe/ethereum/abi/Mirror.json  96% smaller
  ethexe/sdk/src/mirror.rs  79% smaller
  ethexe/db/src/visitor.rs  74% smaller
  ethexe/observer/src/utils.rs  72% smaller
  ethexe/prometheus/src/lib.rs  71% smaller
  ethexe/processor/src/promise.rs  56% smaller
  ethexe/db/src/iterator.rs  51% smaller
  ethexe/ethereum/src/abi/gear.rs  51% smaller
  ethexe/processor/src/lib.rs  48% smaller
  ethexe/processor/src/handling/run/mod.rs  47% smaller
  ethexe/rpc/src/tests.rs  46% smaller
  ethexe/compute/src/prepare.rs  44% smaller
  ethexe/common/src/gear.rs  40% smaller
  ethexe/db/src/dump/collect.rs  40% smaller
  ethexe/db/src/database.rs  39% smaller
  ethexe/network/src/lib.rs  36% smaller
  ethexe/ethereum/src/abi/events/router.rs  33% smaller
  ethexe/network/src/validator/topic.rs  32% smaller
  ethexe/processor/src/tests.rs  32% smaller
  ethexe/rpc/src/apis/block.rs  28% smaller
  ethexe/compute/src/service.rs  28% smaller
  ethexe/processor/src/handling/overlaid.rs  27% smaller
  ethexe/common/src/db.rs  27% smaller
  ethexe/common/src/utils.rs  26% smaller
  ethexe/consensus/src/validator/batch/types.rs  26% smaller
  ethexe/common/src/consensus.rs  25% smaller
  ethexe/db/src/verifier.rs  25% smaller
  ethexe/common/src/injected.rs  24% smaller
  ethexe/ethereum/src/router/mod.rs  23% smaller
  ethexe/consensus/src/validator/batch/tests.rs  21% smaller
  ethexe/consensus/src/validator/batch/manager.rs  21% smaller
  ethexe/service/src/tests/utils/events.rs  20% smaller
  ethexe/consensus/src/validator/batch/filler.rs  19% smaller
  ethexe/cli/src/params/node.rs  18% smaller
  ethexe/db/src/migrations/init.rs  18% smaller
  ethexe/cli/src/commands/check.rs  16% smaller
  ethexe/service/src/tests/utils/env.rs  14% smaller
  ethexe/common/src/mock.rs  14% smaller
  ethexe/rpc/src/apis/program.rs  13% smaller
  ethexe/common/src/events/router.rs  13% smaller
  ethexe/db/src/migrations/migration.rs  13% smaller
  ethexe/common/src/lib.rs  12% smaller
  ethexe/observer/src/lib.rs  12% smaller
  ethexe/rpc/src/apis/injected/relay.rs  12% smaller
  ethexe/consensus/src/validator/mod.rs  11% smaller
  ethexe/compute/src/compute.rs  10% smaller
  ethexe/network/src/db_sync/mod.rs  9% smaller
  ethexe/network/src/db_sync/responses.rs  9% smaller
  ethexe/consensus/src/validator/batch/utils.rs  9% smaller
  ethexe/db/src/migrations/v5.rs  7% smaller
  ethexe/service/src/tests/mod.rs  6% smaller
  ethexe/consensus/src/validator/core.rs  6% smaller
  ethexe/consensus/src/validator/coordinator.rs  5% smaller
  ethexe/observer/src/sync.rs  4% smaller
  ethexe/runtime/common/src/transitions.rs  4% smaller
  ethexe/service/tests/smoke.rs  4% smaller
  ethexe/common/src/network.rs  3% smaller
  ethexe/consensus/src/utils.rs  3% smaller
  ethexe/rpc/src/utils.rs  3% smaller
  ethexe/network/src/db_sync/requests.rs  3% smaller
  ethexe/service/src/config.rs  3% smaller
  ethexe/common/src/primitives.rs  2% smaller
  ethexe/consensus/src/validator/participant.rs  1% smaller
  ethexe/node-loader/src/batch.rs  1% smaller
  ethexe/cli/src/params/network.rs  1% smaller
  ethexe/compute/src/tests.rs  1% smaller
  ethexe/processor/src/handling/run/chunk_execution_spawn.rs  1% smaller
  .config/nextest.toml Unsupported file format
  CLAUDE.md Unsupported file format
  Cargo.lock Unsupported file format
  Cargo.toml Unsupported file format
  ethexe/cli/Cargo.toml Unsupported file format
  ethexe/cli/src/commands/dump.rs  0% smaller
  ethexe/cli/src/commands/malachite.rs  0% smaller
  ethexe/cli/src/commands/mod.rs  0% smaller
  ethexe/cli/src/params/malachite.rs  0% smaller
  ethexe/cli/src/params/mod.rs  0% smaller
  ethexe/common/src/malachite.rs  0% smaller
  ethexe/compute/Cargo.toml Unsupported file format
  ethexe/compute/src/lib.rs Unsupported file format
  ethexe/consensus/Cargo.toml Unsupported file format
  ethexe/consensus/src/announces.rs  0% smaller
  ethexe/consensus/src/connect/mod.rs  0% smaller
  ethexe/consensus/src/lib.rs Unsupported file format
  ethexe/consensus/src/mock.rs  0% smaller
  ethexe/consensus/src/tx_validation.rs  0% smaller
  ethexe/consensus/src/validator/idle.rs  0% smaller
  ethexe/consensus/src/validator/initial.rs  0% smaller
  ethexe/consensus/src/validator/mock.rs  0% smaller
  ethexe/consensus/src/validator/producer.rs  0% smaller
  ethexe/consensus/src/validator/subordinate.rs  0% smaller
  ethexe/consensus/src/validator/tx_pool.rs  0% smaller
  ethexe/contracts/src/IRouter.sol Unsupported file format
  ethexe/contracts/src/Router.sol Unsupported file format
  ethexe/contracts/src/libraries/Gear.sol Unsupported file format
  ethexe/contracts/test/Base.t.sol Unsupported file format
  ethexe/db/Cargo.toml Unsupported file format
  ethexe/db/src/dump/mod.rs  0% smaller
  ethexe/db/src/migrations/mod.rs Unsupported file format
  ethexe/db/src/migrations/v1.rs  0% smaller
  ethexe/db/src/migrations/v2.rs  0% smaller
  ethexe/db/src/migrations/v3.rs  0% smaller
  ethexe/db/src/migrations/v4.rs  0% smaller
  ethexe/ethereum/abi/Router.json Unsupported file format
  ethexe/ethereum/src/router/events.rs Unsupported file format
  ethexe/malachite/core/Cargo.toml Unsupported file format
  ethexe/malachite/core/src/app.rs  0% smaller
  ethexe/malachite/core/src/codec.rs Unsupported file format
  ethexe/malachite/core/src/config.rs  0% smaller
  ethexe/malachite/core/src/context.rs  0% smaller
  ethexe/malachite/core/src/externalities.rs  0% smaller
  ethexe/malachite/core/src/lib.rs  0% smaller
  ethexe/malachite/core/src/service.rs  0% smaller
  ethexe/malachite/core/src/signing.rs  0% smaller
  ethexe/malachite/core/src/state.rs  0% smaller
  ethexe/malachite/core/src/store.rs  0% smaller
  ethexe/malachite/core/src/streaming.rs  0% smaller
  ethexe/malachite/core/src/types.rs  0% smaller
  ethexe/malachite/core/tests/multi_validators.rs  0% smaller
  ethexe/malachite/service/Cargo.toml Unsupported file format
  ethexe/malachite/service/src/config.rs  0% smaller
  ethexe/malachite/service/src/externalities.rs  0% smaller
  ethexe/malachite/service/src/lib.rs  0% smaller
  ethexe/malachite/service/src/mempool.rs  0% smaller
  ethexe/malachite/service/src/quarantine.rs  0% smaller
  ethexe/malachite/service/src/service.rs  0% smaller
  ethexe/malachite/service/tests/restart_resilience.rs  0% smaller
  ethexe/network/Cargo.toml Unsupported file format
  ethexe/observer/Cargo.toml Unsupported file format
  ethexe/observer/src/tests.rs Unsupported file format
  ethexe/rpc/src/apis/injected/promise_manager.rs  0% smaller
  ethexe/rpc/src/apis/injected/server.rs  0% smaller
  ethexe/rpc/src/lib.rs  0% smaller
  ethexe/scripts/start-local-network.sh Unsupported file format
  ethexe/service/Cargo.toml Unsupported file format
  ethexe/service/src/fast_sync.rs Unsupported file format
  ethexe/service/src/lib.rs Unsupported file format
  ethexe/service/src/tests/utils/mod.rs  0% smaller

grishasobol and others added 9 commits May 11, 2026 14:56
…IONS

Splits the migrations module back into the historical shape:

- `migration.rs` — `Migration` trait (now `Sync` so the slice can be
  `&'static`). `migrate(&InitConfig, &RawDatabase)` returns a pinned
  boxed future so `&dyn Migration` is dyn-compatible.
- `v5.rs` — schema-version anchor. Holds `pub const VERSION: u32 = 5;`
  and nothing else. Future schema bumps add `v6.rs`, `v7.rs`, … each
  with their own `VERSION` and (where applicable) a
  `migration_from_v(N-1)` function.
- `mod.rs` — re-introduces the user-requested constants:
    pub const OLDEST_SUPPORTED_VERSION: u32 = v5::VERSION;
    pub const LATEST_VERSION: u32 = v5::VERSION;
    pub const MIGRATIONS: &[&dyn Migration] = &[];
    const _: () = assert!(
        (LATEST_VERSION - OLDEST_SUPPORTED_VERSION) as usize == MIGRATIONS.len(),
        "Wrong number of migrations available"
    );

  The const-assert guarantees every step in the supported version
  range has a corresponding migration entry — adding a new schema
  version without a migration step (or vice versa) now fails to
  compile. Both anchor constants point at `v5::VERSION` for now;
  splits into distinct OLDEST/LATEST anchors when the next migration
  lands.

- `init.rs` — `migrate(&config, &db).await` (was `migrate(&db)`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`EventReceiver<T>` no longer carries a `kicks: Option<…>` field nor
implements `KickExt`. Kicks live exclusively on the `KickingStream<S>`
wrapper, and `TestingEventReceiver` / `ObserverEventReceiver` are now
type aliases for `KickingStream<EventReceiver<…>>`.

Side effects:
- `channel<T>(db, kicks)` returns `(EventSender, KickingStream<EventReceiver<T>>)`.
- `KickingStream` gains `Debug + Clone` derives (the inner
  `EventReceiver` is `Debug + Clone`) and forwards `FusedStream` from
  its inner stream.
- `TestingEventReceiver::find_map_with_db` and `ObserverEventReceiver::
  filter_map_*` switch from `self.db`/`self.kicks` field access to
  `self.db()`/`self.take_kicks()` helpers on the wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s builder

`WaitForUploadCode`, `WaitForProgramCreation`, and `WaitForReplyTo`
no longer carry an `Option<(RootProvider, Duration)> hack` field —
the receiver itself is now a `KickingStream`, so the kick lives there.

Each waiter gains a public
`fn with_kicks(self, kicks: Option<(Duration, RootProvider)>) -> Self`
builder that forwards into `receiver.set_kicks` / `clear_kicks`.
`force_mine_hack` is renamed to `default_wait_kicks` and produces the
`(block_time * 3, provider)` tuple in the shape the kick consumes
directly — no more in-call `* 3` math inside `wait_for`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
200 corner-case / hack tests exercised the public APIs of
`ethexe-malachite-core`, `ethexe-malachite`, `ethexe-consensus`, and
`ethexe-service`. Every iteration ran a fresh test; passing tests are
listed here so future iterations don't repeat coverage. All 200 passed;
no test code remains in the working tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The black-box test log lives alongside the working tree; no need to
ship it through git.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`ethexe check --computation` now re-executes every persisted MB
through a fresh `Processor` and asserts the cached
`mb_program_states` / `mb_outcome` / `mb_schedule` match the fresh
run — instead of just asserting the cached records exist.

To avoid duplicating the live-pipeline's parent-state lookup, expose
`ethexe_compute::prepare_executable_for_mb(db, mb_hash)`. It resolves
the parent MB's program states / schedule / `last_advanced_eb`, reads
the MB's `Transactions` blob, and assembles the same `ExecutableData`
the `compute_mb` sub-service feeds to the processor. The CLI runs it
over `processor.clone().overlaid()` so re-execution writes don't
touch on-disk state.

Adds `--chunk-size` (default 2) for the verification `Processor`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restores the wiring from #5377: when this node carries an RPC server,
force `ComputeService` into `PromiseEmissionMode::AlwaysEmit` so the
RPC subscribers see promises regardless of the per-MB consensus
decision. Pure validator/peer nodes stay on the default
`ConsensusDriven` and let the consensus layer pick the policy per MB.

Without this, an RPC node running alongside a non-emitting validator
role would drop replies on the floor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The service-level wiring (`ComputeService::with_promise_mode`)
translated the per-MB policy for the *target* but the SubService still
silenced every walked predecessor unconditionally. An RPC node
catching up in `AlwaysEmit` therefore missed the replies for every
ancestor MB it had to re-execute.

- `ComputeSubService` gains a `promise_emission_mode` field plus a
  `with_promise_mode(db, processor, mode)` constructor; `new` keeps
  defaulting to `ConsensusDriven`
- `ComputeService::with_promise_mode` forwards the mode through
- `compute()` builds a `BoundPromiseSink` per predecessor when the
  mode is `AlwaysEmit`; under `ConsensusDriven` predecessors stay
  silent as before (their promises were already gossiped)

The target MB still follows `req.promise_policy` (already translated
to `Enabled` for `AlwaysEmit` at the service layer), so the behaviour
of pure-validator nodes is unchanged.

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

Migrated existing TODO+_+_+ markers in streaming.rs / mempool.rs to issue
numbers and added new TODO comments at audit follow-up sites with linked
issues (#5473-#5482). No behavior change.
- Drop the empty doc-comment line that broke rustfmt between the
  `post_quarantine_delay` doc comment and its `TODO: #5478` follow-up.
- Remove `rand`, `rand_chacha`, `nonempty` from `ethexe-consensus`
  Cargo.toml — none are referenced by the crate's source.
`receive_eb_prepared` is `pub`; linking to `pub(crate)` items from its
doc-comment triggers rustdoc's `private_intra_doc_links` lint under
`-D warnings`. Replace the link with prose.
`crates-io` publish-verify swaps the default feature from `server` to
`client` before publishing, so the `pub trait Block` body is exposed to
the compiler without `feature = "server"` active. Move `BlockHeader`
and `BlockRequestEvent` imports out from under that cfg gate — they're
used in the trait method signatures regardless of which side
(server/client) the rpc macro generates.
@grishasobol
Copy link
Copy Markdown
Member Author

@claude opus review

@claude
Copy link
Copy Markdown

claude Bot commented May 19, 2026

Claude finished @grishasobol's task in 4m 13s —— View job


Opus Review (2nd pass) — feat!(ethexe): malachite

Re-reviewing after 20 commits since the prior pass. Status of prior findings, plus what's still open.


Status of prior findings

# Prior finding Status
1 Silent Ok(()) in try_include_checkpoint_chain_commitment partially fixed: last_committed_eb missing now hard-errors (utils.rs:301); block_header(advanced) missing and "didn't fit" path still silent — see below
2 SignedMagnitude::add_assign panic on overflow unresolved (utils.rs:451)
3 unreachable! in update_inner Err path unresolved (validator/mod.rs:171)
4 prepare_one_block non-atomic 2-write sequence unresolved (prepare.rs:355-366)
5 Streaming reaper / PartStreamsMap unbounded tracked: TODO #5473 with ignored regression test — acceptable
6 Checkpoint walk over-trusts peer-supplied last_advanced_eth_block unresolved (same code path as #1)
7 Codex: regression of last_advanced_eb in validator path fixed: is_strict_descendant_of enforced (externalities.rs:484-508) ✓

Also resolved during this revision: shape+ordering+gas-allowance check in validate_block_above, propose-timeout hang in app.rs (now drops reply sender on process_get_value error), checkpoint commitment loss on chain progress (cf45922), and the O(n²) batch-pack pass (5a90bb1).


severity: high — update_inner leaves inner = None on Err path

File: ethexe/consensus/src/validator/mod.rs:164-176

fn update_inner(&mut self, update: impl FnOnce(ValidatorState) -> Result<ValidatorState>) -> Result<()> {
    let inner = self.inner.take().unwrap_or_else(|| unreachable!("inner must be Some"));
    update(inner).map(|inner| { self.inner = Some(inner); })
}

take() consumes inner. When update(...) returns Err, the closure's .map is never invoked and self.inner stays None. The next context() / context_mut() call (e.g. lines 153, 160 or via role() at 181) hits unreachable!.

Every ConsensusService method (receive_new_chain_head, receive_synced_block, receive_prepared_block, receive_validation_request, receive_validation_reply) routes through update_inner with closures that propagate ? from DB and protocol code — any of those errors poisons the validator. The first error bubbles up cleanly; the second event the consumer dispatches panics in unreachable! instead of returning a clean error.

Either restore inner on the Err path (mem::replace with a sentinel Faulted state) or refactor the closure to (ValidatorState, Result<()>) so the state is always restored. The TODO note in Idle already hints that a permanently-faulted state is expected.

severity: high — SignedMagnitude::add_assign panics on adversarial transition aggregation

File: ethexe/consensus/src/validator/batch/utils.rs:447-452

fn add_assign(&mut self, other: Self, actor_id: ActorId) {
    match self.negative == other.negative {
        true => {
            self.value = self.value.checked_add(other.value).unwrap_or_else(|| {
                panic!("squashed transition value overflow for actor {actor_id:?}")
            });
        }

Reachable via squash_transitions_by_actor over StateTransitions sourced from MB payloads that the coordinator did not author. The current unit test test_squash_value_overflow_panics documents the panic — in production code path this means every honest participant crashes on the same input. Convert to Result and reject the offending commitment instead of crashing the validator process.

severity: medium — silent Ok(()) on missing block_header(advanced)

File: ethexe/consensus/src/validator/batch/utils.rs:291-297

let advanced = db.mb_meta(mb_head).last_advanced_eb;
if advanced.is_zero() { return Ok(()); }
let Some(advanced_header) = db.block_header(advanced) else {
    return Ok(());                       // silent skip
};

Inconsistent with the new hard-error at 301-303 (last_committed_eb missing despite prepared==true). By construction, if mb_meta(mb_head).last_advanced_eb is non-zero, the MB observed that EB during proposal/finalize (see EthexeExternalities::process_mb_proposal setting meta.last_advanced_eb = last_advanced), so the header must be locally present. A None here is the same invariant violation class — hard-error to surface real bugs.

Separately at lines 327-330, when the chain commitment doesn't fit:

if let Err(err) = batch_filler.include_chain_commitment(commitment) {
    tracing::trace!("checkpoint chain commitment didn't fit (...)");
}

If uncommitted_chain_len_threshold is later breached for this reason, validators will reject the next coordinator's batches and the network stalls with only a trace! to debug from. At minimum warn!, and consider surfacing as a BatchFillerError variant so the coordinator can choose a smaller payload.

severity: medium — prepare_one_block writes block-meta and globals non-atomically

File: ethexe/compute/src/prepare.rs:355-366

db.mutate_block_meta(block.hash, |meta| {
    meta.prepared = true;
    /* ... */
});
db.globals_mutate(|globals| {
    globals.latest_prepared_eb_hash = block.hash;
});

Two separate RocksDB writes. If the process dies between them, block_meta(at).prepared == true but globals.latest_prepared_eb_hash lags. Downstream code (Idle::process_prepared_block, the validator's coordinator selection at idle.rs:99-on) keys off prepared, so a recovery walk seeing prepared==true could pick a block ahead of latest_prepared_eb_hash. Use a single batch-write or document the recovery contract.

severity: low — TxValidityChecker::check_tx_validity bails on local-state miss inside producer loop

File: ethexe/malachite/service/src/tx_validity.rs:173-179

let Some(state) = self.db.program_state(destination_state_hash.hash) else {
    anyhow::bail!("program state not found for actor({}) by valid hash({})", ...);
};

In build_block_above (externalities.rs:270) this ? propagates and the proposer fails to produce a block for this round. The hash came from latest_states which the checker just loaded, so the only way to hit this is a transient lookup race (or DB corruption). The current behavior — abort the round, let the engine rotate — is probably fine, but worth a comment noting that bail is intentional and the alternative (skip the tx) was rejected.

severity: low — eb_touched_programs walk is intentionally unbounded but lacks a soft cap

File: ethexe/malachite/service/src/tx_validity.rs:328-346

The comment is candid: "The walk is intentionally unbounded ... bailing at start_block_hash is the safe fallback." Under a reorg deeper than canonical_quarantine, the walk runs from advanced_eb back to start_block_hash accumulating into chain: Vec<H256>. That's a one-shot O(chain_depth) allocation, not a persistent leak, but it's worth a defense-in-depth MAX_EB_WALK_DEPTH that errors rather than silently allocating an enormous Vec. Especially given this runs inside both validate_block_above and build_block_above per proposal.


Verified clean

Spot-checks worth adding before merge

  1. A state-machine test that drives update_inner through an Err closure and then dispatches a follow-up event — currently panics with unreachable!.
  2. A property test on SignedMagnitude::add_assign with adversarial inputs; assert no panic.
  3. A test where mb_meta(mb_head).last_advanced_eb is set but the corresponding block_header is purged — current behavior emits no checkpoint and reports success.

  • Re-fetch branch & survey changes since last opus review
  • Verify status of prior opus findings
  • Read malachite/core + consensus/validator/batch in detail
  • Check new findings (atomicity, streaming reaper, EB regression)
  • Post final review
    · branch gsobol/ethexe/malachite-new

grishasobol and others added 10 commits May 20, 2026 09:39
…hite-new

# Conflicts:
#	ethexe/consensus/src/announces.rs
#	ethexe/consensus/src/connect/mod.rs
#	ethexe/consensus/src/mock.rs
#	ethexe/consensus/src/tx_validation.rs
#	ethexe/consensus/src/validator/initial.rs
#	ethexe/consensus/src/validator/mock.rs
#	ethexe/consensus/src/validator/producer.rs
#	ethexe/consensus/src/validator/subordinate.rs
#	ethexe/consensus/src/validator/tx_pool.rs
#	ethexe/db/src/migrations/v1.rs
#	ethexe/db/src/migrations/v2.rs
#	ethexe/db/src/migrations/v3.rs
#	ethexe/db/src/migrations/v4.rs
The merge re-introduced stale `rand`/`rand_chacha`/`nonempty` entries
for `ethexe-consensus` in Cargo.lock that were already removed from its
Cargo.toml (commit 8cad9f4). Re-sync.
Files added unintentionally via `git add -A` in the previous merge
commit and now ignored to prevent re-adding:
- `ok.md` (local scratch notes — was already untracked in 6610c5b)
- `.claude/` (Claude Code internal state)
- `**/proptest-regressions/` (locally-generated proptest seed cache)
- `ethexe/contracts/broadcast/*/*/` (Foundry deployment logs for any
  chain-id; the existing 31337 carve-out is widened to all chain dirs,
  since broadcast records belong in deliberate deployment commits, not
  general merges).

Files stay on disk; only their git-tracking is removed.
The promise-emission match guard `if mb_hash == head_mb_hash` applied to
both `|` arms, so AlwaysEmit never emitted promises for parent-walked
predecessor MBs — an RPC node catching up silently dropped replies for
those MBs. Split the arms: AlwaysEmit now emits for every walked MB;
ConsensusDriven still emits only for the requested head.

Adds two compute-level emission-mode tests (real Processor + demo-ping).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Conflict in ethexe/processor/src/handling/run/mod.rs (imports only) resolved by
keeping master's expanded imports — they're required by the lazy
instrumentation flow introduced in #5396.

Also adapted the new `process_programs_instruments_valid_code_missing_current_runtime_instrumentation`
test in ethexe/processor/src/tests.rs to use malachite-new's
`setup_handler(db, height: u32)` and the 5-arg `process_queues(transitions,
height, timestamp, gas_allowance, None)` signatures.
@grishasobol grishasobol enabled auto-merge May 25, 2026 11:56
@grishasobol grishasobol added this pull request to the merge queue May 25, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks May 25, 2026
@grishasobol grishasobol added this pull request to the merge queue May 25, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 25, 2026
@grishasobol grishasobol added this pull request to the merge queue May 25, 2026
Merged via the queue into master with commit 7fc7044 May 25, 2026
185 of 187 checks passed
@grishasobol grishasobol deleted the gsobol/ethexe/malachite-new branch May 25, 2026 16:59
Comment thread ethexe/network/src/lib.rs
@@ -1066,6 +1048,8 @@ mod tests {
}

#[tokio::test]
#[ignore = "test setup populates the requester's data provider rather than the responder's; \
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does this even mean?

}

/// Buckets a message era can fall into relative to the snapshot era.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This literally has nothing to do with buckets

}

fn signer_with_pubkey() -> (PublicKey, Signer) {
fn signed_promise() -> SignedCompactPromise {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

mock(()) or arb_value::<T>() should be used instead

@@ -38,7 +38,8 @@ use std::{
pin::Pin,
task::{Context, Poll, ready},
};
use sync::ChainSync;
pub use sync::SyncError;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pub use and use must not be mixed in imports

let attempt = self.subscription_retry_attempt.saturating_add(1);
self.subscription_retry_attempt = attempt;
let backoff = std::time::Duration::from_millis(
(500u64.saturating_mul(1u64 << attempt.min(6))).min(30_000),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

<< is rudimentary way to calculate power of 2. Use pow instead.

};

fn init_tracing() {
static ONCE: Once = Once::new();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need for Once

use parity_scale_codec::{Decode, Encode};
use proptest::prelude::*;
use tempfile::TempDir;
use tokio::time::sleep;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Imports are always at the top

idx: usize,
}

fn arb_churn_events(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
fn arb_churn_events(
fn churn_events_strategy(

The strategy has nothing to do with aribtrary crate

Comment on lines +109 to +111
let Some(msg) = self.channels.consensus.recv().await else {
return Err(anyhow!("consensus channel closed"));
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
let Some(msg) = self.channels.consensus.recv().await else {
return Err(anyhow!("consensus channel closed"));
};
let msg = self.channels.consensus.recv().await.context("consensus channel closed")?;

externalities: Arc<EXT>,
}

impl<P, EXT> AppMsgHandler<P, EXT>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

https://rust-lang.github.io/api-guidelines/naming.html
Type parameters: concise UpperCamelCase, usually single uppercase letter: T

Suggested change
impl<P, EXT> AppMsgHandler<P, EXT>
impl<P, Ext> AppMsgHandler<P, Ext>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci: full Run full CI matrix (all platforms and profiles) scope: vara.eth Vara Ethereum application layer (L2) type: feat New functionality or user-visible improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace current consensus with Malachite

5 participants