Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
90f4727
feat(reference-index): add morph-reference-index crate with MDBX stor…
panos-xyz Apr 30, 2026
b1dbc92
feat(rpc): wire morph_getTransactionHashesByReference RPC + ExEx
panos-xyz Apr 30, 2026
50654d2
chore(reference-index): fix clippy lints (collapsible_if, redundant_c…
panos-xyz Apr 30, 2026
470123b
chore: cargo fmt
panos-xyz Apr 30, 2026
f5fff3b
test(reference-index): add integration tests for backfill + query
panos-xyz Apr 30, 2026
eb1f34b
fix(reference-index): fix 5 adversarial review findings
panos-xyz Apr 30, 2026
6ce077a
fix(reference-index): fix 3 more confirmed bugs
panos-xyz Apr 30, 2026
d3ab7e4
fix(reference-index): first-ready ChainReverted must fill gap up to p…
panos-xyz Apr 30, 2026
6237a47
fix(reference-index): final sweep — reconcile None skip + monotonic F…
panos-xyz Apr 30, 2026
4afc286
fix(reference-index): InProgress+sentinel re-resolves Jade; RPC inter…
panos-xyz Apr 30, 2026
b6aa870
chore(reference-index): remove unused paired-snapshot validation
panos-xyz May 6, 2026
eb42e6c
chore(reference-index): remove unused pub methods and dead imports
panos-xyz May 6, 2026
3a2f977
chore(reference-index): simplify tracing targets, drop sleep, clean u…
panos-xyz May 7, 2026
a41b55b
Merge branch 'main' into feature/morph-reference-rpc
panos-xyz May 9, 2026
9182af6
fix(ci): satisfy clippy and cargo-deny on PR #106
panos-xyz May 9, 2026
90dd720
Merge branch 'main' into feature/morph-reference-rpc
panos-xyz May 9, 2026
a02d289
fix(ci): resolve cargo-deny advisories on PR #106
panos-xyz May 9, 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
38 changes: 38 additions & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
"crates/payload/builder",
"crates/payload/types",
"crates/primitives",
"crates/reference-index",
"crates/revm",
"crates/txpool",
]
Expand Down Expand Up @@ -50,6 +51,7 @@ morph-node = { path = "crates/node"}
morph-payload-builder = { path = "crates/payload/builder", default-features = false }
morph-payload-types = { path = "crates/payload/types", default-features = false }
morph-primitives = { path = "crates/primitives", default-features = false }
morph-reference-index = { path = "crates/reference-index", default-features = false }
morph-rpc = { path = "crates/rpc" }
morph-revm = { path = "crates/revm", default-features = false }
morph-txpool = { path = "crates/txpool", default-features = false }
Expand All @@ -72,6 +74,9 @@ reth-engine-primitives = { git = "https://github.com/morph-l2/reth", rev = "1b07
reth-engine-tree = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
reth-errors = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
reth-eth-wire-types = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
reth-exex = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
reth-exex-test-utils = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
reth-exex-types = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
reth-ethereum = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
reth-ethereum-cli = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
reth-ethereum-consensus = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" }
Expand Down
2 changes: 2 additions & 0 deletions bin/morph-reth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ morph-chainspec = { workspace = true, features = ["cli"] }
morph-consensus.workspace = true
morph-evm.workspace = true
morph-node.workspace = true
morph-reference-index.workspace = true

# Reth CLI
reth-chainspec.workspace = true
reth-cli.workspace = true
reth-cli-util.workspace = true
reth-ethereum-cli.workspace = true
Expand Down
35 changes: 33 additions & 2 deletions bin/morph-reth/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ use clap::Parser;
use morph_chainspec::{MorphChainSpec, MorphChainSpecParser};
use morph_consensus::MorphConsensus;
use morph_evm::{MorphEvmConfig, evm::MorphEvmFactory};
use morph_node::{MorphArgs, MorphNode};
use morph_node::{
MorphAddOns, MorphArgs, MorphNode,
exex::{ReferenceIndexControl, reference_index_exex},
};
use morph_reference_index::ReferenceIndexDb;
use reth_chainspec::EthChainSpec;
use reth_cli_util::sigsegv_handler;
use reth_ethereum_cli::Cli;
use reth_node_builder::Node;
use reth_rpc_server_types::DefaultRpcModuleValidator;
use std::sync::Arc;
use tracing::info;
Expand Down Expand Up @@ -43,8 +49,33 @@ fn main() {
.run_with_components::<MorphNode>(components, async move |builder, morph_args| {
info!(target: "morph::cli", "Starting Morph-Reth node");

// Open the reference index DB before launching the node so we
// can wire it into both the ExEx and the add-ons.
let chain_spec = builder.config().chain.clone();
let datadir = builder.config().datadir();
let reference_index_path = datadir.data_dir().join("morph").join("reference_index");
let chain_id = chain_spec.chain().id();
let genesis_hash = chain_spec.genesis_hash(); // from EthChainSpec trait

info!(
target: "morph::reference_index",
path = %reference_index_path.display(),
chain_id,
"opening Morph reference index database"
);
let db = ReferenceIndexDb::open(&reference_index_path, chain_id, genesis_hash)?;
let (control, startup_rx) = ReferenceIndexControl::new(db);

let exex_control = control.clone();
let node = MorphNode::new(morph_args);

let handle = builder
.node(MorphNode::new(morph_args))
.with_types::<MorphNode>()
.with_components(node.components_builder())
.with_add_ons(MorphAddOns::new().with_reference_index(control))
.install_exex("morph-reference-index", async move |ctx| {
Ok(reference_index_exex(ctx, exex_control, startup_rx))
})
.launch_with_debug_capabilities()
.await?;

Expand Down
16 changes: 10 additions & 6 deletions crates/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@ morph-evm.workspace = true
morph-payload-builder.workspace = true
morph-payload-types.workspace = true
morph-primitives = { workspace = true, features = ["reth-codec"] }
morph-reference-index.workspace = true
morph-rpc.workspace = true
morph-txpool.workspace = true

# Reth dependencies
reth-db.workspace = true
reth-db-api.workspace = true
reth-node-core.workspace = true
reth-chainspec.workspace = true
reth-engine-local.workspace = true
reth-engine-tree.workspace = true
reth-errors.workspace = true
reth-exex.workspace = true
reth-node-api.workspace = true
reth-node-builder.workspace = true
reth-node-ethereum.workspace = true
Expand All @@ -37,12 +40,15 @@ reth-primitives-traits.workspace = true
reth-provider.workspace = true
reth-rpc-builder.workspace = true
reth-rpc-eth-api.workspace = true
reth-storage-api.workspace = true
reth-tasks.workspace = true
reth-transaction-pool.workspace = true
reth-tracing.workspace = true
reth-trie.workspace = true

# Alloy
alloy-consensus.workspace = true
alloy-eips.workspace = true
alloy-genesis.workspace = true
alloy-hardforks.workspace = true
alloy-primitives.workspace = true
Expand All @@ -54,16 +60,15 @@ eyre.workspace = true
clap.workspace = true
dashmap.workspace = true
parking_lot.workspace = true
tokio = { workspace = true, features = ["sync", "rt"] }
tokio-stream.workspace = true
tracing.workspace = true

serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true

# Optional: E2E testing framework
reth-e2e-test-utils = { workspace = true, optional = true }
reth-tasks = { workspace = true, optional = true }
tokio = { workspace = true, features = ["sync"], optional = true }
alloy-eips = { workspace = true, optional = true }
alloy-rlp = { workspace = true, optional = true }
alloy-signer = { workspace = true, optional = true }
alloy-signer-local = { workspace = true, optional = true }
Expand All @@ -85,9 +90,11 @@ alloy-primitives.workspace = true
alloy-rpc-types-engine.workspace = true
alloy-rpc-types-eth.workspace = true
jsonrpsee.workspace = true
morph-chainspec.workspace = true
morph-payload-types.workspace = true
morph-primitives.workspace = true
serde_json.workspace = true
tempfile.workspace = true

[[test]]
name = "it"
Expand All @@ -98,10 +105,7 @@ required-features = ["test-utils"]
default = []
test-utils = [
"dep:reth-e2e-test-utils",
"dep:reth-tasks",
"dep:alloy-signer",
"dep:tokio",
"dep:alloy-eips",
"dep:alloy-rlp",
"dep:alloy-signer-local",
]
59 changes: 58 additions & 1 deletion crates/node/src/add_ons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

use crate::{
MorphNode,
exex::ReferenceIndexControl,
validator::{MorphEngineValidatorBuilder, MorphTreeEngineValidatorBuilder},
};
use morph_evm::MorphEvmConfig;
use morph_primitives::{Block, MorphHeader, MorphReceipt};
use morph_rpc::{MorphEthApiBuilder, MorphEthConfigApiServer, MorphEthConfigHandler};
use morph_reference_index::{DEFAULT_LAG_THRESHOLD, ReferenceIndexReader};
use morph_rpc::{
MorphEthApiBuilder, MorphEthConfigApiServer, MorphEthConfigHandler,
morph::{MorphRpc, MorphRpcHandler, MorphRpcServer},
};
use reth_node_api::{AddOnsContext, FullNodeComponents, FullNodeTypes, NodeAddOns, NodePrimitives};
use reth_node_builder::{
NodeAdapter,
Expand Down Expand Up @@ -39,6 +44,10 @@ pub struct MorphAddOns<
> {
/// Inner RPC add-ons from reth.
inner: RpcAddOns<N, EthB, PVB, NoopEngineApiBuilder, EVB, RpcMiddleware>,
/// Optional reference-index control injected by `main.rs`. When present
/// the add-on spawns startup indexing on launch and registers the
/// `morph_` RPC namespace.
reference_index: Option<ReferenceIndexControl>,
}

impl<N> MorphAddOns<NodeAdapter<N>, MorphEthApiBuilder>
Expand All @@ -59,8 +68,16 @@ where
MorphTreeEngineValidatorBuilder::new(pvb),
Identity::default(),
),
reference_index: None,
}
}

/// Attach a reference index control so the add-on can spawn startup
/// indexing and register the `morph_` RPC namespace on launch.
pub fn with_reference_index(mut self, control: ReferenceIndexControl) -> Self {
self.reference_index = Some(control);
self
}
}

impl<N> Default for MorphAddOns<NodeAdapter<N>, MorphEthApiBuilder>
Expand Down Expand Up @@ -116,6 +133,37 @@ where
}
});

