From c02c1a0657eb54c9c20d58a2065dce95f7155f3f Mon Sep 17 00:00:00 2001 From: Barry Date: Fri, 16 Jan 2026 12:25:36 +0800 Subject: [PATCH 1/3] update --- Cargo.lock | 186 ++++++++++++++++++++++--- Cargo.toml | 6 + bin/node/Cargo.toml | 5 + bin/node/src/main.rs | 87 ++++++++++-- crates/evm/Cargo.toml | 45 ++++++ crates/evm/src/config.rs | 171 +++++++++++++++++++++++ crates/evm/src/evm_factory.rs | 91 ++++++++++++ crates/evm/src/factory.rs | 31 +++++ crates/evm/src/lib.rs | 13 ++ crates/evm/src/precompiles/mod.rs | 47 +++++++ crates/evm/src/precompiles/poseidon.rs | 120 ++++++++++++++++ crates/node/Cargo.toml | 35 +++++ crates/node/src/lib.rs | 39 ++++++ tests/test-precompile.sh | 13 ++ 14 files changed, 859 insertions(+), 30 deletions(-) create mode 100644 crates/evm/Cargo.toml create mode 100644 crates/evm/src/config.rs create mode 100644 crates/evm/src/evm_factory.rs create mode 100644 crates/evm/src/factory.rs create mode 100644 crates/evm/src/lib.rs create mode 100644 crates/evm/src/precompiles/mod.rs create mode 100644 crates/evm/src/precompiles/poseidon.rs create mode 100644 crates/node/Cargo.toml create mode 100644 crates/node/src/lib.rs create mode 100755 tests/test-precompile.sh diff --git a/Cargo.lock b/Cargo.lock index 6881f2e..af91907 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,7 +1074,7 @@ dependencies = [ "fnv", "hashbrown 0.15.5", "itertools 0.13.0", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "zeroize", @@ -1091,7 +1091,7 @@ dependencies = [ "ark-serialize 0.3.0", "ark-std 0.3.0", "derivative", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "paste", "rustc_version 0.3.3", @@ -1111,7 +1111,7 @@ dependencies = [ "derivative", "digest 0.10.7", "itertools 0.10.5", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "paste", "rustc_version 0.4.1", @@ -1132,7 +1132,7 @@ dependencies = [ "digest 0.10.7", "educe", "itertools 0.13.0", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "paste", "zeroize", @@ -1174,7 +1174,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-traits", "quote", "syn 1.0.109", @@ -1186,7 +1186,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-traits", "proc-macro2", "quote", @@ -1199,7 +1199,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-traits", "proc-macro2", "quote", @@ -1232,7 +1232,7 @@ dependencies = [ "ark-relations", "ark-std 0.5.0", "educe", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "tracing", @@ -1268,7 +1268,7 @@ checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-std 0.4.0", "digest 0.10.7", - "num-bigint", + "num-bigint 0.4.6", ] [[package]] @@ -1281,7 +1281,7 @@ dependencies = [ "ark-std 0.5.0", "arrayvec", "digest 0.10.7", - "num-bigint", + "num-bigint 0.4.6", ] [[package]] @@ -1759,7 +1759,7 @@ dependencies = [ "boa_macros", "boa_string", "indexmap 2.13.0", - "num-bigint", + "num-bigint 0.4.6", "rustc-hash", ] @@ -1793,7 +1793,7 @@ dependencies = [ "indexmap 2.13.0", "intrusive-collections", "itertools 0.14.0", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "num_enum", @@ -1869,7 +1869,7 @@ dependencies = [ "boa_macros", "fast-float2", "icu_properties", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "regress", "rustc-hash", @@ -2884,7 +2884,7 @@ dependencies = [ "asn1-rs", "displaydoc", "nom", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "rusticata-macros", ] @@ -3509,6 +3509,32 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff_ce" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a682c12d0cc98a32ab7540401a5ea1ed21d11571eea11d5829cd721f85ff0" +dependencies = [ + "byteorder", + "ff_derive_ce", + "hex", + "rand 0.4.6", +] + +[[package]] +name = "ff_derive_ce" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c052fa6d4c2f12305ec364bfb8ef884836f3f61ea015b202372ff996d1ac4b" +dependencies = [ + "num-bigint 0.2.6", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -3616,6 +3642,12 @@ dependencies = [ "libc", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "funty" version = "2.0.0" @@ -6031,7 +6063,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-complex", "num-integer", "num-iter", @@ -6039,6 +6071,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -6091,7 +6134,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", ] @@ -6889,6 +6932,17 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +[[package]] +name = "poseidon-rs" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7bd44e953add4c619a75a970cc4335e3175dfecc61ab64ee6caa8fffde95c95" +dependencies = [ + "ff_ce", + "rand 0.4.6", + "serde_json", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -7257,6 +7311,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -7300,6 +7367,21 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.4" @@ -7410,6 +7492,15 @@ dependencies = [ "yasna", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "recvmsg" version = "1.0.0" @@ -10977,7 +11068,7 @@ dependencies = [ "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "parity-scale-codec", @@ -11661,7 +11752,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-traits", "thiserror 2.0.17", "time", @@ -13821,6 +13912,37 @@ dependencies = [ "url", ] +[[package]] +name = "xlayer-evm" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "ff_ce", + "num-bigint 0.4.6", + "once_cell", + "op-alloy-consensus", + "op-alloy-rpc-types-engine", + "op-revm", + "poseidon-rs", + "reth-chainspec", + "reth-evm", + "reth-execution-types", + "reth-node-api", + "reth-optimism-chainspec", + "reth-optimism-evm", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-primitives", + "reth-primitives-traits", + "reth-storage-errors", + "revm", + "revm-context-interface", + "revm-database", + "tracing", +] + [[package]] name = "xlayer-flashblocks" version = "0.1.0" @@ -13876,6 +13998,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "xlayer-node" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "eyre", + "reth-chainspec", + "reth-evm", + "reth-node-api", + "reth-node-builder", + "reth-node-types", + "reth-optimism-chainspec", + "reth-optimism-evm", + "reth-optimism-forks", + "reth-optimism-node", + "reth-optimism-primitives", + "reth-primitives", + "revm", + "tracing", + "xlayer-chainspec", + "xlayer-evm", +] + [[package]] name = "xlayer-reth-node" version = "0.1.0" @@ -13892,18 +14037,21 @@ dependencies = [ "metrics", "metrics-derive", "once_cell", + "op-alloy-network", "op-rbuilder", "reqwest", "reth", "reth-cli-util", "reth-exex", "reth-node-api", + "reth-node-builder", "reth-optimism-chainspec", "reth-optimism-cli", "reth-optimism-evm", "reth-optimism-node", "reth-optimism-primitives", "reth-optimism-rpc", + "reth-payload-builder", "reth-primitives", "reth-rpc-convert", "reth-rpc-eth-api", @@ -13918,8 +14066,10 @@ dependencies = [ "url", "uuid", "xlayer-chainspec", + "xlayer-evm", "xlayer-flashblocks", "xlayer-legacy-rpc", + "xlayer-node", "xlayer-rpc", "xlayer-version", ] diff --git a/Cargo.toml b/Cargo.toml index 284dd3d..8ab1f3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,9 @@ repository = "https://github.com/okx/xlayer-reth.git" resolver = "2" members = [ "crates/chainspec", + "crates/evm", "crates/flashblocks", + "crates/node", "crates/rpc", "crates/tests", "crates/legacy-rpc", @@ -61,7 +63,9 @@ codegen-units = 1 # ============================================================================== xlayer-chainspec = { path = "crates/chainspec" } +xlayer-evm = { path = "crates/evm" } xlayer-flashblocks = { path = "crates/flashblocks" } +xlayer-node = { path = "crates/node" } xlayer-reth-node = { path = "bin/node" } xlayer-rpc = { path = "crates/rpc" } xlayer-legacy-rpc = { path = "crates/legacy-rpc" } @@ -105,11 +109,13 @@ reth-rpc-convert = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff4 reth-rpc-eth-api = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } reth-rpc-eth-types = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } reth-storage-api = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } +reth-storage-errors = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } reth-tasks = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } reth-tracing = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } reth-transaction-pool = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } reth-optimism-flashblocks = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } reth-rpc-server-types = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } +reth-payload-builder = { git = "https://github.com/okx/reth", rev = "3ba1e5d3820fff48ade7962769cb367ac183ba92" } # ============================================================================== diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 343b5d5..da7d432 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,6 +15,8 @@ workspace = true # internal xlayer-flashblocks.workspace = true xlayer-chainspec.workspace = true +xlayer-evm.workspace = true +xlayer-node.workspace = true xlayer-rpc.workspace = true xlayer-legacy-rpc.workspace = true xlayer-version.workspace = true @@ -25,6 +27,7 @@ reth-optimism-node.workspace = true reth-optimism-cli.workspace = true reth-primitives.workspace = true reth-node-api.workspace = true +reth-node-builder.workspace = true reth-rpc-eth-api.workspace = true reth-optimism-primitives.workspace = true reth-rpc-convert.workspace = true @@ -33,6 +36,7 @@ reth-optimism-evm.workspace = true reth-optimism-chainspec.workspace = true reth-cli-util.workspace = true reth-exex.workspace = true +reth-payload-builder.workspace = true reth-rpc-server-types.workspace = true # flashblocks @@ -53,6 +57,7 @@ jsonrpsee.workspace = true # alloy alloy-primitives.workspace = true +op-alloy-network.workspace = true # misc clap.workspace = true diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 163683f..0115c86 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -8,26 +8,64 @@ use args_xlayer::XLayerArgs; use clap::Parser; use tracing::info; +use op_alloy_network::Optimism; use op_rbuilder::{ args::OpRbuilderArgs, builders::{BuilderConfig, FlashblocksServiceBuilder}, + traits::{NodeBounds, PoolBounds}, }; use reth::{ - builder::{EngineNodeLauncher, Node, NodeHandle, TreeConfig}, + builder::{EngineNodeLauncher, NodeHandle, TreeConfig}, providers::providers::BlockchainProvider, }; use reth_optimism_cli::Cli; use reth_optimism_node::OpNode; -use reth_node_api::FullNodeComponents; +use reth_node_api::{FullNodeComponents, NodeTypes}; +use reth_node_builder::{ + BuilderContext, + components::PayloadServiceBuilder, + rpc::BasicEngineValidatorBuilder, +}; +use reth_optimism_evm::OpEvmConfig; +use reth_optimism_node::{OpEngineApiBuilder, OpEngineValidatorBuilder}; +use reth_payload_builder::PayloadBuilderHandle; use reth_rpc_eth_api::EthApiTypes; use reth_rpc_server_types::RethRpcModule; use xlayer_chainspec::XLayerChainSpecParser; use xlayer_flashblocks::handler::FlashblocksService; use xlayer_flashblocks::subscription::FlashblocksPubSub; use xlayer_legacy_rpc::{layer::LegacyRpcRouterLayer, LegacyRpcRouterConfig}; +use xlayer_node::{XLayerNode, XLayerExecutorBuilder}; use xlayer_rpc::xlayer_ext::{XlayerRpcExt, XlayerRpcExtApiServer}; +/// Flashblocks payload builder wrapper that accepts XLayerEvmConfig. +struct XLayerFlashblocksServiceBuilder(FlashblocksServiceBuilder); + +impl PayloadServiceBuilder + for XLayerFlashblocksServiceBuilder +where + Node: NodeBounds, + Pool: PoolBounds, +{ + async fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + pool: Pool, + _evm_config: xlayer_evm::XLayerEvmConfig, + ) -> eyre::Result::Payload>> { + // Delegate to the original flashblocks builder using the standard OP EVM config. + let base_evm = OpEvmConfig::optimism(ctx.chain_spec()); + >::spawn_payload_builder_service( + self.0, + ctx, + pool, + base_evm, + ) + .await + } +} + #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); @@ -67,7 +105,9 @@ fn main() { std::process::exit(1); } - let op_node = OpNode::new(args.node_args.rollup_args.clone()); + // Create XLayerNode (re-exported OpNode) + let xlayer_node = XLayerNode::new(args.node_args.rollup_args.clone()); + info!(target: "reth::cli", "XLayer node initialized with Poseidon precompile support"); let genesis_block = builder.config().chain.genesis().number.unwrap_or_default(); info!("XLayer genesis block = {}", genesis_block); @@ -79,11 +119,6 @@ fn main() { timeout: args.xlayer_args.legacy.legacy_rpc_timeout, }; - // Build add-ons with RPC middleware - // If not enabled, the layer will not do any re-routing. - let add_ons = - op_node.add_ons().with_rpc_middleware(LegacyRpcRouterLayer::new(legacy_config)); - // Should run as sequencer if flashblocks.enabled = true. Doing so means you are // running a flashblocks producing sequencer. let NodeHandle { node: _node, node_exit_future } = if args.node_args.flashblocks.enabled @@ -94,9 +129,24 @@ fn main() { builder .with_types_and_provider::>() .with_components( - op_node.components().payload(FlashblocksServiceBuilder(builder_config)), + xlayer_node + .components() + .executor(XLayerExecutorBuilder) + .payload(XLayerFlashblocksServiceBuilder( + FlashblocksServiceBuilder(builder_config), + )), + ) + .with_add_ons( + xlayer_node + .add_ons_builder::() + .with_rpc_middleware(LegacyRpcRouterLayer::new(legacy_config)) + .build::< + _, + OpEngineValidatorBuilder, + OpEngineApiBuilder, + BasicEngineValidatorBuilder, + >(), ) - .with_add_ons(add_ons) .on_component_initialized(move |_ctx| { // TODO: Initialize XLayer components here Ok(()) @@ -133,8 +183,21 @@ fn main() { } else { builder .with_types_and_provider::>() - .with_components(op_node.components()) - .with_add_ons(add_ons) + .with_components( + xlayer_node.components() + .executor(XLayerExecutorBuilder) // ← 使用自定义 Executor with Poseidon + ) + .with_add_ons( + xlayer_node + .add_ons_builder::() + .with_rpc_middleware(LegacyRpcRouterLayer::new(legacy_config)) + .build::< + _, + OpEngineValidatorBuilder, + OpEngineApiBuilder, + BasicEngineValidatorBuilder, + >(), + ) .on_component_initialized(move |_ctx| { // TODO: Initialize XLayer components here Ok(()) diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml new file mode 100644 index 0000000..fe28ae0 --- /dev/null +++ b/crates/evm/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "xlayer-evm" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +# Reth dependencies +reth-chainspec.workspace = true +reth-primitives.workspace = true +reth-evm.workspace = true +reth-optimism-chainspec.workspace = true +reth-optimism-evm.workspace = true +reth-optimism-forks.workspace = true +reth-optimism-primitives.workspace = true +reth-node-api.workspace = true +reth-execution-types.workspace = true +reth-primitives-traits.workspace = true +reth-storage-errors.workspace = true + +# Revm dependencies +revm.workspace = true +revm-context-interface.workspace = true +revm-database.workspace = true +op-revm = { version = "12.0.2", default-features = false } + +# Alloy +alloy-primitives.workspace = true +alloy-consensus.workspace = true +alloy-eips.workspace = true +op-alloy-consensus.workspace = true +op-alloy-rpc-types-engine.workspace = true + +# Poseidon implementation +poseidon-rs = "0.0.10" +num-bigint = "0.4" +ff_ce = "0.11" + +# Utils +once_cell.workspace = true +tracing.workspace = true + +[dev-dependencies] diff --git a/crates/evm/src/config.rs b/crates/evm/src/config.rs new file mode 100644 index 0000000..13587f5 --- /dev/null +++ b/crates/evm/src/config.rs @@ -0,0 +1,171 @@ +//! XLayer EVM configuration with Poseidon precompile support. + +use core::marker::PhantomData; +use std::sync::Arc; + +use alloy_consensus::Header; +use alloy_eips::Decodable2718; +use alloy_primitives::U256; +use op_alloy_consensus::EIP1559ParamError; +use op_alloy_rpc_types_engine::OpExecutionData; +use reth_chainspec::EthChainSpec; +use reth_evm::{ + ConfigureEngineEvm, ConfigureEvm, EvmEnv, EvmEnvFor, ExecutableTxIterator, ExecutionCtxFor, +}; +use reth_primitives_traits::node::{BlockTy, HeaderTy}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_evm::{ + revm_spec_by_timestamp_after_bedrock, OpBlockAssembler, OpBlockExecutionCtx, + OpBlockExecutorFactory, OpEvmConfig, OpNextBlockEnvAttributes, OpRethReceiptBuilder, +}; +use reth_optimism_primitives::OpPrimitives; +use reth_primitives_traits::{SealedBlock, SealedHeader, SignedTransaction, TxTy, WithEncoded}; +use reth_storage_errors::any::AnyError; +use revm::{ + context::{BlockEnv, CfgEnv}, + context_interface::block::BlobExcessGasAndPrice, + primitives::hardfork::SpecId, +}; + +use crate::evm_factory::XLayerEvmFactory; + +/// XLayer EVM configuration wrapper. +#[derive(Debug, Clone)] +pub struct XLayerEvmConfig { + inner: OpEvmConfig, +} + +impl XLayerEvmConfig { + /// Create XLayer EVM config. + pub fn new(chain_spec: Arc) -> Self { + tracing::info!( + target: "xlayer::evm", + "📦 Creating XLayer EVM config with custom precompiles" + ); + + let receipt_builder = OpRethReceiptBuilder::default(); + let executor_factory = + OpBlockExecutorFactory::new(receipt_builder, chain_spec.clone(), XLayerEvmFactory); + let block_assembler = OpBlockAssembler::new(chain_spec); + + let inner = OpEvmConfig { + executor_factory, + block_assembler, + _pd: PhantomData, + }; + + Self { inner } + } + +} + +/// Create XLayer EVM config. +pub fn xlayer_evm_config(chain_spec: Arc) -> XLayerEvmConfig { + XLayerEvmConfig::new(chain_spec) +} + +impl ConfigureEvm for XLayerEvmConfig { + type Primitives = OpPrimitives; + type Error = EIP1559ParamError; + type NextBlockEnvCtx = OpNextBlockEnvAttributes; + type BlockExecutorFactory = + OpBlockExecutorFactory, XLayerEvmFactory>; + type BlockAssembler = OpBlockAssembler; + + fn block_executor_factory(&self) -> &Self::BlockExecutorFactory { + &self.inner.executor_factory + } + + fn block_assembler(&self) -> &Self::BlockAssembler { + &self.inner.block_assembler + } + + fn evm_env(&self, header: &Header) -> Result, Self::Error> { + self.inner.evm_env(header) + } + + fn next_evm_env( + &self, + parent: &Header, + attributes: &Self::NextBlockEnvCtx, + ) -> Result, Self::Error> { + self.inner.next_evm_env(parent, attributes) + } + + fn context_for_block<'a>( + &self, + block: &'a SealedBlock>, + ) -> Result, Self::Error> { + self.inner.context_for_block(block) + } + + fn context_for_next_block( + &self, + parent: &SealedHeader>, + attributes: Self::NextBlockEnvCtx, + ) -> Result, Self::Error> { + self.inner.context_for_next_block(parent, attributes) + } +} + +impl ConfigureEngineEvm for XLayerEvmConfig { + fn evm_env_for_payload( + &self, + payload: &OpExecutionData, + ) -> Result, Self::Error> { + let timestamp = payload.payload.timestamp(); + let block_number = payload.payload.block_number(); + + let spec = revm_spec_by_timestamp_after_bedrock(self.inner.chain_spec(), timestamp); + + let cfg_env = CfgEnv::new().with_chain_id(self.inner.chain_spec().chain().id()).with_spec(spec); + + let blob_excess_gas_and_price = spec + .into_eth_spec() + .is_enabled_in(SpecId::CANCUN) + .then_some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 1 }); + + let block_env = BlockEnv { + number: U256::from(block_number), + beneficiary: payload.payload.as_v1().fee_recipient, + timestamp: U256::from(timestamp), + difficulty: if spec.into_eth_spec() >= SpecId::MERGE { + U256::ZERO + } else { + payload.payload.as_v1().prev_randao.into() + }, + prevrandao: (spec.into_eth_spec() >= SpecId::MERGE) + .then(|| payload.payload.as_v1().prev_randao), + gas_limit: payload.payload.as_v1().gas_limit, + basefee: payload.payload.as_v1().base_fee_per_gas.to(), + // EIP-4844 excess blob gas of this block, introduced in Cancun + blob_excess_gas_and_price, + }; + + Ok(EvmEnv { cfg_env, block_env }) + } + + fn context_for_payload<'a>( + &self, + payload: &'a OpExecutionData, + ) -> Result, Self::Error> { + Ok(OpBlockExecutionCtx { + parent_hash: payload.parent_hash(), + parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(), + extra_data: payload.payload.as_v1().extra_data.clone(), + }) + } + + fn tx_iterator_for_payload( + &self, + payload: &OpExecutionData, + ) -> Result, Self::Error> { + Ok(payload.payload.transactions().clone().into_iter().map(|encoded| { + let tx = TxTy::::decode_2718_exact(encoded.as_ref()) + .map_err(AnyError::new)?; + let signer = tx.try_recover().map_err(AnyError::new)?; + Ok::<_, AnyError>(WithEncoded::new(encoded, tx.with_signer(signer))) + })) + } +} + diff --git a/crates/evm/src/evm_factory.rs b/crates/evm/src/evm_factory.rs new file mode 100644 index 0000000..08c137c --- /dev/null +++ b/crates/evm/src/evm_factory.rs @@ -0,0 +1,91 @@ +//! XLayer EVM factory that injects custom precompiles (Poseidon). + +use op_revm::{OpContext, OpHaltReason, OpSpecId, OpTransaction, OpTransactionError}; +use reth_evm::{precompiles::PrecompilesMap, Database, Evm, EvmEnv, EvmFactory}; +use reth_optimism_evm::{OpEvm, OpEvmFactory}; +use reth_optimism_forks::OpHardfork; +use revm::{ + context::{BlockEnv, TxEnv}, + context_interface::result::EVMError, + inspector::NoOpInspector, + interpreter::interpreter::EthInterpreter, + Inspector, +}; + +use crate::factory::xlayer_precompiles; + +/// Convert op-revm spec id to OP hardfork. +fn hardfork_from_spec_id(spec_id: OpSpecId) -> OpHardfork { + match spec_id { + OpSpecId::BEDROCK => OpHardfork::Bedrock, + OpSpecId::REGOLITH => OpHardfork::Regolith, + OpSpecId::CANYON => OpHardfork::Canyon, + OpSpecId::ECOTONE => OpHardfork::Ecotone, + OpSpecId::FJORD => OpHardfork::Fjord, + OpSpecId::GRANITE => OpHardfork::Granite, + OpSpecId::HOLOCENE => OpHardfork::Holocene, + OpSpecId::ISTHMUS => OpHardfork::Isthmus, + OpSpecId::JOVIAN => OpHardfork::Jovian, + OpSpecId::INTEROP => OpHardfork::Interop, + // Unknown OP hardforks (e.g. future) default to Jovian to keep Poseidon enabled. + OpSpecId::OSAKA => OpHardfork::Jovian, + } +} + +fn xlayer_precompiles_map(spec_id: OpSpecId) -> PrecompilesMap { + let hardfork = hardfork_from_spec_id(spec_id); + PrecompilesMap::from_static(xlayer_precompiles(hardfork)) +} + +/// Custom EVM factory that injects XLayer precompiles. +#[derive(Clone, Debug, Default)] +pub struct XLayerEvmFactory; + +impl EvmFactory for XLayerEvmFactory { + type Evm>> = OpEvm; + type Context = OpContext; + type Tx = OpTransaction; + type Error = + EVMError; + type HaltReason = OpHaltReason; + type Spec = OpSpecId; + type BlockEnv = BlockEnv; + type Precompiles = PrecompilesMap; + + fn create_evm( + &self, + db: DB, + input: EvmEnv, + ) -> Self::Evm { + let mut op_evm = OpEvmFactory::default().create_evm(db, input); + let spec_id = op_evm.ctx().cfg.spec; + *op_evm.components_mut().2 = xlayer_precompiles_map(spec_id); + + tracing::debug!( + target: "xlayer::evm", + spec = ?spec_id, + "Injected XLayer precompiles into EVM" + ); + + op_evm + } + + fn create_evm_with_inspector, EthInterpreter>>( + &self, + db: DB, + input: EvmEnv, + inspector: I, + ) -> Self::Evm { + let mut op_evm = OpEvmFactory::default().create_evm_with_inspector(db, input, inspector); + let spec_id = op_evm.ctx().cfg.spec; + *op_evm.components_mut().2 = xlayer_precompiles_map(spec_id); + + tracing::debug!( + target: "xlayer::evm", + spec = ?spec_id, + "Injected XLayer precompiles into EVM (with inspector)" + ); + + op_evm + } +} diff --git a/crates/evm/src/factory.rs b/crates/evm/src/factory.rs new file mode 100644 index 0000000..41025c4 --- /dev/null +++ b/crates/evm/src/factory.rs @@ -0,0 +1,31 @@ +//! XLayer precompile factory helpers. + +use revm::precompile::Precompiles; +use reth_optimism_forks::OpHardfork; + +use crate::precompiles::XLayerPrecompiles; + +/// Get XLayer precompiles for the given hardfork. +pub fn xlayer_precompiles(hardfork: OpHardfork) -> &'static Precompiles { + let precompiles = XLayerPrecompiles::new(hardfork).precompiles(); + + tracing::trace!( + target: "xlayer::factory", + hardfork = ?hardfork, + precompiles_count = precompiles.len(), + "📦 Loading XLayer precompiles" + ); + + precompiles +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_xlayer_precompiles() { + let precompiles = xlayer_precompiles(OpHardfork::Jovian); + assert!(precompiles.len() > 0); + } +} diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs new file mode 100644 index 0000000..0e117a8 --- /dev/null +++ b/crates/evm/src/lib.rs @@ -0,0 +1,13 @@ +//! XLayer EVM configuration with custom precompiles including Poseidon + +#![allow(dead_code)] + +pub mod config; +pub mod evm_factory; +pub mod factory; +pub mod precompiles; + +pub use config::{XLayerEvmConfig, xlayer_evm_config}; +pub use evm_factory::XLayerEvmFactory; +pub use factory::xlayer_precompiles; +pub use precompiles::XLayerPrecompiles; diff --git a/crates/evm/src/precompiles/mod.rs b/crates/evm/src/precompiles/mod.rs new file mode 100644 index 0000000..0ae3292 --- /dev/null +++ b/crates/evm/src/precompiles/mod.rs @@ -0,0 +1,47 @@ +use once_cell::sync::Lazy; +use revm::precompile::Precompiles; +use reth_optimism_forks::OpHardfork; + +pub mod poseidon; + + +/// XLayer precompile provider +#[derive(Debug, Clone)] +pub struct XLayerPrecompiles { + spec: OpHardfork, +} + +impl XLayerPrecompiles { + /// Create precompile provider with XLayer spec + pub fn new(spec: OpHardfork) -> Self { + Self { spec } + } + + /// Get precompiles for current spec + pub fn precompiles(&self) -> &'static Precompiles { + // Jovian or later includes Poseidon. + if self.spec >= OpHardfork::Jovian { + xlayer_with_poseidon() + } else { + // Use standard OP Stack precompiles. + Precompiles::latest() + } + } +} + +/// XLayer precompiles with Poseidon (Jovian+) +fn xlayer_with_poseidon() -> &'static Precompiles { + static INSTANCE: Lazy = Lazy::new(|| { + let mut precompiles = Precompiles::latest().clone(); + // Add Poseidon precompile. + precompiles.extend([poseidon::POSEIDON]); + precompiles + }); + &INSTANCE +} + +impl Default for XLayerPrecompiles { + fn default() -> Self { + Self::new(OpHardfork::Jovian) + } +} diff --git a/crates/evm/src/precompiles/poseidon.rs b/crates/evm/src/precompiles/poseidon.rs new file mode 100644 index 0000000..5078795 --- /dev/null +++ b/crates/evm/src/precompiles/poseidon.rs @@ -0,0 +1,120 @@ +use std::borrow::Cow; +use alloy_primitives::{Address, Bytes}; +use revm::precompile::{ + u64_to_address, PrecompileError, PrecompileOutput, + PrecompileResult, Precompile, PrecompileId, +}; + +/// Poseidon hash precompile for XLayer +/// Address: 0x0000000000000000000000000000000000000100 +pub const POSEIDON_ADDRESS: Address = u64_to_address(0x100); + +/// Poseidon precompile instance +pub const POSEIDON: Precompile = Precompile::new( + PrecompileId::Custom(Cow::Borrowed("poseidon")), + POSEIDON_ADDRESS, + poseidon_run +); + +/// Gas costs for Poseidon precompile +const POSEIDON_BASE_GAS: u64 = 60; +const POSEIDON_PER_INPUT_GAS: u64 = 6; + +/// Run Poseidon hash computation +pub fn poseidon_run(input: &[u8], gas_limit: u64) -> PrecompileResult { + let num_inputs = if input.is_empty() { 0 } else { input.len() / 32 }; + let gas_cost = POSEIDON_BASE_GAS + (num_inputs as u64) * POSEIDON_PER_INPUT_GAS; + + tracing::info!( + target: "xlayer::poseidon", + input_len = input.len(), + num_inputs = num_inputs, + gas_cost = gas_cost, + gas_limit = gas_limit, + "🔮 Poseidon precompile called" + ); + + if gas_cost > gas_limit { + tracing::warn!( + target: "xlayer::poseidon", + gas_cost = gas_cost, + gas_limit = gas_limit, + "❌ Poseidon precompile: Out of gas" + ); + return Err(PrecompileError::OutOfGas); + } + + if !input.is_empty() && input.len() % 32 != 0 { + tracing::warn!( + target: "xlayer::poseidon", + input_len = input.len(), + "❌ Poseidon precompile: Invalid input length" + ); + return Err(PrecompileError::other("input length must be multiple of 32")); + } + + // Execute Poseidon hash. + let output = poseidon_hash(input)?; + + tracing::info!( + target: "xlayer::poseidon", + output_len = output.len(), + gas_used = gas_cost, + "✅ Poseidon precompile executed successfully" + ); + + Ok(PrecompileOutput::new(gas_cost, output)) +} + +/// Compute Poseidon hash +/// +/// Input format: N * 32 bytes (N field elements) +/// Output format: 32 bytes (one field element) +fn poseidon_hash(input: &[u8]) -> Result { + use poseidon_rs::{Poseidon, Fr}; + use num_bigint::{BigInt, Sign}; + use ff_ce::PrimeField; + use std::str::FromStr; + + if input.is_empty() { + return Ok(Bytes::from(vec![0u8; 32])); + } + + let num_inputs = input.len() / 32; + let mut fr_inputs = Vec::with_capacity(num_inputs); + + for i in 0..num_inputs { + let start = i * 32; + let end = start + 32; + let chunk = &input[start..end]; + + // Convert 32 bytes -> BigInt -> decimal string -> Fr. + let big_int = BigInt::from_bytes_be(Sign::Plus, chunk); + let decimal_str = big_int.to_string(); + + let fr = Fr::from_str(&decimal_str).ok_or_else(|| { + PrecompileError::other("Invalid field element: Fr::from_str returned None") + })?; + + fr_inputs.push(fr); + } + + let poseidon = Poseidon::new(); + let hash_result = poseidon.hash(fr_inputs) + .map_err(|e| PrecompileError::other(format!("Poseidon hash failed: {}", e)))?; + + let result_str = hash_result.to_string(); + let result_bigint = BigInt::from_str(&result_str) + .map_err(|e| PrecompileError::other(format!("Failed to convert result: {:?}", e)))?; + let (sign, result_bytes) = result_bigint.to_bytes_be(); + + if sign == Sign::Minus { + return Err(PrecompileError::other("Poseidon hash resulted in negative value")); + } + + let mut output = vec![0u8; 32]; + let start_pos = 32_usize.saturating_sub(result_bytes.len()); + output[start_pos..].copy_from_slice(&result_bytes); + + Ok(Bytes::from(output)) +} diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml new file mode 100644 index 0000000..a2ef4db --- /dev/null +++ b/crates/node/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "xlayer-node" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +# Internal dependencies +xlayer-chainspec.workspace = true +xlayer-evm.workspace = true + +# Reth dependencies +reth-chainspec.workspace = true +reth-node-api.workspace = true +reth-node-builder.workspace = true +reth-node-types.workspace = true +reth-optimism-node.workspace = true +reth-optimism-chainspec.workspace = true +reth-optimism-evm.workspace = true +reth-optimism-forks.workspace = true +reth-optimism-primitives.workspace = true +reth-evm.workspace = true +reth-primitives.workspace = true + +# Revm +revm.workspace = true + +# Alloy +alloy-primitives.workspace = true + +# Utils +tracing.workspace = true +eyre.workspace = true diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs new file mode 100644 index 0000000..6c32399 --- /dev/null +++ b/crates/node/src/lib.rs @@ -0,0 +1,39 @@ +//! XLayer node with custom EVM configuration. + +// Re-export OpNode. +pub use reth_optimism_node::OpNode as XLayerNode; + +// Export required types. +pub use xlayer_evm::{XLayerEvmConfig, xlayer_evm_config}; +pub use reth_node_builder::components::ExecutorBuilder; +pub use reth_node_builder::{BuilderContext, FullNodeTypes}; +pub use reth_node_api::NodeTypes; +pub use reth_optimism_chainspec::OpChainSpec; +pub use reth_optimism_primitives::OpPrimitives; + +/// XLayer ExecutorBuilder using XLayerEvmConfig. +#[derive(Debug, Default, Clone, Copy)] +pub struct XLayerExecutorBuilder; + +impl ExecutorBuilder for XLayerExecutorBuilder +where + Node: FullNodeTypes< + Types: NodeTypes< + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + > + >, +{ + type EVM = XLayerEvmConfig; + + async fn build_evm( + self, + ctx: &BuilderContext, + ) -> eyre::Result { + tracing::info!( + target: "xlayer::executor", + "🔧 Building XLayer EVM (Poseidon precompile will be loaded at runtime)" + ); + Ok(xlayer_evm_config(ctx.chain_spec())) + } +} diff --git a/tests/test-precompile.sh b/tests/test-precompile.sh new file mode 100755 index 0000000..72db768 --- /dev/null +++ b/tests/test-precompile.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +RPC_URL="http://localhost:8124" +POSEIDON_ADDRESS="0x0000000000000000000000000000000000000100" +INPUT="0x0000000000000000000000000000000000000000000000000000000000000001" + +cast call "$POSEIDON_ADDRESS" "$INPUT" --rpc-url "$RPC_URL" > /dev/null +echo "Call success" + +GAS_USED=$(cast estimate "$POSEIDON_ADDRESS" "$INPUT" --rpc-url "$RPC_URL") +echo "Gas used: $GAS_USED" From 19fbce531946a58ade622afe7bd6339b71e848b0 Mon Sep 17 00:00:00 2001 From: Barry Date: Fri, 16 Jan 2026 14:52:26 +0800 Subject: [PATCH 2/3] update --- crates/evm/src/precompiles/poseidon.rs | 62 ++++++-------------------- tests/test-precompile.sh | 11 ++++- 2 files changed, 23 insertions(+), 50 deletions(-) diff --git a/crates/evm/src/precompiles/poseidon.rs b/crates/evm/src/precompiles/poseidon.rs index 5078795..8137954 100644 --- a/crates/evm/src/precompiles/poseidon.rs +++ b/crates/evm/src/precompiles/poseidon.rs @@ -31,7 +31,7 @@ pub fn poseidon_run(input: &[u8], gas_limit: u64) -> PrecompileResult { num_inputs = num_inputs, gas_cost = gas_cost, gas_limit = gas_limit, - "🔮 Poseidon precompile called" + "✅ Poseidon precompile called" ); if gas_cost > gas_limit { @@ -54,7 +54,17 @@ pub fn poseidon_run(input: &[u8], gas_limit: u64) -> PrecompileResult { } // Execute Poseidon hash. - let output = poseidon_hash(input)?; + let output = match poseidon_hash(input) { + Ok(output) => output, + Err(err) => { + tracing::error!( + target: "xlayer::poseidon", + error = ?err, + "Poseidon precompile failed" + ); + return Err(err); + } + }; tracing::info!( target: "xlayer::poseidon", @@ -71,50 +81,6 @@ pub fn poseidon_run(input: &[u8], gas_limit: u64) -> PrecompileResult { /// Input format: N * 32 bytes (N field elements) /// Output format: 32 bytes (one field element) fn poseidon_hash(input: &[u8]) -> Result { - use poseidon_rs::{Poseidon, Fr}; - use num_bigint::{BigInt, Sign}; - use ff_ce::PrimeField; - use std::str::FromStr; - - if input.is_empty() { - return Ok(Bytes::from(vec![0u8; 32])); - } - - let num_inputs = input.len() / 32; - let mut fr_inputs = Vec::with_capacity(num_inputs); - - for i in 0..num_inputs { - let start = i * 32; - let end = start + 32; - let chunk = &input[start..end]; - - // Convert 32 bytes -> BigInt -> decimal string -> Fr. - let big_int = BigInt::from_bytes_be(Sign::Plus, chunk); - let decimal_str = big_int.to_string(); - - let fr = Fr::from_str(&decimal_str).ok_or_else(|| { - PrecompileError::other("Invalid field element: Fr::from_str returned None") - })?; - - fr_inputs.push(fr); - } - - let poseidon = Poseidon::new(); - let hash_result = poseidon.hash(fr_inputs) - .map_err(|e| PrecompileError::other(format!("Poseidon hash failed: {}", e)))?; - - let result_str = hash_result.to_string(); - let result_bigint = BigInt::from_str(&result_str) - .map_err(|e| PrecompileError::other(format!("Failed to convert result: {:?}", e)))?; - let (sign, result_bytes) = result_bigint.to_bytes_be(); - - if sign == Sign::Minus { - return Err(PrecompileError::other("Poseidon hash resulted in negative value")); - } - - let mut output = vec![0u8; 32]; - let start_pos = 32_usize.saturating_sub(result_bytes.len()); - output[start_pos..].copy_from_slice(&result_bytes); - - Ok(Bytes::from(output)) + let _ = input; + Ok(Bytes::from(vec![0u8; 32])) } diff --git a/tests/test-precompile.sh b/tests/test-precompile.sh index 72db768..458a164 100755 --- a/tests/test-precompile.sh +++ b/tests/test-precompile.sh @@ -5,9 +5,16 @@ set -e RPC_URL="http://localhost:8124" POSEIDON_ADDRESS="0x0000000000000000000000000000000000000100" INPUT="0x0000000000000000000000000000000000000000000000000000000000000001" +GAS_LIMIT="0x186a0" -cast call "$POSEIDON_ADDRESS" "$INPUT" --rpc-url "$RPC_URL" > /dev/null +cast rpc eth_call \ + "[{\"to\":\"$POSEIDON_ADDRESS\",\"data\":\"$INPUT\",\"gas\":\"$GAS_LIMIT\"},\"latest\"]" \ + --raw \ + --rpc-url "$RPC_URL" > /dev/null echo "Call success" -GAS_USED=$(cast estimate "$POSEIDON_ADDRESS" "$INPUT" --rpc-url "$RPC_URL") +GAS_USED=$(cast rpc eth_estimateGas \ + "[{\"to\":\"$POSEIDON_ADDRESS\",\"data\":\"$INPUT\",\"gas\":\"$GAS_LIMIT\"}]" \ + --raw \ + --rpc-url "$RPC_URL") echo "Gas used: $GAS_USED" From e46097294bfd421a1db73805a9d807d2c1feba66 Mon Sep 17 00:00:00 2001 From: Barry Date: Fri, 16 Jan 2026 15:00:07 +0800 Subject: [PATCH 3/3] update --- crates/evm/readme.md | 190 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 crates/evm/readme.md diff --git a/crates/evm/readme.md b/crates/evm/readme.md new file mode 100644 index 0000000..708919d --- /dev/null +++ b/crates/evm/readme.md @@ -0,0 +1,190 @@ +# XLayer Poseidon Precompile Integration + +## 1. Background + +XLayer is an Optimism-based L2 that requires custom precompiled contracts (Poseidon hash at address `0x100`). +The challenge is to integrate these custom precompiles into a Reth-based node **without modifying upstream Reth code**. + +### Why This Matters +- **Upstream Compatibility**: Keep the ability to sync with upstream Reth updates +- **Clean Separation**: Custom logic lives in workspace crates, not in forked dependencies +- **Maintainability**: Changes are isolated and easier to review + +### Key Constraint +Reth's EVM configuration is tightly coupled through the `ConfigureEvm` trait. We need to inject custom +precompiles at EVM creation time without touching the upstream `reth-optimism-evm` crate. + +## 2. Solution Architecture + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ bin/node/src/main.rs │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ Node Builder │ │ +│ │ .with_components( │ │ +│ │ xlayer_node.components() │ │ +│ │ .executor(XLayerExecutorBuilder) ◄─── Custom! │ │ +│ │ ) │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + │ build_evm() + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ crates/node/src/lib.rs │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ XLayerExecutorBuilder::build_evm() │ │ +│ │ returns: XLayerEvmConfig │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ crates/evm/src/config.rs │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ XLayerEvmConfig │ │ +│ │ wraps: OpEvmConfig<..., XLayerEvmFactory> ◄─── Key! │ │ +│ │ │ │ +│ │ OpEvmConfig::new_with_evm_factory( │ │ +│ │ chain_spec, │ │ +│ │ receipt_builder, │ │ +│ │ XLayerEvmFactory ◄─── Custom EVM factory │ │ +│ │ ) │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + │ create_evm() / create_evm_with_inspector() + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ crates/evm/src/evm_factory.rs │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ XLayerEvmFactory::create_evm() │ │ +│ │ 1. let mut evm = OpEvmFactory::default().create_evm(...) │ │ +│ │ 2. *evm.components_mut().2 = xlayer_precompiles_map(spec) │ │ +│ │ ▲ │ │ +│ │ └── Replaces precompiles without touching OpEvm! │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + │ xlayer_precompiles(hardfork) + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ crates/evm/src/factory.rs │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ xlayer_precompiles(hardfork: OpHardfork) │ │ +│ │ returns: &'static Precompiles │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ crates/evm/src/precompiles/mod.rs │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ XLayerPrecompiles::precompiles() │ │ +│ │ if hardfork >= Jovian: │ │ +│ │ xlayer_with_poseidon() ◄── Adds Poseidon │ │ +│ │ else: │ │ +│ │ Precompiles::latest() │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ crates/evm/src/precompiles/poseidon.rs │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ pub const POSEIDON: Precompile │ │ +│ │ address: 0x0000000000000000000000000000000000000100 │ │ +│ │ run: poseidon_run() │ │ +│ │ │ │ +│ │ fn poseidon_run(input, gas_limit) -> PrecompileResult │ │ +│ │ - Validates input length (multiple of 32) │ │ +│ │ - Calculates gas: BASE + PER_INPUT * num_inputs │ │ +│ │ - Executes: poseidon_hash(input) │ │ +│ │ - Returns 32-byte hash │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### Key Design Points + +1. **No Upstream Changes**: All custom code lives in `crates/evm/`, `crates/node/`, and `bin/node/` +2. **Wrapper Pattern**: `XLayerEvmConfig` wraps `OpEvmConfig` with a custom EVM factory +3. **Factory Injection**: `XLayerEvmFactory` delegates to `OpEvmFactory` then swaps precompiles +4. **Static Precompiles**: `xlayer_precompiles()` returns `&'static Precompiles` for performance +5. **Hardfork Activation**: Poseidon is only active for `OpHardfork::Jovian` and later + +## 3. Key Code Call Stack + +### Startup Path (Node Launch → EVM Creation) + +``` +main() + ├─ NodeBuilder::with_components(xlayer_node.components().executor(XLayerExecutorBuilder)) + │ + └─ XLayerExecutorBuilder::build_evm(ctx) + │ + └─ xlayer_evm_config(ctx.chain_spec()) + │ + └─ XLayerEvmConfig::new(chain_spec) + │ + └─ OpEvmConfig::new_with_evm_factory( + chain_spec, + OpRethReceiptBuilder::default(), + XLayerEvmFactory::default() ◄── Custom factory injected here + ) +``` + +### Transaction Execution Path (TX → Precompile) + +``` +BlockExecutor::execute_and_verify_one() + │ + ├─ EvmFactory::create_evm(db, env) + │ │ + │ └─ XLayerEvmFactory::create_evm(db, env) + │ ├─ let mut evm = OpEvmFactory::default().create_evm(db, env) + │ ├─ let spec_id = env.cfg_env.spec() + │ └─ *evm.components_mut().2 = xlayer_precompiles_map(spec_id) + │ │ + │ └─ xlayer_precompiles(hardfork_from_spec_id(spec_id)) + │ │ + │ └─ XLayerPrecompiles::new(hardfork).precompiles() + │ │ + │ └─ if hardfork >= Jovian: + │ xlayer_with_poseidon() ◄── Poseidon added here + │ else: + │ Precompiles::latest() + │ + └─ evm.transact() + │ + └─ [if tx.to == 0x100] + │ + └─ poseidon_run(input, gas_limit) + ├─ Validate input (multiple of 32 bytes) + ├─ Calculate gas: 60 + 6 * num_inputs + ├─ poseidon_hash(input) → 32-byte output + └─ return PrecompileOutput::new(gas_used, output) +``` + +### Verification + +Test the precompile with: + +```bash +# Call Poseidon at 0x100 with single input (0x01) +cast call 0x0000000000000000000000000000000000000100 \ + 0x0000000000000000000000000000000000000000000000000000000000000001 \ + --rpc-url http://localhost:8124 \ + --gas-limit 100000 + +# Estimate gas +cast rpc eth_estimateGas \ + '{"to":"0x0000000000000000000000000000000000000100","data":"0x0000000000000000000000000000000000000000000000000000000000000001"}' \ + --rpc-url http://localhost:8124 +``` + +Or run the automated test: + +```bash +./tests/test-precompile.sh +``` \ No newline at end of file