Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2c27c72
initial
grishasobol Apr 24, 2026
ab4d2e2
wip(ethexe/malachite): rolling eth_head_history + quarantine anchor
grishasobol Apr 24, 2026
0af3991
refactor(ethexe/malachite): drive quarantine anchor from the shared D…
grishasobol Apr 24, 2026
e846cfe
refactor(ethexe/malachite): anchor off the last event-received EB head
grishasobol Apr 24, 2026
1114c38
refactor(ethexe/malachite): validity-window-aware mempool with seen-h…
grishasobol Apr 24, 2026
58a4f96
refactor(ethexe/malachite): switch to secp256k1/ECDSA crypto via gsigner
grishasobol Apr 24, 2026
67ef2f9
fix(ethexe/malachite): stop parent-walks at start_block, tighten memp…
grishasobol Apr 24, 2026
86a431e
append mb computation
grishasobol Apr 25, 2026
ba79e8b
feat(ethexe/malachite): independent libp2p key, persistent peers, slo…
grishasobol Apr 25, 2026
7b929d4
test(ethexe/malachite): cover mempool, genesis, libp2p key derivation…
grishasobol Apr 25, 2026
3167e26
refactor(ethexe): replace announce flow with MB-driven coordinator/pa…
grishasobol Apr 25, 2026
1a26a8a
test(ethexe): keep build green during MB refactor
grishasobol Apr 25, 2026
3bd87ec
test(ethexe/consensus): restore batch validation tests on MB flow
grishasobol Apr 26, 2026
3bcf9c5
test(ethexe/consensus): cover aggregate_validators_commitment
grishasobol Apr 26, 2026
b59c808
test(ethexe/service): wire malachite into the test harness so ping pa…
grishasobol Apr 26, 2026
1009e33
fix(ethexe/processor): AdvanceTillEthereumBlock walks the full range
grishasobol Apr 26, 2026
09a5e21
feat(ethexe/malachite): synced-flag invariant + lenient producer chai…
grishasobol Apr 27, 2026
32be184
split to service and core
grishasobol Apr 29, 2026
115c328
different fixes
grishasobol Apr 29, 2026
f4a7ac2
remove accessing block by height, fix blocks hashing
grishasobol Apr 29, 2026
1b448d8
fix gas_allowance usage
grishasobol Apr 29, 2026
d379295
pass whole network restore; fix problems with not synced eth blocks i…
grishasobol Apr 30, 2026
9d1c4d0
remove Announces
grishasobol Apr 30, 2026
e315121
return some ethexe-service tests back
grishasobol Apr 30, 2026
095d90c
Merge origin/master, drop Announce-driven diffs
grishasobol Apr 30, 2026
fa95b8b
start-local-network.sh fix
grishasobol Apr 30, 2026
575c515
fix(ethexe): wire canonical_quarantine to malachite + drop coordinato…
grishasobol Apr 30, 2026
21c5453
append promise waiting time printing in for injected
grishasobol Apr 30, 2026
8dbe8c8
fix(ethexe/rpc): broadcast injected tx to every validator
grishasobol Apr 30, 2026
4291606
feat(ethexe): wire reply-promise gossip on the producer
grishasobol Apr 30, 2026
155cd5e
test(ethexe/service): restore send_injected_tx; persist injected tx i…
grishasobol Apr 30, 2026
bb02ab0
feat(ethexe/compute): stream reply promises mid-MB instead of batching
grishasobol Apr 30, 2026
ab0f30b
fix(ethexe/malachite): preserve mempool wakeup permit between fetch a…
grishasobol May 1, 2026
0207de8
fix(ethexe/rpc): register promise waiter before broadcasting injected tx
grishasobol May 1, 2026
1d4e2aa
test(ethexe/node-loader): add ethexe-ping-rate-load bin for rate-step…
grishasobol May 1, 2026
aeb2d1b
test(ethexe/scripts): advertise container-DNS public addr per validator
grishasobol May 1, 2026
a97522d
fix(ethexe/service): tolerate duplicate OutboundAcceptance from RPC f…
grishasobol May 1, 2026
d178a70
fix(ethexe/malachite): accept injected txs whose ref_block is not yet…
grishasobol May 4, 2026
52f5d01
fix(ethexe/malachite): validator waits for chain_head catch-up before…
grishasobol May 4, 2026
68c900f
fix(ethexe/consensus): chunk over-sized chain commitments instead of …
grishasobol May 4, 2026
c17594d
diag(ethexe/consensus): add coordinator/participant lifecycle logging
grishasobol May 4, 2026
ee518eb
diag(ethexe/network): log every validator-topic message at gossipsub …
grishasobol May 4, 2026
543e1bf
fix(ethexe/consensus): is_ancestor_or_equal must walk both directions
grishasobol May 4, 2026
d0c659a
chore(ethexe): demote consensus diagnostics to debug, drop spam logs
grishasobol May 4, 2026
458bfca
refactor(ethexe/consensus): replace is_ancestor_or_equal with mb_meta…
grishasobol May 4, 2026
02167df
db(ethexe): bump LATEST_VERSION to 6 for MbMeta schema change
grishasobol May 4, 2026
26506ef
refactor(ethexe/consensus): finalized check via globals walk instead …
grishasobol May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
899 changes: 862 additions & 37 deletions Cargo.lock

