From e7f7d23ea1129c75a6e2157a99ddabdb8fe88d38 Mon Sep 17 00:00:00 2001 From: jfldde <168934971+jfldde@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:31:26 +0100 Subject: [PATCH 1/2] Derive rpc(client,server) for EthereumRpc (#1632) --- Cargo.lock | 1 + bin/citrea/src/eth.rs | 13 +- bin/citrea/tests/evm/mod.rs | 6 - bin/citrea/tests/test_client/mod.rs | 7 - crates/ethereum-rpc/Cargo.toml | 5 +- crates/ethereum-rpc/src/ethereum.rs | 9 - crates/ethereum-rpc/src/lib.rs | 1146 +++++++++++---------------- crates/ethereum-rpc/src/trace.rs | 30 +- 8 files changed, 475 insertions(+), 742 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34b4d992f..51ce6996c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2937,6 +2937,7 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-trace", "anyhow", + "async-trait", "borsh", "citrea-evm", "citrea-primitives", diff --git a/bin/citrea/src/eth.rs b/bin/citrea/src/eth.rs index 4ff7c0c8c..2751b32e3 100644 --- a/bin/citrea/src/eth.rs +++ b/bin/citrea/src/eth.rs @@ -1,4 +1,3 @@ -use std::str::FromStr; use std::sync::Arc; use anyhow::Context as _; @@ -20,15 +19,13 @@ pub(crate) fn register_ethereum( soft_confirmation_rx: Option>, ) -> Result<(), anyhow::Error> { let eth_rpc_config = { - let eth_signer = eth_dev_signer(); EthRpcConfig { - eth_signer, gas_price_oracle_config: GasPriceOracleConfig::default(), fee_history_cache_config: FeeHistoryCacheConfig::default(), } }; - let ethereum_rpc = ethereum_rpc::get_ethereum_rpc::( + let ethereum_rpc = ethereum_rpc::create_rpc_module::( da_service, eth_rpc_config, storage, @@ -40,11 +37,3 @@ pub(crate) fn register_ethereum( .merge(ethereum_rpc) .context("Failed to merge Ethereum RPC modules") } - -// TODO: #840 -fn eth_dev_signer() -> ethereum_rpc::DevSigner { - ethereum_rpc::DevSigner::new(vec![secp256k1::SecretKey::from_str( - "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", - ) - .unwrap()]) -} diff --git a/bin/citrea/tests/evm/mod.rs b/bin/citrea/tests/evm/mod.rs index 9688f2c86..747806ebc 100644 --- a/bin/citrea/tests/evm/mod.rs +++ b/bin/citrea/tests/evm/mod.rs @@ -531,12 +531,6 @@ async fn execute(client: &Box) -> Result<(), Box Box { let test_client = make_test_client(rpc_address).await.unwrap(); - let etc_accounts = test_client.eth_accounts().await; - assert_eq!( - vec![Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap()], - etc_accounts - ); - let eth_chain_id = test_client.eth_chain_id().await; assert_eq!(5655, eth_chain_id); diff --git a/bin/citrea/tests/test_client/mod.rs b/bin/citrea/tests/test_client/mod.rs index 3d83c5389..db854b927 100644 --- a/bin/citrea/tests/test_client/mod.rs +++ b/bin/citrea/tests/test_client/mod.rs @@ -307,13 +307,6 @@ impl TestClient { .unwrap() } - pub(crate) async fn eth_accounts(&self) -> Vec
{ - self.http_client - .request("eth_accounts", rpc_params![]) - .await - .unwrap() - } - pub(crate) async fn eth_chain_id(&self) -> u64 { self.client.get_chain_id().await.unwrap() } diff --git a/crates/ethereum-rpc/Cargo.toml b/crates/ethereum-rpc/Cargo.toml index 2c533e25a..a2084bd6b 100644 --- a/crates/ethereum-rpc/Cargo.toml +++ b/crates/ethereum-rpc/Cargo.toml @@ -14,6 +14,7 @@ resolver = "2" [dependencies] # 3rd-party dependencies anyhow = { workspace = true } +async-trait = { workspace = true } borsh = { workspace = true } citrea-evm = { path = "../evm", features = ["native"] } citrea-primitives = { path = "../primitives" } @@ -46,7 +47,3 @@ sov-rollup-interface = { path = "../sovereign-sdk/rollup-interface", features = [dev-dependencies] tokio = { workspace = true } - -[features] -default = ["local"] -local = [] diff --git a/crates/ethereum-rpc/src/ethereum.rs b/crates/ethereum-rpc/src/ethereum.rs index 207df4a9a..60d52cc92 100644 --- a/crates/ethereum-rpc/src/ethereum.rs +++ b/crates/ethereum-rpc/src/ethereum.rs @@ -2,8 +2,6 @@ use std::sync::{Arc, Mutex}; use alloy_primitives::U256; use alloy_rpc_types_trace::geth::GethTrace; -#[cfg(feature = "local")] -use citrea_evm::DevSigner; use citrea_evm::Evm; use jsonrpsee::http_client::HttpClient; use rustc_version_runtime::version; @@ -26,16 +24,12 @@ const DEFAULT_PRIORITY_FEE: U256 = U256::from_limbs([100, 0, 0, 0]); pub struct EthRpcConfig { pub gas_price_oracle_config: GasPriceOracleConfig, pub fee_history_cache_config: FeeHistoryCacheConfig, - #[cfg(feature = "local")] - pub eth_signer: DevSigner, } pub struct Ethereum { #[allow(dead_code)] pub(crate) da_service: Arc, pub(crate) gas_price_oracle: GasPriceOracle, - #[cfg(feature = "local")] - pub(crate) eth_signer: DevSigner, pub(crate) storage: C::Storage, pub(crate) ledger_db: LedgerDB, pub(crate) sequencer_client: Option, @@ -50,7 +44,6 @@ impl Ethereum { da_service: Arc, gas_price_oracle_config: GasPriceOracleConfig, fee_history_cache_config: FeeHistoryCacheConfig, - #[cfg(feature = "local")] eth_signer: DevSigner, storage: C::Storage, ledger_db: LedgerDB, sequencer_client: Option, @@ -74,8 +67,6 @@ impl Ethereum { Self { da_service, gas_price_oracle, - #[cfg(feature = "local")] - eth_signer, storage, ledger_db, sequencer_client, diff --git a/crates/ethereum-rpc/src/lib.rs b/crates/ethereum-rpc/src/lib.rs index 51785339f..e2441b261 100644 --- a/crates/ethereum-rpc/src/lib.rs +++ b/crates/ethereum-rpc/src/lib.rs @@ -9,20 +9,20 @@ use alloy_network::AnyNetwork; use alloy_primitives::{keccak256, Bytes, B256, U256}; use alloy_rpc_types::{FeeHistory, Index}; use alloy_rpc_types_trace::geth::{GethDebugTracingOptions, GethTrace}; -#[cfg(feature = "local")] -pub use citrea_evm::DevSigner; use citrea_evm::{Evm, Filter}; use citrea_sequencer::SequencerRpcClient; pub use ethereum::{EthRpcConfig, Ethereum}; pub use gas_price::fee_history::FeeHistoryCacheConfig; pub use gas_price::gas_oracle::GasPriceOracleConfig; +use jsonrpsee::core::{RpcResult, SubscriptionResult}; use jsonrpsee::http_client::HttpClientBuilder; +use jsonrpsee::proc_macros::rpc; use jsonrpsee::types::ErrorObjectOwned; -use jsonrpsee::RpcModule; +use jsonrpsee::{PendingSubscriptionSink, RpcModule}; use reth_primitives::BlockNumberOrTag; use reth_rpc_eth_api::RpcTransaction; use reth_rpc_eth_types::EthApiError; -use serde_json::json; +use serde_json::{json, Value}; use sov_db::ledger_db::{LedgerDB, SharedLedgerOps}; use sov_ledger_rpc::LedgerRpcClient; use sov_modules_api::da::BlockHeaderTrait; @@ -51,498 +51,341 @@ pub struct SyncStatus { pub l2_status: LayerStatus, } -pub fn get_ethereum_rpc( - da_service: Arc, - eth_rpc_config: EthRpcConfig, - storage: C::Storage, - ledger_db: LedgerDB, - sequencer_client_url: Option, - soft_confirmation_rx: Option>, -) -> RpcModule> { - // Unpack config - let EthRpcConfig { - #[cfg(feature = "local")] - eth_signer, - gas_price_oracle_config, - fee_history_cache_config, - } = eth_rpc_config; - - // If the node does not have a sequencer client, then it is the sequencer. - let is_sequencer = sequencer_client_url.is_none(); - let enable_subscriptions = soft_confirmation_rx.is_some(); - - // If the running node is a full node rpc context should also have sequencer client so that it can send txs to sequencer - let mut rpc = RpcModule::new(Ethereum::new( - da_service, - gas_price_oracle_config, - fee_history_cache_config, - #[cfg(feature = "local")] - eth_signer, - storage, - ledger_db, - sequencer_client_url.map(|url| HttpClientBuilder::default().build(url).unwrap()), - soft_confirmation_rx, - )); - - register_rpc_methods(&mut rpc, is_sequencer, enable_subscriptions) - .expect("Failed to register ethereum RPC methods"); - rpc +#[rpc(server)] +pub trait EthereumRpc { + /// Returns the client version. + #[method(name = "web3_clientVersion")] + fn web3_client_version(&self) -> RpcResult; + + /// Returns Keccak-256 hash of the given data. + #[method(name = "web3_sha3")] + #[blocking] + fn web3_sha3(&self, data: Bytes) -> RpcResult; + + /// Returns the current gas price. + #[method(name = "eth_gasPrice")] + #[blocking] + fn eth_gas_price(&self) -> RpcResult; + + /// Returns the maximum fee per gas. + #[method(name = "eth_maxFeePerGas")] + #[blocking] + fn eth_max_fee_per_gas(&self) -> RpcResult; + + /// Returns the maximum priority fee per gas. + #[method(name = "eth_maxPriorityFeePerGas")] + #[blocking] + fn eth_max_priority_fee_per_gas(&self) -> RpcResult; + + /// Returns fee history. + #[method(name = "eth_feeHistory")] + #[blocking] + fn eth_fee_history( + &self, + block_count: Index, + newest_block: BlockNumberOrTag, + reward_percentiles: Option>, + ) -> RpcResult; + + /// Returns traces for a block by hash. + #[method(name = "debug_traceBlockByHash")] + #[blocking] + fn debug_trace_block_by_hash( + &self, + block_hash: B256, + opts: Option, + ) -> RpcResult>; + + /// Returns traces for a block by number. + #[method(name = "debug_traceBlockByNumber")] + #[blocking] + fn debug_trace_block_by_number( + &self, + block_number: BlockNumberOrTag, + opts: Option, + ) -> RpcResult>; + + /// Returns trace for a transaction. + #[method(name = "debug_traceTransaction")] + #[blocking] + fn debug_trace_transaction( + &self, + tx_hash: B256, + opts: Option, + ) -> RpcResult; + + /// Returns the transaction pool content. + #[method(name = "txpool_content")] + fn txpool_content(&self) -> RpcResult; + + /// Gets uncle by block hash and index. + #[method(name = "eth_getUncleByBlockHashAndIndex")] + fn get_uncle_by_block_hash_and_index( + &self, + block_hash: String, + uncle_index: String, + ) -> RpcResult; + + /// Sends raw transaction (full node only). + #[method(name = "eth_sendRawTransaction")] + async fn eth_send_raw_transaction(&self, data: Bytes) -> RpcResult; + + /// Gets transaction by hash (full node only). + #[method(name = "eth_getTransactionByHash")] + async fn eth_get_transaction_by_hash( + &self, + hash: B256, + mempool_only: Option, + ) -> RpcResult>>; + + /// Gets sync status (full node only). + #[method(name = "citrea_syncStatus")] + async fn citrea_sync_status(&self) -> RpcResult; + + /// Subscribe to debug events. + #[subscription(name = "debug_subscribe" => "debug_subscription", unsubscribe = "debug_unsubscribe", item = GethTrace)] + async fn subscribe_debug( + &self, + topic: String, + start_block: BlockNumberOrTag, + end_block: BlockNumberOrTag, + opts: Option, + ) -> SubscriptionResult; + + /// Subscribe to Ethereum events. + #[subscription(name = "eth_subscribe" => "eth_subscription", unsubscribe = "eth_unsubscribe", item = Value)] + async fn subscribe_eth(&self, topic: String, filter: Option) -> SubscriptionResult; } -fn register_rpc_methods( - rpc: &mut RpcModule>, - // Checks wether the running node is a sequencer or not, if it is not a sequencer it should also have methods like eth_sendRawTransaction here. - is_sequencer: bool, - enable_subscriptions: bool, -) -> Result<(), jsonrpsee::core::RegisterMethodError> { - rpc.register_async_method("web3_clientVersion", |_, ethereum, _| async move { - Ok::<_, ErrorObjectOwned>(ethereum.web3_client_version.clone()) - })?; +const ETH_RPC_ERROR: &str = "ETH_RPC_ERROR"; - rpc.register_blocking_method("web3_sha3", move |params, _, _| { - let data: Bytes = params.one()?; +fn to_eth_rpc_error(err: impl ToString) -> ErrorObjectOwned { + to_jsonrpsee_error_object(ETH_RPC_ERROR, err) +} - let hash = B256::from_slice(keccak256(&data).as_slice()); +pub struct EthereumRpcServerImpl +where + C: sov_modules_api::Context, + Da: DaService, +{ + ethereum: Arc>, +} - Ok::<_, ErrorObjectOwned>(hash) - })?; +impl EthereumRpcServerImpl +where + C: sov_modules_api::Context, + Da: DaService, +{ + pub fn new(ethereum: Arc>) -> Self { + Self { ethereum } + } +} - rpc.register_blocking_method("eth_gasPrice", move |_, ethereum, _| { - let price = { - let mut working_set = WorkingSet::new(ethereum.storage.clone()); +#[async_trait::async_trait] +impl EthereumRpcServer for EthereumRpcServerImpl +where + C: sov_modules_api::Context, + Da: DaService, +{ + fn web3_client_version(&self) -> RpcResult { + Ok(self.ethereum.web3_client_version.clone()) + } - let (base_fee, suggested_tip) = ethereum.max_fee_per_gas(&mut working_set); + fn web3_sha3(&self, data: Bytes) -> RpcResult { + Ok(B256::from_slice(keccak256(&data).as_slice())) + } - suggested_tip + base_fee - }; + fn eth_gas_price(&self) -> RpcResult { + let mut working_set = WorkingSet::new(self.ethereum.storage.clone()); + let (base_fee, suggested_tip) = self.ethereum.max_fee_per_gas(&mut working_set); + Ok(suggested_tip + base_fee) + } - Ok::(price) - })?; + fn eth_max_fee_per_gas(&self) -> RpcResult { + let mut working_set = WorkingSet::new(self.ethereum.storage.clone()); + let (base_fee, suggested_tip) = self.ethereum.max_fee_per_gas(&mut working_set); + Ok(suggested_tip + base_fee) + } - rpc.register_blocking_method("eth_maxFeePerGas", move |_, ethereum, _| { - let max_fee_per_gas = { - let mut working_set = WorkingSet::new(ethereum.storage.clone()); + fn eth_max_priority_fee_per_gas(&self) -> RpcResult { + let mut working_set = WorkingSet::new(self.ethereum.storage.clone()); + let (_base_fee, suggested_tip) = self.ethereum.max_fee_per_gas(&mut working_set); + Ok(suggested_tip) + } - let (base_fee, suggested_tip) = ethereum.max_fee_per_gas(&mut working_set); + fn eth_fee_history( + &self, + block_count: Index, + newest_block: BlockNumberOrTag, + reward_percentiles: Option>, + ) -> RpcResult { + let block_count = block_count.0 as u64; + let mut working_set = WorkingSet::new(self.ethereum.storage.clone()); + + self.ethereum + .gas_price_oracle + .fee_history( + block_count, + newest_block, + reward_percentiles, + &mut working_set, + ) + .map_err(to_eth_rpc_error) + } - suggested_tip + base_fee + fn debug_trace_block_by_hash( + &self, + block_hash: B256, + opts: Option, + ) -> RpcResult> { + let evm = Evm::::default(); + let mut working_set = WorkingSet::new(self.ethereum.storage.clone()); + + let block_number = match evm.get_block_number_by_block_hash(block_hash, &mut working_set) { + Some(block_number) => block_number, + None => { + return Err(EthApiError::HeaderNotFound(block_hash.into()).into()); + } }; - Ok::(max_fee_per_gas) - })?; - - rpc.register_blocking_method("eth_maxPriorityFeePerGas", move |_, ethereum, _| { - let max_priority_fee = { - let mut working_set = WorkingSet::new(ethereum.storage.clone()); - - let (_base_fee, suggested_tip) = ethereum.max_fee_per_gas(&mut working_set); + debug_trace_by_block_number( + block_number, + None, + &self.ethereum, + &evm, + &mut working_set, + opts, + ) + .map_err(to_eth_rpc_error) + } - suggested_tip + fn debug_trace_block_by_number( + &self, + block_number: BlockNumberOrTag, + opts: Option, + ) -> RpcResult> { + let mut working_set = WorkingSet::new(self.ethereum.storage.clone()); + let evm = Evm::::default(); + let latest_block_number: u64 = evm.block_number(&mut working_set)?.saturating_to(); + + let block_number = match block_number { + BlockNumberOrTag::Number(block_number) => block_number, + BlockNumberOrTag::Latest => latest_block_number, + _ => return Err(EthApiError::Unsupported( + "Earliest, pending, safe and finalized are not supported for debug_traceBlockByNumber", + ).into()), }; - Ok::(max_priority_fee) - })?; - - rpc.register_blocking_method("eth_feeHistory", move |params, ethereum, _| { - let mut params = params.sequence(); - - let block_count: Index = params.next()?; - let newest_block: BlockNumberOrTag = params.next()?; - let reward_percentiles: Option> = params.optional_next()?; - - // convert block count to u64 from hex - let block_count = usize::from(block_count) as u64; - - let fee_history = { - let mut working_set = WorkingSet::new(ethereum.storage.clone()); - - ethereum.gas_price_oracle.fee_history( - block_count, - newest_block, - reward_percentiles, - &mut working_set, - )? - }; + debug_trace_by_block_number( + block_number, + None, + &self.ethereum, + &evm, + &mut working_set, + opts, + ) + .map_err(to_eth_rpc_error) + } - Ok::(fee_history) - })?; - - #[cfg(feature = "local")] - rpc.register_async_method("eth_accounts", |_, ethereum, _| async move { - Ok::<_, ErrorObjectOwned>(ethereum.eth_signer.signers()) - })?; - - // #[cfg(feature = "local")] - // rpc.register_async_method("eth_sendTransaction", |parameters, ethereum| async move { - // let mut transaction_request: TransactionRequest = parameters.one().unwrap(); - - // let evm = Evm::::default(); - - // // get from, return error if none - // let from = transaction_request - // .from - // .ok_or(to_jsonrpsee_error_object("No from address", ETH_RPC_ERROR))?; - - // // return error if not in signers - // if !ethereum.eth_signer.signers().contains(&from) { - // return Err(to_jsonrpsee_error_object( - // "From address not in signers", - // ETH_RPC_ERROR, - // )); - // } - - // let raw_evm_tx = { - // let mut working_set = WorkingSet::new(ethereum.storage.clone()); - - // // set nonce if none - // if transaction_request.nonce.is_none() { - // let nonce = evm - // .get_transaction_count(from, None, &mut working_set) - // .unwrap_or_default(); - - // transaction_request.nonce = Some(nonce); - // } - - // // get current chain id - // let chain_id = evm - // .chain_id(&mut working_set) - // .expect("Failed to get chain id") - // .map(|id| id.to::()) - // .unwrap_or(1); - - // // get call request to estimate gas and gas prices - // let (call_request, _gas_price, _max_fee_per_gas) = - // get_call_request_and_params(from, chain_id, &transaction_request); - - // // estimate gas limit - // let gas_limit = U256::from( - // evm.eth_estimate_gas(call_request, None, &mut working_set)? - // .to::(), - // ); - - // let TransactionRequest { - // to, - // gas_price, - // max_fee_per_gas, - // max_priority_fee_per_gas, - // gas, - // value, - // input: data, - // nonce, - // mut access_list, - // max_fee_per_blob_gas, - // blob_versioned_hashes, - // sidecar, - // .. - // } = transaction_request; - - // // todo: remove this inlining after https://github.com/alloy-rs/alloy/pull/183#issuecomment-1928161285 - // let transaction = match ( - // gas_price, - // max_fee_per_gas, - // access_list.take(), - // max_fee_per_blob_gas, - // blob_versioned_hashes, - // sidecar, - // ) { - // // legacy transaction - // // gas price required - // (Some(_), None, None, None, None, None) => { - // Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest { - // nonce: nonce.unwrap_or_default(), - // gas_price: gas_price.unwrap_or_default(), - // gas_limit: gas.unwrap_or_default(), - // value: value.unwrap_or_default(), - // input: data.into_input().unwrap_or_default(), - // kind: match to { - // Some(to) => RpcTransactionKind::Call(to), - // None => RpcTransactionKind::Create, - // }, - // chain_id: None, - // })) - // } - // // EIP2930 - // // if only accesslist is set, and no eip1599 fees - // (_, None, Some(access_list), None, None, None) => Some( - // TypedTransactionRequest::EIP2930(EIP2930TransactionRequest { - // nonce: nonce.unwrap_or_default(), - // gas_price: gas_price.unwrap_or_default(), - // gas_limit: gas.unwrap_or_default(), - // value: value.unwrap_or_default(), - // input: data.into_input().unwrap_or_default(), - // kind: match to { - // Some(to) => RpcTransactionKind::Call(to), - // None => RpcTransactionKind::Create, - // }, - // chain_id: 0, - // access_list, - // }), - // ), - // // EIP1559 - // // if 4844 fields missing - // // gas_price, max_fee_per_gas, access_list, max_fee_per_blob_gas, blob_versioned_hashes, - // // sidecar, - // (None, _, _, None, None, None) => { - // // Empty fields fall back to the canonical transaction schema. - // Some(TypedTransactionRequest::EIP1559( - // EIP1559TransactionRequest { - // nonce: nonce.unwrap_or_default(), - // max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - // max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), - // gas_limit: gas.unwrap_or_default(), - // value: value.unwrap_or_default(), - // input: data.into_input().unwrap_or_default(), - // kind: match to { - // Some(to) => RpcTransactionKind::Call(to), - // None => RpcTransactionKind::Create, - // }, - // chain_id: 0, - // access_list: access_list.unwrap_or_default(), - // }, - // )) - // } - // // EIP4884 - // // all blob fields required - // ( - // None, - // _, - // _, - // Some(max_fee_per_blob_gas), - // Some(blob_versioned_hashes), - // Some(sidecar), - // ) => { - // // As per the EIP, we follow the same semantics as EIP-1559. - // Some(TypedTransactionRequest::EIP4844( - // EIP4844TransactionRequest { - // chain_id: 0, - // nonce: nonce.unwrap_or_default(), - // max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), - // max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - // gas_limit: gas.unwrap_or_default(), - // value: value.unwrap_or_default(), - // input: data.into_input().unwrap_or_default(), - // kind: match to { - // Some(to) => RpcTransactionKind::Call(to), - // None => RpcTransactionKind::Create, - // }, - // access_list: access_list.unwrap_or_default(), - - // // eip-4844 specific. - // max_fee_per_blob_gas, - // blob_versioned_hashes, - // sidecar, - // }, - // )) - // } - - // _ => None, - // }; - - // // get typed transaction request - // let transaction_request = match transaction { - // Some(TypedTransactionRequest::Legacy(mut m)) => { - // m.chain_id = Some(chain_id); - // m.gas_limit = gas_limit; - // m.gas_price = gas_price.unwrap(); - - // TypedTransactionRequest::Legacy(m) - // } - // Some(TypedTransactionRequest::EIP2930(mut m)) => { - // m.chain_id = chain_id; - // m.gas_limit = gas_limit; - // m.gas_price = gas_price.unwrap(); - - // TypedTransactionRequest::EIP2930(m) - // } - // Some(TypedTransactionRequest::EIP1559(mut m)) => { - // m.chain_id = chain_id; - // m.gas_limit = gas_limit; - // m.max_fee_per_gas = max_fee_per_gas.unwrap(); - - // TypedTransactionRequest::EIP1559(m) - // } - // Some(TypedTransactionRequest::EIP4844(mut m)) => { - // m.chain_id = chain_id; - // m.gas_limit = gas_limit; - // m.max_fee_per_gas = max_fee_per_gas.unwrap(); - - // TypedTransactionRequest::EIP4844(m) - // } - // None => return Err(EthApiError::ConflictingFeeFieldsInRequest.into()), - // }; - - // // get raw transaction - // let transaction = to_primitive_transaction(transaction_request) - // .ok_or(SignError::InvalidTransactionRequest)?; - - // // sign transaction - // let signed_tx = ethereum - // .eth_signer - // .sign_transaction(transaction, from) - // .map_err(|e| to_jsonrpsee_error_object(e, ETH_RPC_ERROR))?; - - // RlpEvmTransaction { - // rlp: signed_tx.envelope_encoded().to_vec(), - // } - // }; - // let (tx_hash, raw_message) = ethereum - // .make_raw_tx(raw_evm_tx) - // .map_err(|e| to_jsonrpsee_error_object(e, ETH_RPC_ERROR))?; - - // ethereum.add_messages(vec![raw_message]); - - // Ok::<_, ErrorObjectOwned>(tx_hash) - // })?; - - rpc.register_blocking_method::, ErrorObjectOwned>, _>( - "debug_traceBlockByHash", - move |parameters, ethereum, _| { - let mut params = parameters.sequence(); - - let block_hash: B256 = params.next()?; - let evm = Evm::::default(); - let mut working_set = WorkingSet::new(ethereum.storage.clone()); - let opts: Option = params.optional_next()?; - - let block_number = - match evm.get_block_number_by_block_hash(block_hash, &mut working_set) { - Some(block_number) => block_number, - None => { - return Err(EthApiError::HeaderNotFound(block_hash.into()).into()); - } - }; - - debug_trace_by_block_number(block_number, None, ðereum, &evm, &mut working_set, opts) - }, - )?; - - rpc.register_blocking_method::, ErrorObjectOwned>, _>( - "debug_traceBlockByNumber", - move |parameters, ethereum, _| { - let mut params = parameters.sequence(); - - let block_number: BlockNumberOrTag = params.next()?; - let opts: Option = params.optional_next()?; - - let mut working_set = WorkingSet::new(ethereum.storage.clone()); - let evm = Evm::::default(); - let latest_block_number: u64 = evm.block_number(&mut working_set)?.saturating_to(); - - let block_number = match block_number { - BlockNumberOrTag::Number(block_number) => block_number, - BlockNumberOrTag::Latest => latest_block_number, - _ => return Err(EthApiError::Unsupported( - "Earliest, pending, safe and finalized are not supported for debug_traceBlockByNumber", - ) - .into()), - }; - - debug_trace_by_block_number(block_number, None, ðereum, &evm, &mut working_set, opts) - }, - )?; - - rpc.register_blocking_method::, _>( - "debug_traceTransaction", - move |parameters, ethereum, _| { - // the main rpc handler for debug_traceTransaction - // Checks the cache in ethereum struct if the trace exists - // if found; returns the trace - // else; calls the debug_trace_transaction_block function in evm - // that function traces the entire block, returns all the traces to here - // then we put them into cache and return the trace of the requested transaction - let mut params = parameters.sequence(); - - let tx_hash: B256 = params.next()?; - - let evm = Evm::::default(); - let mut working_set = WorkingSet::new(ethereum.storage.clone()); - - let tx = evm - .get_transaction_by_hash(tx_hash, &mut working_set) - .unwrap() - .ok_or_else(|| EthApiError::UnknownBlockOrTxIndex)?; - let trace_idx: u64 = tx - .transaction_index - .expect("Tx index must be set for tx inside block"); - - let block_number: u64 = tx - .block_number - .expect("Block number must be set for tx inside block"); - - let opts: Option = params.optional_next()?; - - let traces = debug_trace_by_block_number( - block_number, - Some(trace_idx as usize), - ðereum, - &evm, - &mut working_set, - opts, - )?; - Ok(traces[0].clone()) - }, - )?; + // the main rpc handler for debug_traceTransaction + // Checks the cache in ethereum struct if the trace exists + // if found; returns the trace + // else; calls the debug_trace_transaction_block function in evm + // that function traces the entire block, returns all the traces to here + // then we put them into cache and return the trace of the requested transaction + fn debug_trace_transaction( + &self, + tx_hash: B256, + opts: Option, + ) -> RpcResult { + let evm = Evm::::default(); + let mut working_set = WorkingSet::new(self.ethereum.storage.clone()); + + let tx = evm + .get_transaction_by_hash(tx_hash, &mut working_set) + .unwrap() + .ok_or_else(|| EthApiError::UnknownBlockOrTxIndex)?; + + let trace_idx: u64 = tx + .transaction_index + .expect("Tx index must be set for tx inside block"); + + let block_number: u64 = tx + .block_number + .expect("Block number must be set for tx inside block"); + + let traces = debug_trace_by_block_number( + block_number, + Some(trace_idx as usize), + &self.ethereum, + &evm, + &mut working_set, + opts, + ) + .map_err(to_eth_rpc_error)?; + + Ok(traces[0].clone()) + } - rpc.register_async_method("txpool_content", |_, _, _| async move { + fn txpool_content(&self) -> RpcResult { // This is a simple mock for serde. - let json = json!({ + Ok(json!({ "pending": {}, "queued": {} - }); - - Ok::<_, ErrorObjectOwned>(json) - })?; - - rpc.register_async_method( - "eth_getUncleByBlockHashAndIndex", - |parameters, _, _| async move { - let mut params = parameters.sequence(); - - let _block_hash: String = params.next()?; - let _uncle_index_position: String = params.next()?; + })) + } - let res = json!(null); + fn get_uncle_by_block_hash_and_index( + &self, + _block_hash: String, + _uncle_index: String, + ) -> RpcResult { + Ok(json!(null)) + } - Ok::<_, ErrorObjectOwned>(res) - }, - )?; + async fn eth_send_raw_transaction(&self, data: Bytes) -> RpcResult { + self.ethereum + .sequencer_client + .as_ref() + .unwrap() + .eth_send_raw_transaction(data) + .await + .map_err(|e| match e { + jsonrpsee::core::client::Error::Call(e_owned) => e_owned, + _ => to_jsonrpsee_error_object("SEQUENCER_CLIENT_ERROR", e), + }) + } - if !is_sequencer { - rpc.register_async_method::, _, _>( - "eth_sendRawTransaction", - |parameters, ethereum, _| async move { - // send this directly to the sequencer - let data: Bytes = parameters.one()?; - // sequencer client should send it - let tx_hash = ethereum + async fn eth_get_transaction_by_hash( + &self, + hash: B256, + mempool_only: Option, + ) -> RpcResult>> { + match mempool_only { + Some(true) => { + match self + .ethereum .sequencer_client .as_ref() .unwrap() - .eth_send_raw_transaction(data) - .await; - - match tx_hash { - Ok(tx_hash) => Ok(tx_hash), + .eth_get_transaction_by_hash(hash, Some(true)) + .await + { + Ok(tx) => Ok(tx), Err(e) => match e { jsonrpsee::core::client::Error::Call(e_owned) => Err(e_owned), _ => Err(to_jsonrpsee_error_object("SEQUENCER_CLIENT_ERROR", e)), }, } - }, - )?; - - rpc.register_async_method::>, ErrorObjectOwned>, _, _>( - "eth_getTransactionByHash", - |parameters, ethereum, _| async move { - let mut params = parameters.sequence(); - let hash: B256 = params.next()?; - let mempool_only: Result, ErrorObjectOwned> = params.optional_next(); - - // check if mempool_only parameter was given what was its value - match mempool_only { - // only ask sequencer - Ok(Some(true)) => { - match ethereum + } + _ => { + let evm = Evm::::default(); + let mut working_set = WorkingSet::new(self.ethereum.storage.clone()); + match evm.get_transaction_by_hash(hash, &mut working_set) { + Ok(Some(tx)) => Ok(Some(tx)), + Ok(None) => { + match self + .ethereum .sequencer_client .as_ref() .unwrap() @@ -556,227 +399,174 @@ fn register_rpc_methods( }, } } - _ => { - // if mempool_only is not true ask evm first then sequencer - let evm = Evm::::default(); - let mut working_set = WorkingSet::new(ethereum.storage.clone()); - match evm.get_transaction_by_hash(hash, &mut working_set) { - Ok(Some(tx)) => Ok(Some(tx)), - Ok(None) => { - // if not found in evm then ask to sequencer mempool - match ethereum - .sequencer_client - .as_ref() - .unwrap() - .eth_get_transaction_by_hash(hash, Some(true)) - .await - { - Ok(tx) => Ok(tx), - Err(e) => match e { - jsonrpsee::core::client::Error::Call(e_owned) => { - Err(e_owned) - } - _ => Err(to_jsonrpsee_error_object( - "SEQUENCER_CLIENT_ERROR", - e, - )), - }, - } - } - Err(e) => { - // return error - Err(e) - } - } - } + Err(e) => Err(e), } - }, - )?; - - rpc.register_async_method::, _, _>( - "citrea_syncStatus", - |_, ethereum, _| async move { - // sequencer client should send latest l2 height - // da service should send latest finalized l1 block header - let (sequencer_response, da_response) = join!( - ethereum - .sequencer_client - .as_ref() - .unwrap() - .get_head_soft_confirmation_height(), - ethereum.da_service.get_last_finalized_block_header() - ); - // handle sequencer response - let l2_head_block_number = match sequencer_response { - Ok(block_number) => block_number, - Err(e) => match e { - jsonrpsee::core::client::Error::Call(e_owned) => return Err(e_owned), - _ => return Err(to_jsonrpsee_error_object("SEQUENCER_CLIENT_ERROR", e)), - }, - }; - - // get l2 synced block number - - let head_soft_confirmation = ethereum.ledger_db.get_head_soft_confirmation(); - - let l2_synced_block_number = match head_soft_confirmation { - Ok(Some((height, _))) => height.0, - Ok(None) => 0u64, - Err(e) => return Err(to_jsonrpsee_error_object("LEDGER_DB_ERROR", e)), - }; - - // handle da service response - let l1_head_block_number = match da_response { - Ok(header) => header.height(), - Err(e) => return Err(to_jsonrpsee_error_object("DA_SERVICE_ERROR", e)), - }; - - // get l1 synced block number - let l1_synced_block_number = match ethereum.ledger_db.get_last_scanned_l1_height() { - Ok(Some(slot_number)) => slot_number.0, - Ok(None) => 0u64, - Err(e) => return Err(to_jsonrpsee_error_object("LEDGER_DB_ERROR", e)), - }; - - let l1_status = if l1_synced_block_number < l1_head_block_number { - LayerStatus::Syncing(SyncValues { - synced_block_number: l1_synced_block_number, - head_block_number: l1_head_block_number, - }) - } else { - LayerStatus::Synced(l1_head_block_number) - }; - let l2_status = if l2_synced_block_number < l2_head_block_number { - LayerStatus::Syncing(SyncValues { - synced_block_number: l2_synced_block_number, - head_block_number: l2_head_block_number, - }) - } else { - LayerStatus::Synced(l2_head_block_number) - }; - Ok::(SyncStatus { - l1_status, - l2_status, - }) - }, - )?; + } + } } - if enable_subscriptions { - rpc.register_subscription( - "debug_subscribe", - "debug_subscription", - "debug_unsubscribe", - |parameters, pending, ethereum, _| async move { - let mut params = parameters.sequence(); - - let topic: String = match params.next() { - Ok(v) => v, - Err(err) => { - pending.reject(err).await; - return Ok(()); - } - }; - match topic.as_str() { - "traceChain" => handle_debug_trace_chain(params, pending, ethereum).await, - _ => { - pending - .reject(EthApiError::Unsupported("Unsupported subscription topic")) - .await; - return Ok(()); - } - }; - - Ok(()) + async fn citrea_sync_status(&self) -> RpcResult { + let (sequencer_response, da_response) = join!( + self.ethereum + .sequencer_client + .as_ref() + .unwrap() + .get_head_soft_confirmation_height(), + self.ethereum.da_service.get_last_finalized_block_header() + ); + + let l2_head_block_number = match sequencer_response { + Ok(block_number) => block_number, + Err(e) => match e { + jsonrpsee::core::client::Error::Call(e_owned) => return Err(e_owned), + _ => return Err(to_jsonrpsee_error_object("SEQUENCER_CLIENT_ERROR", e)), }, - )?; - - rpc.register_subscription( - "eth_subscribe", - "eth_subscription", - "eth_unsubscribe", - |parameters, pending, ethereum, _| async move { - let mut params = parameters.sequence(); - - let topic: String = match params.next() { - Ok(v) => v, - Err(err) => { - pending.reject(err).await; - return Ok(()); - } - }; - match topic.as_str() { - "newHeads" => { - let subscription = pending.accept().await.unwrap(); - ethereum - .subscription_manager - .as_ref() - .unwrap() - .register_new_heads_subscription(subscription) - .await; - } - "logs" => { - let filter: Filter = match params.next() { - Ok(v) => v, - Err(err) => { - pending.reject(err).await; - return Ok(()); - } - }; - let subscription = pending.accept().await.unwrap(); - ethereum - .subscription_manager - .as_ref() - .unwrap() - .register_new_logs_subscription(filter, subscription) - .await; - } - _ => { - pending - .reject(EthApiError::Unsupported("Unsupported subscription topic")) - .await; - return Ok(()); - } - }; + }; - Ok(()) - }, - )?; + let head_soft_confirmation = self.ethereum.ledger_db.get_head_soft_confirmation(); + let l2_synced_block_number = match head_soft_confirmation { + Ok(Some((height, _))) => height.0, + Ok(None) => 0u64, + Err(e) => return Err(to_jsonrpsee_error_object("LEDGER_DB_ERROR", e)), + }; + + let l1_head_block_number = match da_response { + Ok(header) => header.height(), + Err(e) => return Err(to_jsonrpsee_error_object("DA_SERVICE_ERROR", e)), + }; + + let l1_synced_block_number = match self.ethereum.ledger_db.get_last_scanned_l1_height() { + Ok(Some(slot_number)) => slot_number.0, + Ok(None) => 0u64, + Err(e) => return Err(to_jsonrpsee_error_object("LEDGER_DB_ERROR", e)), + }; + + let l1_status = if l1_synced_block_number < l1_head_block_number { + LayerStatus::Syncing(SyncValues { + synced_block_number: l1_synced_block_number, + head_block_number: l1_head_block_number, + }) + } else { + LayerStatus::Synced(l1_head_block_number) + }; + + let l2_status = if l2_synced_block_number < l2_head_block_number { + LayerStatus::Syncing(SyncValues { + synced_block_number: l2_synced_block_number, + head_block_number: l2_head_block_number, + }) + } else { + LayerStatus::Synced(l2_head_block_number) + }; + + Ok(SyncStatus { + l1_status, + l2_status, + }) + } + + async fn subscribe_debug( + &self, + pending: PendingSubscriptionSink, + topic: String, + start_block: BlockNumberOrTag, + end_block: BlockNumberOrTag, + opts: Option, + ) -> SubscriptionResult { + if &topic == "traceChain" { + handle_debug_trace_chain(start_block, end_block, opts, pending, self.ethereum.clone()) + .await; + } else { + pending + .reject(to_eth_rpc_error("Unsupported subscription topic")) + .await; + } + Ok(()) } - Ok(()) + async fn subscribe_eth( + &self, + pending: PendingSubscriptionSink, + topic: String, + filter: Option, + ) -> SubscriptionResult { + match topic.as_str() { + "newHeads" => { + let subscription = pending.accept().await?; + self.ethereum + .subscription_manager + .as_ref() + .unwrap() + .register_new_heads_subscription(subscription) + .await; + } + "logs" => { + let subscription = pending.accept().await?; + self.ethereum + .subscription_manager + .as_ref() + .unwrap() + .register_new_logs_subscription(filter.unwrap_or_default(), subscription) + .await; + } + _ => { + pending + .reject(EthApiError::Unsupported("Unsupported subscription topic")) + .await; + } + } + Ok(()) + } } -// fn get_call_request_and_params( -// from: Address, -// chain_id: u64, -// request: &TransactionRequest, -// ) -> (TransactionRequest, U256, U256) { -// // TODO: we need an oracle to fetch the gas price of the current chain -// // https://github.com/Sovereign-Labs/sovereign-sdk/issues/883 -// let gas_price = request.gas_price.unwrap_or_default(); -// let max_fee_per_gas = request.max_fee_per_gas.unwrap_or_default(); - -// // TODO: Generate call request better according to the transaction type -// // https://github.com/Sovereign-Labs/sovereign-sdk/issues/946 -// let call_request = TransactionRequest { -// from: Some(from), -// to: request.to, -// gas: request.gas, -// gas_price: Some(U256::from(gas_price)), -// max_fee_per_gas: Some(U256::from(max_fee_per_gas)), -// value: request.value, -// input: request.input.clone(), -// nonce: request.nonce, -// chain_id: Some(U64::from(chain_id)), -// access_list: request.access_list.clone(), -// max_priority_fee_per_gas: Some(U256::from(max_fee_per_gas)), -// transaction_type: None, -// blob_versioned_hashes: None, -// max_fee_per_blob_gas: None, -// sidecar: None, -// other: OtherFields::default(), -// }; - -// (call_request, gas_price, max_fee_per_gas) -// } +pub fn create_rpc_module( + da_service: Arc, + eth_rpc_config: EthRpcConfig, + storage: C::Storage, + ledger_db: LedgerDB, + sequencer_client_url: Option, + soft_confirmation_rx: Option>, +) -> RpcModule> +where + C: sov_modules_api::Context, + Da: DaService, +{ + // Unpack config + let EthRpcConfig { + gas_price_oracle_config, + fee_history_cache_config, + } = eth_rpc_config; + + // If the node does not have a sequencer client, then it is the sequencer. + let is_sequencer = sequencer_client_url.is_none(); + let enable_subscriptions = soft_confirmation_rx.is_some(); + + // If the running node is a full node rpc context should also have sequencer client so that it can send txs to sequencer + let ethereum = Arc::new(Ethereum::new( + da_service, + gas_price_oracle_config, + fee_history_cache_config, + storage, + ledger_db, + sequencer_client_url.map(|url| HttpClientBuilder::default().build(url).unwrap()), + soft_confirmation_rx, + )); + let server = EthereumRpcServerImpl::new(ethereum); + + let mut module = EthereumRpcServer::into_rpc(server); + + if is_sequencer { + module.remove_method("eth_sendRawTransaction"); + module.remove_method("eth_getTransactionByHash"); + module.remove_method("citrea_syncStatus"); + } + + if !enable_subscriptions { + module.remove_method("eth_subscribe"); + module.remove_method("eth_unsubscribe"); + module.remove_method("debug_subscribe"); + module.remove_method("debug_unsubscribe"); + } + + module +} diff --git a/crates/ethereum-rpc/src/trace.rs b/crates/ethereum-rpc/src/trace.rs index 1e3100c9f..8699b82a3 100644 --- a/crates/ethereum-rpc/src/trace.rs +++ b/crates/ethereum-rpc/src/trace.rs @@ -5,9 +5,8 @@ use alloy_rpc_types_trace::geth::{ CallConfig, CallFrame, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerConfig, GethDebugTracerType, GethDebugTracingOptions, GethTrace, NoopFrame, }; -#[cfg(feature = "local")] use citrea_evm::Evm; -use jsonrpsee::types::{ErrorObjectOwned, ParamsSequence}; +use jsonrpsee::types::ErrorObjectOwned; use jsonrpsee::{PendingSubscriptionSink, SubscriptionMessage}; use reth_primitives::BlockNumberOrTag; use reth_rpc_eth_types::error::EthApiError; @@ -18,25 +17,12 @@ use tracing::error; use crate::ethereum::Ethereum; pub async fn handle_debug_trace_chain( - mut params: ParamsSequence<'_>, + start_block: BlockNumberOrTag, + end_block: BlockNumberOrTag, + opts: Option, pending: PendingSubscriptionSink, ethereum: Arc>, ) { - let start_block: BlockNumberOrTag = match params.next() { - Ok(v) => v, - Err(err) => { - pending.reject(err).await; - return; - } - }; - let end_block: BlockNumberOrTag = match params.next() { - Ok(v) => v, - Err(err) => { - pending.reject(err).await; - return; - } - }; - // start block is exclusive, hence latest is not supported let BlockNumberOrTag::Number(start_block) = start_block else { pending.reject(EthApiError::Unsupported( @@ -77,14 +63,6 @@ pub async fn handle_debug_trace_chain = match params.optional_next() { - Ok(v) => v, - Err(err) => { - pending.reject(err).await; - return; - } - }; - let subscription = pending.accept().await.unwrap(); // This task will be fetching and sending to the subscription sink the list of traces From 9626b781fcb1c974433fcce55eda84fc7f566514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Talip=20Akal=C4=B1n?= <56600661+otaliptus@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:49:43 +0100 Subject: [PATCH 2/2] Remove wrong prefix env (#1633) --- docker/docker-compose.yml | 42 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2ace12c5f..982cbc35b 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -29,27 +29,27 @@ services: platform: linux/amd64 container_name: full-node environment: - - ROLLUP__PUBLIC_KEYS__SEQUENCER_PUBLIC_KEY=4682a70af1d3fae53a5a26b682e2e75f7a1de21ad5fc8d61794ca889880d39d1 - - ROLLUP__PUBLIC_KEYS__SEQUENCER_DA_PUB_KEY=03015a7c4d2cc1c771198686e2ebef6fe7004f4136d61f6225b061d1bb9b821b9b - - ROLLUP__PUBLIC_KEYS__PROVER_DA_PUB_KEY=0357d255ab93638a2d880787ebaadfefdfc9bb51a26b4a37e5d588e04e54c60a42 - - ROLLUP__DA__NODE_URL=http://citrea-bitcoin-testnet4:18443/ - - ROLLUP__DA__NODE_USERNAME=citrea - - ROLLUP__DA__NODE_PASSWORD=citrea - - ROLLUP__DA__NETWORK=testnet - - ROLLUP__DA__TX_BACKUP_DIR= - - ROLLUP__STORAGE__PATH=/mnt/task/citrea-db - - ROLLUP__STORAGE__DB_MAX_OPEN_FILES=5000 - - ROLLUP__RPC__BIND_HOST=0.0.0.0 - - ROLLUP__RPC__BIND_PORT=8080 - - ROLLUP__RPC__MAX_CONNECTIONS=100 - - ROLLUP__RPC__MAX_REQUEST_BODY_SIZE=10485760 - - ROLLUP__RPC__MAX_RESPONSE_BODY_SIZE=10485760 - - ROLLUP__RPC__BATCH_REQUESTS_LIMIT=50 - - ROLLUP__RPC__ENABLE_SUBSCRIPTIONS=true - - ROLLUP__RPC__MAX_SUBSCRIPTIONS_PER_CONNECTION=10 - - ROLLUP__RUNNER__SEQUENCER_CLIENT_URL=https://rpc.testnet.citrea.xyz - - ROLLUP__RUNNER__INCLUDE_TX_BODY=false - - ROLLUP__RUNNER__SYNC_BLOCKS_COUNT=10 + - SEQUENCER_PUBLIC_KEY=4682a70af1d3fae53a5a26b682e2e75f7a1de21ad5fc8d61794ca889880d39d1 + - SEQUENCER_DA_PUB_KEY=03015a7c4d2cc1c771198686e2ebef6fe7004f4136d61f6225b061d1bb9b821b9b + - PROVER_DA_PUB_KEY=0357d255ab93638a2d880787ebaadfefdfc9bb51a26b4a37e5d588e04e54c60a42 + - NODE_URL=http://citrea-bitcoin-testnet4:18443/ + - NODE_USERNAME=citrea + - NODE_PASSWORD=citrea + - NETWORK=testnet + - TX_BACKUP_DIR= + - STORAGE_PATH=/mnt/task/citrea-db + - DB_MAX_OPEN_FILES=5000 + - RPC_BIND_HOST=0.0.0.0 + - RPC_BIND_PORT=8080 + - RPC_MAX_CONNECTIONS=100 + - RPC_MAX_REQUEST_BODY_SIZE=10485760 + - RPC_MAX_RESPONSE_BODY_SIZE=10485760 + - RPC_BATCH_REQUESTS_LIMIT=50 + - RPC_ENABLE_SUBSCRIPTIONS=true + - RPC_MAX_SUBSCRIPTIONS_PER_CONNECTION=10 + - SEQUENCER_CLIENT_URL=https://rpc.testnet.citrea.xyz + - INCLUDE_TX_BODY=false + - SYNC_BLOCKS_COUNT=10 - RUST_LOG=info - JSON_LOGS=1 ports: