From 71ed1ed228ce9e37b71df8ac0434c7cf15e22cda Mon Sep 17 00:00:00 2001 From: supernovahs Date: Sun, 5 May 2024 22:14:00 +0530 Subject: [PATCH 1/2] custom rpc wip --- Cargo.lock | 24 + Cargo.toml | 1 + examples/exex/custom-rpc/Cargo.toml | 26 + .../custom-rpc/l1_standard_bridge_abi.json | 664 ++++++++++++++++++ examples/exex/custom-rpc/src/main.rs | 111 +++ 5 files changed, 826 insertions(+) create mode 100644 examples/exex/custom-rpc/Cargo.toml create mode 100644 examples/exex/custom-rpc/l1_standard_bridge_abi.json create mode 100644 examples/exex/custom-rpc/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 1828b2a2faef..3de63a9e5259 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2911,6 +2911,30 @@ 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-exex", + "reth-node-api", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives", + "reth-provider", + "reth-rpc", + "reth-rpc-api", + "reth-tracing", + "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..f28f9415b3c3 --- /dev/null +++ b/examples/exex/custom-rpc/Cargo.toml @@ -0,0 +1,26 @@ +[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 +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 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..f32d5b063894 --- /dev/null +++ b/examples/exex/custom-rpc/src/main.rs @@ -0,0 +1,111 @@ +` + +use clap::{Id, Parser}; +use jsonrpsee::{core::{RpcResult},types::{error::ErrorObject}, proc_macros::rpc}; +use reth::{cli::Cli, rpc::builder::error::RpcError}; +use reth_node_ethereum::EthereumNode; +use alloy_sol_types::{sol, SolEventInterface}; +use reth_provider::DatabaseProviderFactory; +use async_trait::async_trait; +use reth_rpc_api::{EthFilterApiClient, EthFilterApiServer}; +use alloy_rpc_types::{Filter,BlockNumberOrTag}; +sol!(L1StandardBridge, "l1_standard_bridge_abi.json"); +use crate::L1StandardBridge::{ETHBridgeFinalized, ETHBridgeInitiated, L1StandardBridgeEvents}; + +fn main() { + Cli::::parse() + .run(|builder, args| async move { + let handle = builder + .node(EthereumNode::default()) + .extend_rpc_modules(move |ctx| { + if !args.enable_ext { + return Ok(()) + } + + // here we get the configured provider. + let provider = ctx.provider().clone(); + + let ext = OpDepositCountExt::new(provider); + + // now we merge our extension namespace into all configured transports + ctx.modules.merge_configured(ext)?; + + println!("txpool extension enabled"); + + Ok(()) + }) + .launch() + .await?; + + handle.wait_for_node_exit().await + }) + .unwrap(); +} + +/// 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="onDepositCount"]] +pub trait OpDepositCountExtApi { + #[method(name = "opdepositCount")] + fn op_deposit_count(&self) -> RpcResult; +} + +pub struct OpDepositCountExt { + provider: Provider, +} + +impl

OpDepositCountExt

+where + P:EthFilterApiClient + Clone + Send + Sync + 'static +{ + pub fn new(provider : P ) -> OpDepositCountExt

{ + Self{provider} + } +} + +#[async_trait] +impl

OpDepositCountExtApiServer for OpDepositCountExt

