diff --git a/.gitmodules b/.gitmodules index e7ddb0f0b6..b5d6cdeea1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "monad-cxx/monad-execution"] path = monad-cxx/monad-execution - url = https://github.com/category-labs/monad.git + url = https://github.com/category-labs/monad + branch = dhil/replay-txs diff --git a/monad-cxx/monad-execution b/monad-cxx/monad-execution index dbf4aae43e..ee8e3a899b 160000 --- a/monad-cxx/monad-execution +++ b/monad-cxx/monad-execution @@ -1 +1 @@ -Subproject commit dbf4aae43e01315c18a6772f3828f5addab7b6fc +Subproject commit ee8e3a899b9a122036a43408ddb2e26588e8402d diff --git a/monad-ethcall/src/lib.rs b/monad-ethcall/src/lib.rs index b396786b31..2aeddb7e2f 100644 --- a/monad-ethcall/src/lib.rs +++ b/monad-ethcall/src/lib.rs @@ -460,6 +460,231 @@ pub fn decode_revert_message(output_data: &[u8]) -> Option { }) } +pub async fn eth_trace_block( + chain_id: u64, + block_header: Header, + block_number: u64, + block_id: Option<[u8; 32]>, + parent_id: Option<[u8; 32]>, + eth_call_executor: Arc, + tracer: MonadTracer, +) -> CallResult { + let chain_config = match chain_id { + ETHEREUM_MAINNET_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_ETHEREUM_MAINNET, + MONAD_DEVNET_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_MONAD_DEVNET, + MONAD_TESTNET_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_MONAD_TESTNET, + MONAD_MAINNET_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_MONAD_MAINNET, + MONAD_TESTNET2_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_MONAD_TESTNET2, + _ => { + return CallResult::Failure(FailureCallResult { + error_code: EthCallResult::OtherError, + message: "unsupported chain id".to_string(), + data: Some(chain_id.to_string()), + }); + } + }; + + let mut rlp_encoded_block_header = vec![]; + block_header.encode(&mut rlp_encoded_block_header); + + let rlp_encoded_block_id = alloy_rlp::encode(block_id.unwrap_or([0_u8; 32])); + + let rlp_encoded_parent_id = alloy_rlp::encode(parent_id.unwrap_or([0_u8; 32])); + + let (send, recv) = channel(); + let sender_ctx = Box::new(SenderContext { sender: send }); + + unsafe { + let sender_ctx_ptr = Box::into_raw(sender_ctx); + + bindings::monad_eth_trace_block_executor_submit( + eth_call_executor.eth_call_executor, + chain_config, + rlp_encoded_block_header.as_ptr(), + rlp_encoded_block_header.len(), + block_number, + rlp_encoded_block_id.as_ptr(), + rlp_encoded_block_id.len(), + rlp_encoded_parent_id.as_ptr(), + rlp_encoded_parent_id.len(), + Some(eth_call_submit_callback), + sender_ctx_ptr as *mut std::ffi::c_void, + tracer.into(), + ) + }; + + let result = match recv.await { + Ok(r) => r, + Err(e) => { + warn!("callback from eth_trace_block_executor failed: {:?}", e); + + return CallResult::Failure(FailureCallResult { + error_code: EthCallResult::OtherError, + message: "internal eth_trace_block error".to_string(), + data: None, + }); + } + }; + + unsafe { + let status_code = (*result).status_code; + + let call_result = match status_code { + ETH_CALL_SUCCESS => { + // TODO(dhil): I don't think these matter for the output of prestate tracing. Other providers don't seem to return them in prestate mode. + let gas_used = (*result).gas_used as u64; + let gas_refund = (*result).gas_refund as u64; + + let output_data_len = (*result).encoded_trace_len; + let output_data = if output_data_len != 0 { + std::slice::from_raw_parts((*result).encoded_trace, output_data_len).to_vec() + } else { + vec![] + }; + + CallResult::Success(SuccessCallResult { + gas_used, + gas_refund, + output_data, + }) + } + _ => { + let cstr_msg = CStr::from_ptr((*result).message.cast()); + let message = match cstr_msg.to_str() { + Ok(str) => String::from(str), + Err(_) => String::from("execution error eth_trace_block message invalid utf-8"), + }; + + CallResult::Failure(FailureCallResult { + error_code: EthCallResult::OtherError, + message, + data: None, + }) + } + }; + + bindings::monad_eth_call_result_release(result); + + call_result + } +} + +pub async fn eth_trace_transaction( + chain_id: u64, + block_header: Header, + block_number: u64, + block_id: Option<[u8; 32]>, + parent_id: Option<[u8; 32]>, + transaction_index: u64, + eth_call_executor: Arc, + tracer: MonadTracer, +) -> CallResult { + let chain_config = match chain_id { + ETHEREUM_MAINNET_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_ETHEREUM_MAINNET, + MONAD_DEVNET_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_MONAD_DEVNET, + MONAD_TESTNET_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_MONAD_TESTNET, + MONAD_MAINNET_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_MONAD_MAINNET, + MONAD_TESTNET2_CHAIN_ID => bindings::monad_chain_config_CHAIN_CONFIG_MONAD_TESTNET2, + _ => { + return CallResult::Failure(FailureCallResult { + error_code: EthCallResult::OtherError, + message: "unsupported chain id".to_string(), + data: Some(chain_id.to_string()), + }); + } + }; + + let mut rlp_encoded_block_header = vec![]; + block_header.encode(&mut rlp_encoded_block_header); + + let rlp_encoded_block_id = alloy_rlp::encode(block_id.unwrap_or([0_u8; 32])); + + let rlp_encoded_parent_id = alloy_rlp::encode(parent_id.unwrap_or([0_u8; 32])); + + let (send, recv) = channel(); + let sender_ctx = Box::new(SenderContext { sender: send }); + + unsafe { + let sender_ctx_ptr = Box::into_raw(sender_ctx); + + bindings::monad_eth_trace_transaction_executor_submit( + eth_call_executor.eth_call_executor, + chain_config, + rlp_encoded_block_header.as_ptr(), + rlp_encoded_block_header.len(), + block_number, + rlp_encoded_block_id.as_ptr(), + rlp_encoded_block_id.len(), + rlp_encoded_parent_id.as_ptr(), + rlp_encoded_parent_id.len(), + transaction_index, + Some(eth_call_submit_callback), + sender_ctx_ptr as *mut std::ffi::c_void, + tracer.into(), + ) + }; + + let result = match recv.await { + Ok(r) => r, + Err(e) => { + warn!( + "callback from eth_trace_transaction_executor failed: {:?}", + e + ); + + return CallResult::Failure(FailureCallResult { + error_code: EthCallResult::OtherError, + message: "internal eth_trace_transaction error".to_string(), + data: None, + }); + } + }; + + unsafe { + let status_code = (*result).status_code; + + let call_result = match status_code { + ETH_CALL_SUCCESS => { + // TODO(dhil): I don't think these matter for the output of prestate tracing. Other providers don't seem to return them in prestate mode. + let gas_used = (*result).gas_used as u64; + let gas_refund = (*result).gas_refund as u64; + + let output_data_len = (*result).encoded_trace_len; + let output_data = if output_data_len != 0 { + std::slice::from_raw_parts((*result).encoded_trace, output_data_len).to_vec() + } else { + vec![] + }; + + CallResult::Success(SuccessCallResult { + gas_used, + gas_refund, + output_data, + }) + } + _ => { + let cstr_msg = CStr::from_ptr((*result).message.cast()); + let message = match cstr_msg.to_str() { + Ok(str) => String::from(str), + Err(_) => { + String::from("execution error eth_trace_transaction message invalid utf-8") + } + }; + + CallResult::Failure(FailureCallResult { + error_code: EthCallResult::OtherError, + message, + data: None, + }) + } + }; + + bindings::monad_eth_call_result_release(result); + + call_result + } +} + #[cfg(test)] mod tests { use alloy_primitives::hex; diff --git a/monad-rpc/src/chainstate/mod.rs b/monad-rpc/src/chainstate/mod.rs index 4680680f24..3e2c9906d4 100644 --- a/monad-rpc/src/chainstate/mod.rs +++ b/monad-rpc/src/chainstate/mod.rs @@ -312,7 +312,7 @@ impl ChainState { }; let block_key = match &block { - BlockTagOrHash::BlockTags(tag) => get_block_key_from_tag(&self.triedb_env, tag.clone()), + BlockTagOrHash::BlockTags(tag) => get_block_key_from_tag(&self.triedb_env, *tag), BlockTagOrHash::Hash(hash) => { let latest_block_key = get_latest_block_key(&self.triedb_env); @@ -394,7 +394,7 @@ impl ChainState { } let block_key = match &block { - BlockTagOrHash::BlockTags(tag) => get_block_key_from_tag(&self.triedb_env, tag.clone()), + BlockTagOrHash::BlockTags(tag) => get_block_key_from_tag(&self.triedb_env, *tag), BlockTagOrHash::Hash(hash) => { let latest_block_key = get_latest_block_key(&self.triedb_env); diff --git a/monad-rpc/src/eth_json_types.rs b/monad-rpc/src/eth_json_types.rs index 0e2dd288b5..3f870fb3e6 100644 --- a/monad-rpc/src/eth_json_types.rs +++ b/monad-rpc/src/eth_json_types.rs @@ -262,7 +262,7 @@ impl<'de, const N: usize> Deserialize<'de> for FixedData { } } -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum BlockTags { Number(Quantity), // voted or finalized #[default] diff --git a/monad-rpc/src/handlers/debug.rs b/monad-rpc/src/handlers/debug.rs index c9a612f644..d4790778f3 100644 --- a/monad-rpc/src/handlers/debug.rs +++ b/monad-rpc/src/handlers/debug.rs @@ -244,7 +244,7 @@ impl Decodable for CallFrame { } } -#[derive(Deserialize, Debug, Default, schemars::JsonSchema, Clone)] +#[derive(Deserialize, Debug, Default, schemars::JsonSchema, Clone, Copy)] #[serde(rename_all = "camelCase")] pub struct TracerObject { #[serde(default)] @@ -253,7 +253,7 @@ pub struct TracerObject { pub config: TracerConfig, } -#[derive(Deserialize, Debug, Default, schemars::JsonSchema, Clone, PartialEq, Eq)] +#[derive(Deserialize, Debug, Default, schemars::JsonSchema, Clone, Copy, PartialEq, Eq)] pub enum Tracer { #[default] #[serde(rename = "callTracer")] @@ -262,7 +262,7 @@ pub enum Tracer { PreStateTracer, } -#[derive(Clone, Debug, Deserialize, Default, schemars::JsonSchema)] +#[derive(Clone, Copy, Debug, Deserialize, Default, schemars::JsonSchema)] #[serde(rename_all = "camelCase")] pub struct TracerConfig { /// onlyTopCall for callTracer, ignored for prestateTracer @@ -278,11 +278,11 @@ pub struct TracerConfig { pub with_log: bool, } -#[derive(Deserialize, Debug, schemars::JsonSchema)] +#[derive(Clone, Copy, Deserialize, Debug, schemars::JsonSchema)] pub struct MonadDebugTraceTransactionParams { - tx_hash: EthHash, + pub tx_hash: EthHash, #[serde(default)] - tracer: TracerObject, + pub tracer: TracerObject, } #[derive(Serialize, Debug, schemars::JsonSchema)] @@ -394,9 +394,9 @@ pub enum CallKind { #[derive(Deserialize, Debug, schemars::JsonSchema)] pub struct MonadDebugTraceBlockByHashParams { - block_hash: EthHash, + pub block_hash: EthHash, #[serde(default)] - tracer: TracerObject, + pub tracer: TracerObject, } #[rpc(method = "debug_traceBlockByHash")] @@ -463,11 +463,11 @@ pub async fn monad_debug_traceBlockByHash( Err(JsonRpcError::internal_error("block not found".into())) } -#[derive(Deserialize, Debug, schemars::JsonSchema)] +#[derive(Clone, Copy, Deserialize, Debug, schemars::JsonSchema)] pub struct MonadDebugTraceBlockByNumberParams { - block_number: BlockTags, + pub block_number: BlockTags, #[serde(default)] - tracer: TracerObject, + pub tracer: TracerObject, } #[derive(Serialize, Debug, schemars::JsonSchema)] diff --git a/monad-rpc/src/handlers/debug_replay.rs b/monad-rpc/src/handlers/debug_replay.rs new file mode 100644 index 0000000000..d5ad84af0f --- /dev/null +++ b/monad-rpc/src/handlers/debug_replay.rs @@ -0,0 +1,240 @@ +// Copyright (C) 2025 Category Labs, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::sync::Arc; + +use monad_ethcall::{ + eth_trace_block, eth_trace_transaction, CallResult, EthCallExecutor, MonadTracer, +}; +use monad_triedb_utils::triedb_env::Triedb; +use monad_types::SeqNum; +use serde_json::value::RawValue; + +use crate::{ + chainstate::get_block_key_from_tag, + eth_json_types::{BlockTagOrHash, BlockTags, EthHash}, + handlers::debug::{ + MonadDebugTraceBlockByHashParams, MonadDebugTraceBlockByNumberParams, + MonadDebugTraceTransactionParams, Tracer, TracerObject, + }, + jsonrpc::{JsonRpcError, JsonRpcResult}, +}; + +impl From for MonadTracer { + fn from(tracer_obj: TracerObject) -> Self { + match tracer_obj.tracer { + Tracer::PreStateTracer if tracer_obj.config.diff_mode => MonadTracer::StateDiffTracer, + Tracer::PreStateTracer => MonadTracer::PreStateTracer, + Tracer::CallTracer => MonadTracer::CallTracer, + } + } +} + +/// A trait for debug trace parameters as well as determining if a request requires transaction replay. +pub trait DebugTraceParams { + /// Returns true if the tracer requires transaction replay (e.g., PreStateTracer). + fn requires_replay(&self) -> bool; + /// Returns the hash or tag parameter payload associated with the trace request. + fn block_tag_or_hash(&self) -> BlockTagOrHash; + /// Returns whether the trace request is for a single transaction or an entire block (e.g. traceTransaction vs traceBlockByNumber). + fn trace_target(&self) -> TraceTarget; + /// Returns the tracer configuration associated with the trace request. + fn tracer(&self) -> TracerObject; +} + +impl DebugTraceParams for MonadDebugTraceTransactionParams { + fn requires_replay(&self) -> bool { + matches!(self.tracer.tracer, Tracer::PreStateTracer) + } + fn block_tag_or_hash(&self) -> BlockTagOrHash { + BlockTagOrHash::Hash(self.tx_hash) + } + fn trace_target(&self) -> TraceTarget { + TraceTarget::Transaction + } + fn tracer(&self) -> TracerObject { + self.tracer + } +} + +impl DebugTraceParams for MonadDebugTraceBlockByNumberParams { + fn requires_replay(&self) -> bool { + matches!(self.tracer.tracer, Tracer::PreStateTracer) + } + fn block_tag_or_hash(&self) -> BlockTagOrHash { + BlockTagOrHash::BlockTags(self.block_number) + } + fn trace_target(&self) -> TraceTarget { + TraceTarget::Block + } + fn tracer(&self) -> TracerObject { + self.tracer + } +} + +impl DebugTraceParams for MonadDebugTraceBlockByHashParams { + fn requires_replay(&self) -> bool { + matches!(self.tracer.tracer, Tracer::PreStateTracer) + } + fn block_tag_or_hash(&self) -> BlockTagOrHash { + BlockTagOrHash::Hash(self.block_hash) + } + fn trace_target(&self) -> TraceTarget { + TraceTarget::Block + } + fn tracer(&self) -> TracerObject { + self.tracer + } +} + +/// Indicates whether the trace request is for a single transaction or for all transactions in a block. +pub enum TraceTarget { + Block, + Transaction, +} + +/// Projects the block tag, treating any hash as 'latest'. Useful for block key retrieval. +impl From<&T> for BlockTags { + fn from(params: &T) -> Self { + match params.block_tag_or_hash() { + BlockTagOrHash::Hash(_) => BlockTags::Latest, + BlockTagOrHash::BlockTags(tag) => tag, + } + } +} + +impl TryFrom for EthHash { + type Error = JsonRpcError; + fn try_from(value: BlockTagOrHash) -> Result { + match value { + BlockTagOrHash::Hash(hash) => Ok(hash), + BlockTagOrHash::BlockTags(_) => Err(JsonRpcError::internal_error( + "expected block hash, found tag".into(), + )), + } + } +} + +/// A generic handler for debug trace requests that requires transaction replay (e.g., PreStateTracer). +pub async fn monad_debug_trace_replay( + triedb_env: &T, + eth_call_executor: Arc, + chain_id: u64, + params: &impl DebugTraceParams, +) -> JsonRpcResult> { + let block_key = get_block_key_from_tag(triedb_env, params.into()).ok_or_else(|| { + JsonRpcError::internal_error("error getting block key from tag: found none".into()) + })?; + let header = triedb_env + .get_block_header(block_key) + .await + .map_err(|e| JsonRpcError::internal_error(format!("error getting block header: {}", e)))? + .ok_or_else(|| { + JsonRpcError::internal_error("error getting block header: found none".into()) + })?; + let tracer: MonadTracer = params.tracer().into(); + let call_result = match params.trace_target() { + TraceTarget::Transaction => { + let tx_hash: EthHash = params.block_tag_or_hash().try_into()?; + let tx_loc = triedb_env + .get_transaction_location_by_hash(block_key, tx_hash.0) + .await + .map_err(JsonRpcError::internal_error)? + .ok_or_else(|| { + JsonRpcError::internal_error(format!("transaction not found: {:?}", tx_hash)) + })?; + let block_key = triedb_env + .get_block_key(SeqNum(tx_loc.block_num)) + .ok_or_else(|| { + JsonRpcError::internal_error( + "error getting block key from block number: found none".into(), + ) + })?; + let parent_key = triedb_env + .get_block_key(SeqNum(tx_loc.block_num - 1)) + .ok_or_else(|| { + JsonRpcError::internal_error( + "error getting parent block key from block number: found none".into(), + ) + })?; + let (seq_number, block_id) = block_key.seq_num_block_id(); + let (_, parent_id) = parent_key.seq_num_block_id(); + eth_trace_transaction( + chain_id, + header.header, + seq_number.0, + block_id.map(|id| id.0 .0), + parent_id.map(|id| id.0 .0), + tx_loc.tx_index, + eth_call_executor, + tracer, + ) + .await + } + TraceTarget::Block => { + let block_key = match params.block_tag_or_hash() { + BlockTagOrHash::Hash(block_hash) => { + if let Some(block_num) = triedb_env + .get_block_number_by_hash(block_key, block_hash.0) + .await + .map_err(JsonRpcError::internal_error)? + { + triedb_env.get_block_key(SeqNum(block_num)).ok_or_else(|| { + JsonRpcError::internal_error( + "error getting block key from block number: found none".into(), + ) + })? + } else { + return Err(JsonRpcError::internal_error(format!( + "block not found: {:?}", + block_hash + ))); + } + } + BlockTagOrHash::BlockTags(_) => block_key, + }; + let (seq_number, block_id) = block_key.seq_num_block_id(); + let parent_key = triedb_env + .get_block_key(SeqNum(seq_number.0 - 1)) + .ok_or_else(|| { + JsonRpcError::internal_error( + "error getting parent block key from block number: found none".into(), + ) + })?; + let (_, parent_id) = parent_key.seq_num_block_id(); + eth_trace_block( + chain_id, + header.header, + seq_number.0, + block_id.map(|id| id.0 .0), + parent_id.map(|id| id.0 .0), + eth_call_executor, + tracer, + ) + .await + } + }; + let raw_payload = match call_result { + CallResult::Success(monad_ethcall::SuccessCallResult { output_data, .. }) => output_data, + CallResult::Failure(error) => { + return Err(JsonRpcError::eth_call_error(error.message, error.data)) + } + CallResult::Revert(result) => result.trace, + }; + let v: serde_cbor::Value = serde_cbor::from_slice(&raw_payload) + .map_err(|e| JsonRpcError::internal_error(format!("cbor decode error: {}", e)))?; + serde_json::value::to_raw_value(&v) + .map_err(|e| JsonRpcError::internal_error(format!("json serialization error: {}", e))) +} diff --git a/monad-rpc/src/handlers/eth/call.rs b/monad-rpc/src/handlers/eth/call.rs index a209ad2d7e..e6dff9ddd4 100644 --- a/monad-rpc/src/handlers/eth/call.rs +++ b/monad-rpc/src/handlers/eth/call.rs @@ -616,7 +616,7 @@ impl CallParams { } #[tracing::instrument(level = "debug")] -async fn prepare_eth_call( +pub async fn prepare_eth_call( triedb_env: &T, eth_call_executor: Arc, chain_id: u64, diff --git a/monad-rpc/src/handlers/mod.rs b/monad-rpc/src/handlers/mod.rs index 1aee0e3d0e..60263aacbd 100644 --- a/monad-rpc/src/handlers/mod.rs +++ b/monad-rpc/src/handlers/mod.rs @@ -26,6 +26,7 @@ use self::{ monad_debug_getRawTransaction, monad_debug_traceBlockByHash, monad_debug_traceBlockByNumber, monad_debug_traceTransaction, }, + debug_replay::{monad_debug_trace_replay, DebugTraceParams}, eth::{ account::{ monad_eth_getBalance, monad_eth_getCode, monad_eth_getStorageAt, @@ -52,12 +53,17 @@ use self::{ }; use crate::{ eth_json_types::serialize_result, + handlers::debug::{ + MonadDebugTraceBlockByHashParams, MonadDebugTraceBlockByNumberParams, + MonadDebugTraceTransactionParams, + }, jsonrpc::{JsonRpcError, JsonRpcResultExt, Request, RequestWrapper, Response, ResponseWrapper}, timing::RequestId, vpool::{monad_txpool_statusByAddress, monad_txpool_statusByHash}, }; mod debug; +mod debug_replay; pub mod eth; mod meta; pub mod resources; @@ -266,25 +272,76 @@ async fn debug_getRawTransaction( #[allow(non_snake_case)] async fn debug_traceBlockByHash( - _: RequestId, + request_id: RequestId, app_state: &MonadRpcResources, params: Value, ) -> Result, JsonRpcError> { let triedb_env = app_state.triedb_reader.as_ref().method_not_supported()?; - let params = serde_json::from_value(params).invalid_params()?; + let params: MonadDebugTraceBlockByHashParams = + serde_json::from_value(params).invalid_params()?; + if params.requires_replay() { + return collect_debug_trace_via_replay(request_id, triedb_env, app_state, ¶ms).await; + } monad_debug_traceBlockByHash(triedb_env, &app_state.archive_reader, params) .await .map(serialize_result)? } +async fn collect_debug_trace_via_replay( + request_id: RequestId, + triedb_env: &impl Triedb, + app_state: &MonadRpcResources, + params: &impl DebugTraceParams, +) -> Result, JsonRpcError> { + let Some(ref eth_call_executor) = app_state.eth_call_executor else { + return Err(JsonRpcError::method_not_supported()); + }; + // acquire the concurrent requests permit + let _permit = match app_state.rate_limiter.try_acquire() { + Ok(permit) => permit, + Err(_) => { + if let Some(tracker) = &app_state.eth_call_stats_tracker { + tracker.record_queue_rejection().await; + } + return Err(JsonRpcError::internal_error( + "eth_call concurrent requests limit".into(), + )); + } + }; + + if let Some(tracker) = &app_state.eth_call_stats_tracker { + tracker.record_request_start(&request_id).await; + } + + let result = monad_debug_trace_replay( + triedb_env, + eth_call_executor.clone(), + app_state.chain_id, + params, + ) + .await; + + if let Some(tracker) = &app_state.eth_call_stats_tracker { + let is_error = result.is_err(); + tracker.record_request_complete(&request_id, is_error).await; + } + + result +} + #[allow(non_snake_case)] async fn debug_traceBlockByNumber( - _: RequestId, + request_id: RequestId, app_state: &MonadRpcResources, params: Value, ) -> Result, JsonRpcError> { let triedb_env = app_state.triedb_reader.as_ref().method_not_supported()?; - let params = serde_json::from_value(params).invalid_params()?; + let params: MonadDebugTraceBlockByNumberParams = + serde_json::from_value(params).invalid_params()?; + if params.requires_replay() { + return collect_debug_trace_via_replay(request_id, triedb_env, app_state, ¶ms).await; + } + monad_debug_traceBlockByNumber(triedb_env, &app_state.archive_reader, params) .await .map(serialize_result)? @@ -320,12 +377,17 @@ async fn debug_traceCall( #[allow(non_snake_case)] async fn debug_traceTransaction( - _: RequestId, + request_id: RequestId, app_state: &MonadRpcResources, params: Value, ) -> Result, JsonRpcError> { let triedb_env = app_state.triedb_reader.as_ref().method_not_supported()?; - let params = serde_json::from_value(params).invalid_params()?; + let params: MonadDebugTraceTransactionParams = + serde_json::from_value(params).invalid_params()?; + if params.requires_replay() { + return collect_debug_trace_via_replay(request_id, triedb_env, app_state, ¶ms).await; + } + monad_debug_traceTransaction(triedb_env, &app_state.archive_reader, params) .await .map(serialize_result)? diff --git a/monad-triedb-utils/src/triedb_env.rs b/monad-triedb-utils/src/triedb_env.rs index 613630db48..1c0ca5db6a 100644 --- a/monad-triedb-utils/src/triedb_env.rs +++ b/monad-triedb-utils/src/triedb_env.rs @@ -455,6 +455,13 @@ impl BlockKey { BlockKey::Proposed(ProposedBlockKey(seq_num, _)) => seq_num, } } + + pub fn seq_num_block_id(&self) -> (&SeqNum, Option<&BlockId>) { + match self { + BlockKey::Finalized(FinalizedBlockKey(seq_num)) => (seq_num, None), + BlockKey::Proposed(ProposedBlockKey(seq_num, block_id)) => (seq_num, Some(block_id)), + } + } } impl From for Version {