diff --git a/Cargo.lock b/Cargo.lock index 1828b2a2faef..cbedd079e380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2911,6 +2911,33 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exex-custom-rpc" +version = "0.0.0" +dependencies = [ + "alloy-rpc-types 0.1.0 (git+https://github.com/alloy-rs/alloy?rev=af788af)", + "alloy-sol-types", + "async-trait", + "clap", + "eyre", + "futures", + "jsonrpsee", + "reth", + "reth-db", + "reth-exex", + "reth-node-api", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives", + "reth-provider", + "reth-rpc", + "reth-rpc-api", + "reth-tracing", + "rusqlite", + "serde_json", + "tokio", +] + [[package]] name = "exex-minimal" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 0aca2afbbaaa..c69c10377d70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ members = [ "crates/trie/", "crates/trie-parallel/", "examples/node-custom-rpc/", + "examples/exex/custom-rpc", "examples/beacon-api-sse/", "examples/node-event-hooks/", "examples/custom-evm/", diff --git a/examples/exex/custom-rpc/Cargo.toml b/examples/exex/custom-rpc/Cargo.toml new file mode 100644 index 000000000000..40e1d6dee067 --- /dev/null +++ b/examples/exex/custom-rpc/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "exex-custom-rpc" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +reth.workspace = true +reth-exex.workspace = true +reth-node-api.workspace = true +reth-node-core.workspace = true +reth-node-ethereum.workspace = true +reth-primitives.workspace = true +reth-tracing.workspace = true +reth-provider.workspace = true +reth-rpc-api = {workspace = true, features = ["client"]} +reth-rpc.workspace = true +eyre.workspace = true +tokio.workspace = true +futures.workspace = true +reth-db.workspace = true +alloy-sol-types = { workspace = true, features = ["json"] } +alloy-rpc-types.workspace = true +clap = { workspace = true, features = ["derive"] } +jsonrpsee = { workspace = true, features = ["server", "macros"] } +async-trait.workspace = true +rusqlite = { version = "0.31.0", features = ["bundled"] } +serde_json.workspace = true + diff --git a/examples/exex/custom-rpc/l1_standard_bridge_abi.json b/examples/exex/custom-rpc/l1_standard_bridge_abi.json new file mode 100644 index 000000000000..4ae6406f0793 --- /dev/null +++ b/examples/exex/custom-rpc/l1_standard_bridge_abi.json @@ -0,0 +1,664 @@ +[ + { + "inputs": [ + { + "internalType": "address payable", + "name": "_messenger", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20BridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "localToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "remoteToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20BridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20DepositInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "l1Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "l2Token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ERC20WithdrawalFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHBridgeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHBridgeInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHDepositInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "ETHWithdrawalFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "inputs": [], + "name": "MESSENGER", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OTHER_BRIDGE", + "outputs": [ + { + "internalType": "contract StandardBridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeERC20To", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeETHTo", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "depositERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "depositERC20To", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "depositETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "depositETHTo", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "deposits", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_localToken", + "type": "address" + }, + { + "internalType": "address", + "name": "_remoteToken", + "type": "address" + }, + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "finalizeBridgeERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "finalizeBridgeETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_l1Token", + "type": "address" + }, + { + "internalType": "address", + "name": "_l2Token", + "type": "address" + }, + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "finalizeERC20Withdrawal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_from", "type": "address" }, + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint256", "name": "_amount", "type": "uint256" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "finalizeETHWithdrawal", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract SuperchainConfig", + "name": "_superchainConfig", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "l2TokenBridge", + "outputs": [ + { "internalType": "address", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messenger", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "otherBridge", + "outputs": [ + { + "internalType": "contract StandardBridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract SuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/examples/exex/custom-rpc/src/main.rs b/examples/exex/custom-rpc/src/main.rs new file mode 100644 index 000000000000..4236afe8029a --- /dev/null +++ b/examples/exex/custom-rpc/src/main.rs @@ -0,0 +1,310 @@ +use alloy_rpc_types::{BlockNumberOrTag, Filter}; +use alloy_sol_types::{sol, SolEventInterface}; +use clap::{Id, Parser}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::error::ErrorObject}; +use reth_node_ethereum::EthereumNode; +use reth_primitives::{BlockNumber, Log, SealedBlockWithSenders, TransactionSigned}; +use reth_provider::{BlockReaderIdExt, Chain}; +sol!(L1StandardBridge, "l1_standard_bridge_abi.json"); +use crate::L1StandardBridge::{ETHBridgeFinalized, ETHBridgeInitiated, L1StandardBridgeEvents}; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::default::Default; +use std::{path::Path}; +use reth::{ + primitives::ChainSpecBuilder, + providers::{providers::BlockchainProvider, ProviderFactory}, + utils::db::open_db_read_only, +}; +use reth_node_ethereum::EthEvmConfig; + +use reth::{ + blockchain_tree::noop::NoopBlockchainTree, providers::test_utils::TestCanonStateSubscriptions, + tasks::TokioTaskExecutor, +}; +use reth_db::{mdbx::DatabaseArguments, models::client_version::ClientVersion}; +// Bringing up the RPC +use reth::rpc::builder::{ + RethRpcModule, RpcModuleBuilder, RpcServerConfig, TransportRpcModuleConfig, +}; +#[derive(Debug)] +struct Deposit { + id: i32, + block_number: u64, +} + +/// Our custom cli args extension that adds one flag to reth default CLI. +#[derive(Debug, Clone, Copy, Default, clap::Args)] +struct RethCliOpDepositCountExt { + #[arg(long)] + pub enable_ext: bool, +} + +/// trait interface for a custom rpc namespace: `opdepositcount` +/// +/// This defines an additional namespace where all methods are configured as trait functions. +#[rpc[server, namespace="onDepositCounts"]] +pub trait OpDepositCountExtApi { + #[method(name = "opdepositCount")] + fn op_deposit_count(&self) -> RpcResult; +} + +pub struct OpDepositCountExt { + provider: Provider, +} + +impl

OpDepositCountExt

+where + P: BlockReaderIdExt +'static, +{ + pub fn new(provider: P) -> OpDepositCountExt

{ + Self { provider } + } +} + +impl

