Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ff2761d
test: add stress test script
panos-xyz Mar 5, 2026
7f28e2f
fix: pre-audit security fixes across multiple crates
panos-xyz Mar 6, 2026
8774a28
fix: additional pre-audit security hardening
panos-xyz Mar 6, 2026
7a4ce01
fix: pre-audit fixes for security review
panos-xyz Mar 6, 2026
349e6a4
fix: pre-audit security hardening (block size, MorphTx version, fee r…
panos-xyz Mar 6, 2026
e5594a6
refactor: load precompile contracts once
panos-xyz Mar 6, 2026
5524569
fix: align token transfer check with go-eth and clean up txpool removal
panos-xyz Mar 7, 2026
7ad5071
perf: tune reth persistence threshold to reduce disk I/O contention
panos-xyz Mar 7, 2026
e732634
refactor: add geth url
panos-xyz Mar 7, 2026
e02117f
fix: use evm_call for ERC20 token fee to preserve Transfer logs in re…
panos-xyz Mar 8, 2026
70e27c6
fix: decouple token fee logs from handler pipeline to survive tx revert
panos-xyz Mar 8, 2026
33cdd8e
fix: discard logs from balanceOf evm_call to match go-eth StaticCall …
panos-xyz Mar 8, 2026
a891dd0
refactor: minor efficiency improvements in token fee log handling
panos-xyz Mar 8, 2026
718415d
refactor: deduplicate MorphTx validation in consensus layer
panos-xyz Mar 8, 2026
c6f4c9e
style: fmt all
panos-xyz Mar 8, 2026
cf6a7c3
fix: rewrite TxMorph Compact codec using derive helper struct pattern
panos-xyz Mar 9, 2026
3411baf
chore: add local-test-hoodi scripts for Hoodi testnet sync
panos-xyz Mar 9, 2026
c5e488f
fix: read L1BlockInfo per-tx instead of per-block cache
panos-xyz Mar 9, 2026
7920f44
style: fmt all
panos-xyz Mar 9, 2026
4e6f225
fix: allow skipped L1 messages in block validation
panos-xyz Mar 10, 2026
4d3f66c
docs: fix stale comments, remove redundant annotations
panos-xyz Mar 10, 2026
18175ba
fix: set finalized_block_hash in FCU to prevent unbounded memory growth
panos-xyz Mar 11, 2026
8fc171e
fix: limit FCU tag fallback to historical sync
panos-xyz Mar 11, 2026
e29313a
fix: seed finalized tag in new_safe_l2_block for validator memory cle…
panos-xyz Mar 11, 2026
bce93b3
fix: use journal checkpoint/revert for EVM internal calls
panos-xyz Mar 11, 2026
42d4f20
refactor: remove should_compute_state_root override and export fetch_…
panos-xyz Mar 11, 2026
5c66d44
feat: add state-root-check CLI tool for MPT root comparison
panos-xyz Mar 11, 2026
aeb6400
chore: remove legacy-state-root flag and fix geth RPC URL default
panos-xyz Mar 11, 2026
3a06244
chore: add sync speed benchmark scripts
panos-xyz Mar 11, 2026
ce51d44
refactor: remove geth RPC cross-validation from sync path
panos-xyz Mar 11, 2026
f2995be
chore: add state-root-check wrapper scripts
panos-xyz Mar 11, 2026
9b0ad0b
fix: implement OnStateHook in MorphBlockExecutor for StateRootTask
panos-xyz Mar 12, 2026
dc86c45
fix: remove MorphTxRuntime and SLOAD/SSTORE overrides for revm 33.1.0
panos-xyz Mar 12, 2026
e4ec499
style: fmt all
panos-xyz Mar 12, 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
15 changes: 14 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ publish = false
resolver = "3"
members = [
"bin/morph-reth",
"bin/state-root-check",
"crates/chainspec",
"crates/consensus",
"crates/engine-api",
Expand Down Expand Up @@ -103,6 +104,7 @@ reth-storage-api = { git = "https://github.com/morph-l2/reth", rev = "1dd7227738
reth-tasks = { git = "https://github.com/morph-l2/reth", rev = "1dd722773844d1a3c50a691dc09f6cdf8e6bd00e" }
reth-tracing = { git = "https://github.com/morph-l2/reth", rev = "1dd722773844d1a3c50a691dc09f6cdf8e6bd00e" }
reth-trie = { git = "https://github.com/morph-l2/reth", rev = "1dd722773844d1a3c50a691dc09f6cdf8e6bd00e" }
reth-trie-db = { git = "https://github.com/morph-l2/reth", rev = "1dd722773844d1a3c50a691dc09f6cdf8e6bd00e" }
reth-transaction-pool = { git = "https://github.com/morph-l2/reth", rev = "1dd722773844d1a3c50a691dc09f6cdf8e6bd00e" }
reth-zstd-compressors = { git = "https://github.com/morph-l2/reth", rev = "1dd722773844d1a3c50a691dc09f6cdf8e6bd00e", default-features = false }

Expand Down
4 changes: 3 additions & 1 deletion bin/morph-reth/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ fn main() {
// Install signal handler for segmentation faults
sigsegv_handler::install();

// Enable backtraces by default
// Enable backtraces by default.
// SAFETY: Called at process startup before any other threads are spawned,
// so there are no concurrent readers of the environment.
if std::env::var_os("RUST_BACKTRACE").is_none() {
unsafe { std::env::set_var("RUST_BACKTRACE", "1") };
}
Expand Down
24 changes: 24 additions & 0 deletions bin/state-root-check/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "state-root-check"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish.workspace = true

[lints]
workspace = true

[dependencies]
alloy-primitives.workspace = true
clap.workspace = true
eyre.workspace = true
morph-chainspec.workspace = true
morph-node.workspace = true
reth-provider.workspace = true
reth-storage-api.workspace = true
reth-trie.workspace = true

[[bin]]
name = "state-root-check"
path = "src/main.rs"
76 changes: 76 additions & 0 deletions bin/state-root-check/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use alloy_primitives::B256;
use eyre::{Result, ensure};

#[cfg(test)]
mod tests {
use super::find_first_mismatch;
use eyre::Result;

#[test]
fn returns_first_divergent_block_for_monotonic_range() -> Result<()> {
let mismatch_from = 103_u64;

let got = find_first_mismatch(100, 105, |block| Ok(block < mismatch_from))?;

assert_eq!(got, Some(mismatch_from));
Ok(())
}

#[test]
fn returns_none_when_everything_matches() -> Result<()> {
let got = find_first_mismatch(100, 105, |_block| Ok(true))?;

assert_eq!(got, None);
Ok(())
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockRootComparison {
pub block_number: u64,
pub reth_root: B256,
pub geth_disk_root: B256,
}

impl BlockRootComparison {
pub fn is_match(&self) -> bool {
self.reth_root == self.geth_disk_root
}
}

pub fn find_first_mismatch<F>(from: u64, to: u64, mut matches: F) -> Result<Option<u64>>
where
F: FnMut(u64) -> Result<bool>,
{
ensure!(
from <= to,
"invalid range: from-block {from} is greater than to-block {to}"
);

if !matches(from)? {
return Ok(Some(from));
}

if matches(to)? {
return Ok(None);
}

let mut low = from.saturating_add(1);
let mut high = to;
let mut first = to;

while low <= high {
let mid = low + (high - low) / 2;
if matches(mid)? {
low = mid.saturating_add(1);
} else {
first = mid;
if mid == 0 {
break;
}
high = mid - 1;
}
}

Ok(Some(first))
}
217 changes: 217 additions & 0 deletions bin/state-root-check/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
use alloy_primitives::B256;
use clap::{Parser, ValueEnum};
use eyre::{Context, ContextCompat, Result, ensure};
use morph_chainspec::{MORPH_HOODI, MORPH_MAINNET, MorphChainSpec};
use morph_node::{MorphNode, validator::fetch_geth_disk_root};
use reth_provider::{
ProviderFactory,
providers::{ProviderNodeTypes, ReadOnlyConfig},
};
use reth_storage_api::{BlockNumReader, StateRootProvider};
use reth_trie::HashedPostState;
use state_root_check::{BlockRootComparison, find_first_mismatch};
use std::{collections::HashMap, path::PathBuf, sync::Arc};

#[derive(Debug, Clone, Copy, Default, ValueEnum)]
enum ChainArg {
Morph,
#[default]
#[value(name = "morph-hoodi")]
MorphHoodi,
}

impl ChainArg {
fn chain_spec(self) -> Arc<MorphChainSpec> {
match self {
Self::Morph => MORPH_MAINNET.clone(),
Self::MorphHoodi => MORPH_HOODI.clone(),
}
}
}

#[derive(Debug, Parser)]
#[command(name = "state-root-check")]
#[command(about = "Compare local reth MPT roots with geth morph_diskRoot")]
struct Args {
#[arg(long)]
datadir: PathBuf,
#[arg(long, default_value = "morph-hoodi")]
chain: ChainArg,
#[arg(long)]
geth_rpc_url: Option<String>,
#[arg(long, conflicts_with_all = ["fresh_root_at", "bisect"])]
block: Option<u64>,
#[arg(long, conflicts_with_all = ["block", "bisect"])]
fresh_root_at: Option<u64>,
#[arg(long, requires_all = ["from_block", "to_block"], conflicts_with_all = ["block", "fresh_root_at"])]
bisect: bool,
#[arg(long, requires = "bisect")]
from_block: Option<u64>,
#[arg(long, requires = "bisect")]
to_block: Option<u64>,
}

enum Mode {
LocalRoot {
block: u64,
},
Compare {
block: u64,
geth_rpc_url: String,
},
Bisect {
from: u64,
to: u64,
geth_rpc_url: String,
},
}

fn main() -> Result<()> {
let args = Args::parse();
ensure!(
args.datadir.exists(),
"datadir does not exist: {}",
args.datadir.display()
);

let factory = MorphNode::provider_factory_builder()
.open_read_only(
args.chain.chain_spec(),
ReadOnlyConfig::from_datadir(&args.datadir),
)
.with_context(|| format!("failed to open datadir {}", args.datadir.display()))?;

let latest_block = factory.best_block_number()?;
let mode = resolve_mode(&args, latest_block)?;

match mode {
Mode::LocalRoot { block } => {
let root = compute_local_state_root(&factory, block)?;
println!("fresh_root_at #{block}: {root:#x}");
}
Mode::Compare {
block,
geth_rpc_url,
} => {
let comparison = compare_block_roots(&factory, &geth_rpc_url, block)?;
print_comparison(&comparison);
}
Mode::Bisect {
from,
to,
geth_rpc_url,
} => {
let mut cache = HashMap::new();
let first = find_first_mismatch(from, to, |block| {
if let Some(is_match) = cache.get(&block) {
return Ok(*is_match);
}

let comparison = compare_block_roots(&factory, &geth_rpc_url, block)?;
let is_match = comparison.is_match();
print_probe(&comparison);
cache.insert(block, is_match);
Ok(is_match)
})?;

match first {
Some(block) => println!("first_mismatch: {block}"),
None => println!("all blocks matched in range [{from}, {to}]"),
}
}
}

Ok(())
}

fn resolve_mode(args: &Args, latest_block: u64) -> Result<Mode> {
if let Some(block) = args.fresh_root_at {
return Ok(Mode::LocalRoot { block });
}

if args.bisect {
let from = args
.from_block
.context("--from-block is required when --bisect is set")?;
let to = args
.to_block
.context("--to-block is required when --bisect is set")?;
ensure!(
from <= to,
"--from-block must be less than or equal to --to-block"
);
return Ok(Mode::Bisect {
from,
to,
geth_rpc_url: args
.geth_rpc_url
.clone()
.context("--geth-rpc-url is required for --bisect")?,
});
}

let block = args.block.unwrap_or(latest_block);
if let Some(geth_rpc_url) = args.geth_rpc_url.clone() {
Ok(Mode::Compare {
block,
geth_rpc_url,
})
} else {
Ok(Mode::LocalRoot { block })
}
}

fn compute_local_state_root<N>(factory: &ProviderFactory<N>, block: u64) -> Result<B256>
where
N: ProviderNodeTypes,
{
let provider = factory
.history_by_block_number(block)
.with_context(|| format!("failed to open historical state at block {block}"))?;
let root = provider
.state_root(HashedPostState::default())
.with_context(|| format!("failed to compute local state root at block {block}"))?;
Ok(root)
}

fn compare_block_roots<N>(
factory: &ProviderFactory<N>,
geth_rpc_url: &str,
block: u64,
) -> Result<BlockRootComparison>
where
N: ProviderNodeTypes,
{
let reth_root = compute_local_state_root(factory, block)?;
let geth_disk_root =
fetch_geth_disk_root(geth_rpc_url, block).map_err(|err| eyre::eyre!(err))?;
Ok(BlockRootComparison {
block_number: block,
reth_root,
geth_disk_root,
})
}

fn print_comparison(comparison: &BlockRootComparison) {
let status = if comparison.is_match() {
"MATCH"
} else {
"MISMATCH"
};
println!("block #{}", comparison.block_number);
println!("reth_root: {:#x}", comparison.reth_root);
println!("geth_disk_root: {:#x}", comparison.geth_disk_root);
println!("status: {status}");
}

fn print_probe(comparison: &BlockRootComparison) {
let status = if comparison.is_match() {
"MATCH"
} else {
"MISMATCH"
};
println!(
"probe #{} => {} reth={:#x} geth={:#x}",
comparison.block_number, status, comparison.reth_root, comparison.geth_disk_root
);
}
Loading
Loading