+where + P: EthFilterApiClient + Clone + Send + Sync+ 'static, +{ + async fn op_deposit_count(&self) -> RpcResult { + let filter = Filter::new().select(0u64..).event("ETHBridgeFinalized(address,address,uint256)"); + let filter_res = self.provider.new_filter(filter).await; + + match filter_res{ + Ok(id) =>{ + let data = self.provider.filter_logs(id).await; + let mut deposit_count = 0; + match data{ + Ok(logs) =>{ + for log in logs{ + let val = log.log_decode::().ok(); + + if let Some(finalized_data) = val{ + // let's see what to do with this data later. + let da = finalized_data.data(); + deposit_count += 1; + } + } + Ok(deposit_count) + }, + Err(_) =>{ + Ok(0) + } + } + }, + Err(_)=>{ + Ok(0) + } + + } + } +} From ad27291e83c8bb45f84419d14b7f283d864ba00e Mon Sep 17 00:00:00 2001 From: supernovahs Date: Wed, 8 May 2024 22:38:18 +0530 Subject: [PATCH 2/2] pushing for the sake of it, wrong 99% --- Cargo.lock | 3 + examples/exex/custom-rpc/Cargo.toml | 4 + examples/exex/custom-rpc/src/main.rs | 339 +++++++++++++++++++++------ examples/exex/rollup/src/db.rs | 2 +- 4 files changed, 277 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3de63a9e5259..cbedd079e380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2923,6 +2923,7 @@ dependencies = [ "futures", "jsonrpsee", "reth", + "reth-db", "reth-exex", "reth-node-api", "reth-node-core", @@ -2932,6 +2933,8 @@ dependencies = [ "reth-rpc", "reth-rpc-api", "reth-tracing", + "rusqlite", + "serde_json", "tokio", ] diff --git a/examples/exex/custom-rpc/Cargo.toml b/examples/exex/custom-rpc/Cargo.toml index f28f9415b3c3..40e1d6dee067 100644 --- a/examples/exex/custom-rpc/Cargo.toml +++ b/examples/exex/custom-rpc/Cargo.toml @@ -19,8 +19,12 @@ 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/src/main.rs b/examples/exex/custom-rpc/src/main.rs index f32d5b063894..4236afe8029a 100644 --- a/examples/exex/custom-rpc/src/main.rs +++ b/examples/exex/custom-rpc/src/main.rs @@ -1,45 +1,35 @@ -` - +use alloy_rpc_types::{BlockNumberOrTag, Filter}; +use alloy_sol_types::{sol, SolEventInterface}; use clap::{Id, Parser}; -use jsonrpsee::{core::{RpcResult},types::{error::ErrorObject}, proc_macros::rpc}; -use reth::{cli::Cli, rpc::builder::error::RpcError}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::error::ErrorObject}; use reth_node_ethereum::EthereumNode; -use alloy_sol_types::{sol, SolEventInterface}; -use reth_provider::DatabaseProviderFactory; -use async_trait::async_trait; -use reth_rpc_api::{EthFilterApiClient, EthFilterApiServer}; -use alloy_rpc_types::{Filter,BlockNumberOrTag}; +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; -fn main() { - Cli::::parse() - .run(|builder, args| async move { - let handle = builder - .node(EthereumNode::default()) - .extend_rpc_modules(move |ctx| { - if !args.enable_ext { - return Ok(()) - } - - // here we get the configured provider. - let provider = ctx.provider().clone(); - - let ext = OpDepositCountExt::new(provider); - - // now we merge our extension namespace into all configured transports - ctx.modules.merge_configured(ext)?; - - println!("txpool extension enabled"); - - Ok(()) - }) - .launch() - .await?; - - handle.wait_for_node_exit().await - }) - .unwrap(); +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. @@ -52,7 +42,7 @@ struct RethCliOpDepositCountExt { /// trait interface for a custom rpc namespace: `opdepositcount` /// /// This defines an additional namespace where all methods are configured as trait functions. -#[rpc[server, namespace="onDepositCount"]] +#[rpc[server, namespace="onDepositCounts"]] pub trait OpDepositCountExtApi { #[method(name = "opdepositCount")] fn op_deposit_count(&self) -> RpcResult; @@ -62,50 +52,259 @@ pub struct OpDepositCountExt { provider: Provider, } -impl

OpDepositCountExt

+impl

OpDepositCountExt

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

{ - Self{provider} + pub fn new(provider: P) -> OpDepositCountExt

{ + Self { provider } } } -#[async_trait] impl

OpDepositCountExtApiServer for OpDepositCountExt

where - P: EthFilterApiClient + Clone + Send + Sync+ 'static, + P: BlockReaderIdExt + 'static, { - async fn op_deposit_count(&self) -> RpcResult { - let filter = Filter::new().select(0u64..).event("ETHBridgeFinalized(address,address,uint256)"); - let filter_res = self.provider.new_filter(filter).await; - - match filter_res{ - Ok(id) =>{ - let data = self.provider.filter_logs(id).await; - let mut deposit_count = 0; - match data{ - Ok(logs) =>{ - for log in logs{ - let val = log.log_decode::().ok(); - - if let Some(finalized_data) = val{ - // let's see what to do with this data later. - let da = finalized_data.data(); - deposit_count += 1; - } + 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, } - Ok(deposit_count) - }, - Err(_) =>{ - Ok(0) + } + + 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()); } } - }, - Err(_)=>{ - Ok(0) } + 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