// Spawn reference index startup indexing (Task A) if configured.
let reference_rpc_handler = if let Some(control) = self.reference_index {
let startup_control = control.clone();
let startup_node = ctx.node.clone();
// spawn_critical causes node shutdown on panic/error, matching the spec
// requirement that reference index startup failures are fatal.
task_executor.spawn_critical("morph reference index startup", async move {
let result = tokio::task::spawn_blocking(move || {
crate::exex::run_startup_indexing(&startup_node, &startup_control)
})
.await
.unwrap_or_else(|e| Err(eyre::eyre!("reference index startup panicked: {e}")));

match result {
Ok(()) => {}
Err(err) => {
// Propagate to spawn_critical which will shut down the node.
panic!("reference index startup failed: {err:?}");
}
}
});

let morph_rpc_ctx = MorphRpc::new(
ReferenceIndexReader::new(control.db, DEFAULT_LAG_THRESHOLD),
provider.clone(),
);
Some(MorphRpcHandler::new(morph_rpc_ctx))
} else {
None
};

// Use launch_add_ons_with to register custom Engine API and eth_config
self.inner
.launch_add_ons_with(ctx, move |container| {
Expand All @@ -133,6 +181,15 @@ where
.map_err(|e| eyre::eyre!("Failed to register eth_config handler: {}", e))?;
tracing::info!(target: "morph::node", "Morph eth_config handler registered successfully");

// Register morph_ RPC namespace (if reference index was configured).
if let Some(handler) = reference_rpc_handler {
tracing::debug!(target: "morph::node", "Registering morph_ RPC namespace");
modules
.merge_configured(handler.into_rpc())
.map_err(|e| eyre::eyre!("Failed to register morph_ RPC: {}", e))?;
tracing::info!(target: "morph::node", "morph_ RPC namespace registered");
}

// Create and register Morph L2 Engine API
tracing::debug!(target: "morph::node", "Registering Morph L2 Engine API");

Expand Down
5 changes: 5 additions & 0 deletions crates/node/src/exex/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! Morph-specific Execution Extensions (ExEx).

pub mod reference_index;

pub use reference_index::{ReferenceIndexControl, reference_index_exex, run_startup_indexing};
Loading
Loading