Large diffs are not rendered by default.

28 changes: 27 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ resolver = "3"

default-members = ["node/cli"]

exclude = ["ethexe/contracts", "ethexe/docker", "ethexe/scripts"]
exclude = ["ethexe/contracts", "ethexe/docker", "ethexe/malachite", "ethexe/scripts"]

members = [
"common",
Expand Down Expand Up @@ -109,6 +109,8 @@ members = [
"utils/runtime-fuzzer/fuzz",
"utils/lazy-pages-fuzzer/runner",
"ethexe/*",
"ethexe/malachite/core",
"ethexe/malachite/service",
"ethexe/runtime/common",
"ethexe/service/utils",
]
Expand All @@ -125,6 +127,7 @@ arbitrary = "1.3.2"
async-recursion = "1.1.1"
async-trait = "0.1.81"
base64 = "0.21.7"
derive-where = "1.5"
bytemuck = "1.23.2"
byteorder = { version = "1.5.0", default-features = false }
blake2 = { version = "0.10.6", default-features = false }
Expand Down Expand Up @@ -336,6 +339,29 @@ ethexe-compute = { path = "ethexe/compute", default-features = false }
ethexe-blob-loader = { path = "ethexe/blob-loader", default-features = false }
ethexe-db-init = { path = "ethexe/db/init", default-features = false }
ethexe-node-wrapper = {path = "ethexe/node-wrapper", default-features = false}
ethexe-malachite = { path = "ethexe/malachite/service", default-features = false }
ethexe-malachite-core = { path = "ethexe/malachite/core", default-features = false }

# libp2p-identity for ethexe-malachite-core's swarm peer-id derivation.
libp2p-identity = { version = "0.2", default-features = false, features = ["secp256k1"] }
# Pinned at the version `ethexe-db`'s librocksdb-sys uses — only one
# `links = "rocksdb"` crate may live in the dependency graph.
rocksdb = { version = "0.21", default-features = false, features = ["snappy"] }

# Malachite BFT engine — canonical fork at circlefin/malachite, pinned so
# all sub-crates share the same snapshot.
malachitebft-app-channel = { package = "arc-malachitebft-app-channel", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-app = { package = "arc-malachitebft-app", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-codec = { package = "arc-malachitebft-codec", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-core-consensus = { package = "arc-malachitebft-core-consensus", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-core-types = { package = "arc-malachitebft-core-types", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-engine = { package = "arc-malachitebft-engine", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-proto = { package = "arc-malachitebft-proto", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-signing = { package = "arc-malachitebft-signing", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-signing-ed25519 = { package = "arc-malachitebft-signing-ed25519", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-signing-ecdsa = { package = "arc-malachitebft-signing-ecdsa", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7", default-features = false, features = ["k256", "rand", "serde", "std"] }
malachitebft-sync = { package = "arc-malachitebft-sync", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }
malachitebft-test = { package = "arc-malachitebft-test", git = "https://github.com/circlefin/malachite", rev = "1fe7961aca933cefad8e4d9a52f50eda565288e7" }

# Common executor between `sandbox-host` and `lazy-pages-fuzzer`
wasmi = { version = "0.38" }
Expand Down
2 changes: 1 addition & 1 deletion ethexe/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ path = "src/main.rs"

[dependencies]
ethexe-network.workspace = true
ethexe-malachite.workspace = true
ethexe-prometheus.workspace = true
ethexe-rpc = { workspace = true, features = ["client"] }
ethexe-service.workspace = true
Expand All @@ -30,7 +31,6 @@ ethexe-ethereum.workspace = true
ethexe-common.workspace = true
ethexe-processor.workspace = true
ethexe-db.workspace = true
ethexe-compute.workspace = true
gprimitives = { workspace = true, features = ["std"] }

anyhow.workspace = true
Expand Down
128 changes: 13 additions & 115 deletions ethexe/cli/src/commands/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,18 @@
//! Implementation of the `ethexe check` command.

use crate::params::{MergeParams, Params};
use anyhow::{Context, Result, anyhow, ensure};
use anyhow::{Context, Result, anyhow};
use clap::Parser;
use ethexe_common::{
Announce, HashOf, SimpleBlockData,
db::{AnnounceStorageRO, DBGlobals, GlobalsStorageRO, OnChainStorageRO},
gear::CANONICAL_QUARANTINE,
SimpleBlockData,
db::{DBGlobals, GlobalsStorageRO, OnChainStorageRO},
};
use ethexe_db::{
Database, InitConfig, RawDatabase, RocksDatabase,
iterator::{BlockNode, DatabaseIterator},
verifier::IntegrityVerifier,
visitor::{self},
};
use ethexe_processor::{DEFAULT_CHUNK_SIZE, Processor, ProcessorConfig};
use indicatif::{ProgressBar, ProgressStyle};
use std::{collections::HashSet, path::PathBuf};

Expand All @@ -50,7 +48,9 @@ pub struct CheckCommand {
#[arg(long)]
pub db: Option<PathBuf>,

/// Perform computations of announces, by default from start announce to latest computed announce.
/// Re-execute every persisted MB and assert the cached outcome /
/// states / schedule match. Currently disabled — MB equivalent of
/// the legacy announce computation walk is not wired in yet.
#[arg(long, alias = "compute")]
pub computation_check: bool,

Expand Down Expand Up @@ -123,18 +123,11 @@ impl CheckCommand {

let globals = db.globals().clone();

let node_params = self.params.node.unwrap_or_default();
let _node_params = self.params.node.unwrap_or_default();
let checker = Checker {
db,
globals,
progress_bar: !self.verbose,
chunk_size: node_params
.chunk_processing_threads
.unwrap_or(DEFAULT_CHUNK_SIZE)
.get(),
canonical_quarantine: node_params
.canonical_quarantine
.unwrap_or(CANONICAL_QUARANTINE),
};

if self.integrity_check {
Expand All @@ -161,8 +154,6 @@ struct Checker {
db: Database,
globals: DBGlobals,
progress_bar: bool,
chunk_size: usize,
canonical_quarantine: u8,
}

impl Checker {
Expand Down Expand Up @@ -241,106 +232,13 @@ impl Checker {
Ok(())
}

/// Recomputes announces and checks the stored outcomes against fresh execution results.
/// Re-runs every persisted MB and compares the cached outcome / states /
/// schedule against fresh execution. Stubbed pending MB walk wiring.
async fn computation_check(&self) -> Result<()> {
let db = &self.db;
let bottom = self.globals.start_announce_hash;
let head = self.globals.latest_computed_announce_hash;
let progress_bar = self.progress_bar;
let chunk_size = self.chunk_size;
let canonical_quarantine = self.canonical_quarantine;

let bottom_block = announce_block(db, bottom)?;
let head_block = announce_block(db, head)?;
println!(
"📋 Starting computation check from announce {bottom} in {bottom_block} to announce {head} in {head_block}"
);

let pb = if progress_bar {
let total_blocks = announce_block(db, head)?
.header
.height
.checked_sub(announce_block(db, bottom)?.header.height)
.ok_or_else(|| anyhow!("Incorrect announces range"))?;
let bar_style = ProgressStyle::with_template(PROGRESS_BAR_TEMPLATE)
.unwrap()
.progress_chars("=>-");
let pb = ProgressBar::new(total_blocks as u64);
pb.set_style(bar_style);
Some(pb)
} else {
None
};

let processor = Processor::with_config(ProcessorConfig { chunk_size }, db.clone())
.context("failed to create processor")?;

// Iterate back: from `head` announce to `bottom` announce
let mut announce_hash = head;
while announce_hash != bottom {
let announce = db.announce(announce_hash).ok_or_else(|| {
anyhow!("announce {announce_hash} in computed chain not found in db")
})?;
let announce_parent_hash = announce.parent;

let mut processor = processor.clone().overlaid();
let executable =
ethexe_compute::prepare_executable_for_announce(db, announce, canonical_quarantine)
.context("Unable to preparing announce data for execution")?;
let res = processor
.as_mut()
.process_programs(executable, None)
.await
.context("failed to re-compute announce")?;

let states = db.announce_program_states(announce_hash).ok_or_else(|| {
anyhow!("program states for announce {announce_hash:?} not found in db",)
})?;

let outcome = db
.announce_outcome(announce_hash)
.ok_or_else(|| anyhow!("announce outcome {announce_hash:?} not found in db",))?;

let schedule = db.announce_schedule(announce_hash).ok_or_else(|| {
anyhow!("schedule for announce {announce_hash:?} not found in db",)
})?;

ensure!(
states == res.states,
"announce {announce_hash:?} final program states mismatch",
);

ensure!(
outcome == res.transitions,
"announce {announce_hash:?} state transitions mismatch",
);

ensure!(
schedule == res.schedule,
"announce {announce_hash:?} schedule mismatch",
);

if let Some(ref pb) = pb {
pb.inc(1);
}

announce_hash = announce_parent_hash;
}

// TODO: walk `globals.latest_finalized_mb_hash` back through
// `CompactBlock.parent`, re-execute each MB through the
// processor, and assert the persisted `mb_*` records match.
println!("computation_check is currently a stub — MB walk not wired in yet");
Ok(())
}
}

/// Resolves the block associated with a stored announce.
fn announce_block(db: &Database, announce_hash: HashOf<Announce>) -> Result<SimpleBlockData> {
let announce = db
.announce(announce_hash)
.ok_or_else(|| anyhow!("announce {announce_hash} not found in db",))?;

db.block_header(announce.block_hash)
.ok_or_else(|| anyhow!("block header not found for block {}", announce.block_hash))
.map(|header| SimpleBlockData {
hash: announce.block_hash,
header,
})
}
94 changes: 94 additions & 0 deletions ethexe/cli/src/commands/malachite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// This file is part of Gear.
//
// Copyright (C) 2026 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Implementation of the `ethexe malachite` command family.
//!
//! Currently only exposes [`MalachiteSubcommand::PeerId`], which lets
//! operators derive the libp2p peer_id of the Malachite swarm
//! offline (without booting the node) for a given validator key.
//! That value is what fills the `/p2p/<peer_id>` suffix of a
//! `--malachite-persistent-peer` multiaddr.

use crate::params::Params;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use ethexe_malachite::malachite_libp2p_peer_id;
use gsigner::secp256k1::{PublicKey, Signer};
use std::path::PathBuf;

/// Malachite-specific helper commands.
#[derive(Debug, Parser)]
pub struct MalachiteCommand {
/// Validator keystore directory (defaults to the node's standard
/// keys directory derived from `--base-path`).
#[arg(short, long)]
pub key_store: Option<PathBuf>,

/// Subcommand to run.
#[command(subcommand)]
pub command: MalachiteSubcommand,
}

#[derive(Debug, Subcommand)]
pub enum MalachiteSubcommand {
/// Print the libp2p peer_id this validator key uses on the
/// Malachite swarm. The value is derived deterministically from
/// the validator secret and is independent of the on-chain
/// validator address.
PeerId {
/// Validator public key whose Malachite peer_id you want to
/// derive (must be present in the keystore).
validator: PublicKey,
},
}

impl MalachiteCommand {
/// Merge the command with the provided params (fill in the
/// keystore path from the node base path if the user didn't pass
/// `--key-store` explicitly).
pub fn with_params(mut self, params: Params) -> Self {
let node = params.node.unwrap_or_default();
self.key_store = self.key_store.take().or_else(|| Some(node.keys_dir()));
self
}

pub fn exec(self) -> Result<()> {
let key_store = self.key_store.expect("must never be empty after merging");

match self.command {
MalachiteSubcommand::PeerId { validator } => {
let signer = Signer::fs(key_store).context("opening validator keystore")?;
let secret = signer
.private_key(validator)
.context("validator key not found in keystore")?
.to_bytes();

let peer_id = malachite_libp2p_peer_id(&secret);

println!("{peer_id}");
println!();
println!(
"Example persistent-peer multiaddr (replace IP/port for each peer):\n \
/ip4/127.0.0.1/tcp/20334/p2p/{peer_id}"
);
}
}

Ok(())
}
}
6 changes: 6 additions & 0 deletions ethexe/cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ use clap::Subcommand;
mod check;
mod dump;
mod key;
mod malachite;
mod run;
mod tx;

pub use check::CheckCommand;
pub use dump::DumpCommand;
pub use key::KeyCommand;
pub use malachite::MalachiteCommand;
pub use run::RunCommand;
pub use tx::TxCommand;

Expand All @@ -52,6 +54,8 @@ pub enum Command {
Check(CheckCommand),
/// State dump operations for re-genesis.
Dump(DumpCommand),
/// Malachite-specific helper commands (peer-id derivation, etc.).
Malachite(MalachiteCommand),
}

impl Command {
Expand All @@ -63,6 +67,7 @@ impl Command {
Self::Tx(tx_cmd) => Self::Tx(tx_cmd.with_params(file_params)),
Self::Check(check_cmd) => Self::Check(check_cmd.with_params(file_params)),
Self::Dump(dump_cmd) => Self::Dump(dump_cmd.with_params(file_params)),
Self::Malachite(mala_cmd) => Self::Malachite(mala_cmd.with_params(file_params)),
}
}

Expand All @@ -76,6 +81,7 @@ impl Command {
Command::Run(run_cmd) => run_cmd.run(),
Command::Check(check_cmd) => check_cmd.exec(),
Command::Dump(dump_cmd) => dump_cmd.exec(),
Command::Malachite(mala_cmd) => mala_cmd.exec(),
}
}
}
Loading