OpDepositCountExtApiServer for OpDepositCountExt

+where + P: BlockReaderIdExt + 'static, +{ + fn op_deposit_count(&self) -> RpcResult { + + todo!() + +} +} +use reth_exex::{ExExContext, ExExEvent}; +use reth_node_api::FullNodeComponents; +use reth_tracing::tracing::info; +use rusqlite::{params, Connection}; + +struct OpCount { + ctx: ExExContext, + db: Database, +} + +impl OpCount { + fn new(ctx: ExExContext, connection: Connection) -> eyre::Result { + let db = Database::new(connection)?; + Ok(Self { ctx, db }) + } + + async fn start(mut self) -> eyre::Result<()> { + // Process all new chain state notifications + while let Some(notification) = self.ctx.notifications.recv().await { + // Revert all deposits and withdrawals + if let Some(reverted_chain) = notification.reverted_chain() { + let events = decode_chain_into_events(&reverted_chain); + + let mut deposits = 0; + + for (_, tx, _, event) in events { + match event { + // L1 -> L2 deposit + L1StandardBridgeEvents::ETHBridgeInitiated(ETHBridgeInitiated { + .. + }) => { + let deleted = self.db.connection().execute( + "DELETE FROM deposits WHERE tx_hash = ?;", + (tx.hash().to_string(),), + )?; + deposits += deleted; + } + // L2 -> L1 withdrawal + L1StandardBridgeEvents::ETHBridgeFinalized(ETHBridgeFinalized { + .. + }) => {} + _ => continue, + } + } + + info!(block_range = ?reverted_chain.range(), %deposits, "Reverted chain events"); + } + + // Insert all new deposits and withdrawals + if let Some(committed_chain) = notification.committed_chain() { + let events = decode_chain_into_events(&committed_chain); + + let mut deposits = 0; + + for (block, tx, log, event) in events { + match event { + // L1 -> L2 deposit + L1StandardBridgeEvents::ETHBridgeInitiated(ETHBridgeInitiated { + amount, + from, + to, + .. + }) => { + let inserted = self.db.connection().execute( + r#" + INSERT INTO deposits (block_number, tx_hash) + VALUES (?, ?) + "#, + ( + block.number, + tx.hash().to_string(), + ), + )?; + deposits += inserted; + } + // L2 -> L1 withdrawal + L1StandardBridgeEvents::ETHBridgeFinalized(ETHBridgeFinalized { + amount, + from, + to, + .. + }) => {} + _ => continue, + }; + } + + info!(block_range = ?committed_chain.range(), %deposits, "Committed chain events"); + + // Send a finished height event, signaling the node that we don't need any blocks + // below this height anymore + self.ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; + } + } + + Ok(()) + } +} + +pub struct Database { + connection: Arc>, +} + +impl Database { + /// Create new database with the provided connection. + pub fn new(connection: Connection) -> eyre::Result { + let database = Self { connection: Arc::new(Mutex::new(connection)) }; + database.create_tables()?; + Ok(database) + } + + fn connection(&self) -> MutexGuard<'_, Connection> { + self.connection.lock().expect("failed to acquire database lock") + } + + pub fn get_op_deposit_count( + &self, + block_number: BlockNumber, + ) -> eyre::Result> { + let mut deposit_count = 0; + let connection = self.connection(); + let mut stmt = connection + .prepare("SELECT id, block_number FROM deposits WHERE block_number = ?")?; + + let deposit_iter = stmt.query_map([block_number], |row| { + Ok(Deposit { id: row.get(0)?, block_number: row.get(1)? }) + })?; + + for deposit in deposit_iter { + match deposit { + Ok(_dep) => { + deposit_count += 1; + } + Err(e) => { + return Err(e.into()); + } + } + } + Ok(Some(deposit_count)) + } + + /// Create SQLite tables if they do not exist. + fn create_tables(&self) -> rusqlite::Result<()> { + // Create deposits and withdrawals tables + self.connection().execute( + r#" + CREATE TABLE IF NOT EXISTS deposits ( + id INTEGER PRIMARY KEY, + block_number INTEGER NOT NULL + tx_hash TEXT NOT NULL UNIQUE, + ); + "#, + (), + )?; + + Ok(()) + } +} + +/// Decode chain of blocks into a flattened list of receipt logs, and filter only +/// [L1StandardBridgeEvents]. +fn decode_chain_into_events( + chain: &Chain, +) -> impl Iterator +{ + chain + // Get all blocks and receipts + .blocks_and_receipts() + // Get all receipts + .flat_map(|(block, receipts)| { + block + .body + .iter() + .zip(receipts.iter().flatten()) + .map(move |(tx, receipt)| (block, tx, receipt)) + }) + // Get all logs + .flat_map(|(block, tx, receipt)| receipt.logs.iter().map(move |log| (block, tx, log))) + // Decode and filter bridge events + .filter_map(|(block, tx, log)| { + L1StandardBridgeEvents::decode_raw_log(log.topics(), &log.data.data, true) + .ok() + .map(|event| (block, tx, log, event)) + }) +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let handle = builder + .node(EthereumNode::default()) + .install_exex("OPDepositCount", |ctx| async move { + let connection = Connection::open("op_deposit_count.db")?; + Ok(OpCount::new(ctx, connection)?.start()) + }) + .launch() + .await?; + + // 1. Setup the DB + let db_path = std::env::var("op_deposit_count.db")?; + let db_path = Path::new(&db_path); + let db = Arc::new(open_db_read_only( + db_path.join("db").as_path(), + DatabaseArguments::new(ClientVersion::default()), + )?); + let spec = Arc::new(ChainSpecBuilder::mainnet().build()); + let factory = ProviderFactory::new(db.clone(), spec.clone(), db_path.join("static_files"))?; + + // 2. Setup the blockchain provider using only the database provider and a noop for the tree to + // satisfy trait bounds. Tree is not used in this example since we are only operating on the + // disk and don't handle new blocks/live sync etc, which is done by the blockchain tree. + let provider = BlockchainProvider::new(factory, Arc::new(NoopBlockchainTree::default()))?; + + let rpc_builder = RpcModuleBuilder::default() + .with_provider(provider.clone()) + // Rest is just noops that do nothing + .with_noop_pool() + .with_noop_network() + .with_executor(TokioTaskExecutor::default()) + .with_evm_config(EthEvmConfig::default()) + .with_events(TestCanonStateSubscriptions::default()); + + // Pick which namespaces to expose. + let config = TransportRpcModuleConfig::default().with_http([RethRpcModule::Eth]); + let mut server = rpc_builder.build(config); + + // Add a custom rpc namespace + let custom_rpc = OpDepositCountExt { provider }; + server.merge_configured(custom_rpc.into_rpc())?; + + // Start the server & keep it alive + let server_args = + RpcServerConfig::http(Default::default()).with_http_address("0.0.0.0:8545".parse()?); + let _handle = server_args.start(server).await?; + futures::future::pending::<()>().await; + + handle.wait_for_node_exit().await +}) +} diff --git a/examples/exex/rollup/src/db.rs b/examples/exex/rollup/src/db.rs index 39c2b418b81e..334531ef2158 100644 --- a/examples/exex/rollup/src/db.rs +++ b/examples/exex/rollup/src/db.rs @@ -457,4 +457,4 @@ impl reth_revm::Database for Database { Err(err) => Err(err.into()), } } -} +} \ No newline at end of file