cfx_rpc/
debug.rs

1use alloy_rpc_types_trace::geth::{
2    GethDebugBuiltInTracerType,
3    GethDebugTracerType::{BuiltInTracer, JsTracer},
4    GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame,
5    TraceResult,
6};
7use async_trait::async_trait;
8use cfx_rpc_eth_api::DebugApiServer;
9use cfx_rpc_eth_types::{BlockId, BlockProperties, TransactionRequest};
10use cfx_rpc_utils::error::jsonrpsee_error_helpers::invalid_params_msg;
11use cfx_types::{AddressSpaceUtil, Space, H256, U256};
12use cfxcore::{
13    errors::Error as CoreError, ConsensusGraph, SharedConsensusGraph,
14};
15use geth_tracer::to_alloy_h256;
16use jsonrpsee::core::RpcResult;
17use primitives::{
18    Block, BlockHashOrEpochNumber, BlockHeaderBuilder, EpochNumber,
19};
20use std::{sync::Arc, vec};
21
22pub struct DebugApi {
23    consensus: SharedConsensusGraph,
24    max_estimation_gas_limit: Option<U256>,
25}
26
27impl DebugApi {
28    pub fn new(
29        consensus: SharedConsensusGraph, max_estimation_gas_limit: Option<U256>,
30    ) -> Self {
31        DebugApi {
32            consensus,
33            max_estimation_gas_limit,
34        }
35    }
36
37    pub fn consensus_graph(&self) -> &ConsensusGraph { &self.consensus }
38
39    pub fn get_block_epoch_num(&self, block: BlockId) -> Result<u64, String> {
40        let num = match block {
41            BlockId::Num(block_number) => block_number,
42            BlockId::Latest | BlockId::Safe | BlockId::Finalized => {
43                let epoch_num = block.try_into().expect("should success");
44                self.consensus_graph()
45                    .get_height_from_epoch_number(epoch_num)?
46            }
47            BlockId::Hash {
48                hash,
49                require_canonical,
50            } => self
51                .consensus_graph()
52                .get_block_epoch_number_with_pivot_check(
53                    &hash,
54                    require_canonical.unwrap_or_default(),
55                )
56                .map_err(|err| err.to_string())?,
57            _ => return Err("not supported".to_string()),
58        };
59        Ok(num)
60    }
61
62    pub fn trace_call(
63        &self, mut request: TransactionRequest, block_number: Option<BlockId>,
64        opts: Option<GethDebugTracingCallOptions>,
65    ) -> Result<GethTrace, CoreError> {
66        if request.from.is_none() {
67            return Err(CoreError::InvalidParam(
68                "from is required".to_string(),
69                Default::default(),
70            ));
71        }
72
73        let opts = opts.unwrap_or_default();
74        let block_num = block_number.unwrap_or_default();
75
76        let epoch_num = self
77            .get_block_epoch_num(block_num)
78            .map_err(|err| CoreError::Msg(err))?;
79
80        // validate epoch state
81        self.consensus_graph()
82            .validate_stated_epoch(&EpochNumber::Number(epoch_num))
83            .map_err(|err| CoreError::Msg(err))?;
84
85        let epoch_block_hashes = self
86            .consensus_graph()
87            .get_block_hashes_by_epoch(EpochNumber::Number(epoch_num))
88            .map_err(|err| CoreError::Msg(err.into()))?;
89        let epoch_id = epoch_block_hashes
90            .last()
91            .ok_or(CoreError::Msg("should have block hash".to_string()))?;
92
93        // nonce auto fill
94        if request.nonce.is_none() {
95            let nonce = self.consensus_graph().next_nonce(
96                request.from.unwrap().with_evm_space(),
97                BlockHashOrEpochNumber::EpochNumber(EpochNumber::Number(
98                    epoch_num,
99                )),
100                "num",
101            )?;
102            request.nonce = Some(nonce);
103        }
104
105        // construct blocks from call_request
106        let chain_id = self.consensus.best_chain_id();
107        // debug trace call has a fixed large gas limit.
108        let signed_tx = request.sign_call(
109            chain_id.in_evm_space(),
110            self.max_estimation_gas_limit,
111        )?;
112        let epoch_blocks = self
113            .consensus_graph()
114            .data_man
115            .blocks_by_hash_list(
116                &epoch_block_hashes,
117                true, /* update_cache */
118            )
119            .ok_or(CoreError::Msg("blocks should exist".to_string()))?;
120        let pivot_block = epoch_blocks
121            .last()
122            .ok_or(CoreError::Msg("should have block".to_string()))?;
123
124        let header = BlockHeaderBuilder::new()
125            .with_base_price(pivot_block.block_header.base_price())
126            .with_parent_hash(pivot_block.block_header.hash())
127            .with_height(epoch_num + 1)
128            .with_timestamp(pivot_block.block_header.timestamp() + 1)
129            .with_gas_limit(*pivot_block.block_header.gas_limit())
130            .build();
131        let block = Block::new(header, vec![Arc::new(signed_tx)]);
132        let blocks: Vec<Arc<Block>> = vec![Arc::new(block)];
133
134        let traces = self.consensus_graph().collect_blocks_geth_trace(
135            *epoch_id,
136            epoch_num,
137            &blocks,
138            opts.tracing_options,
139            None,
140        )?;
141
142        let res = traces
143            .first()
144            .ok_or(CoreError::Msg("trace generation failed".to_string()))?;
145
146        Ok(res.trace.clone())
147    }
148
149    pub fn trace_block_by_num(
150        &self, block_num: u64, opts: Option<GethDebugTracingOptions>,
151    ) -> Result<Vec<TraceResult>, CoreError> {
152        let opts = opts.unwrap_or_default();
153        let epoch_traces = self
154            .consensus_graph()
155            .collect_epoch_geth_trace(block_num, None, opts)?;
156
157        let result = epoch_traces
158            .into_iter()
159            .filter(|val| val.space == Space::Ethereum)
160            .map(|val| TraceResult::Success {
161                result: val.trace,
162                tx_hash: Some(to_alloy_h256(val.tx_hash)),
163            })
164            .collect();
165        Ok(result)
166    }
167
168    pub fn trace_transaction(
169        &self, hash: H256, opts: Option<GethDebugTracingOptions>,
170    ) -> Result<GethTrace, CoreError> {
171        let opts = opts.unwrap_or_default();
172
173        // early return if tracer is not supported or NoopTracer is requested
174        if let Some(tracer_type) = &opts.tracer {
175            match tracer_type {
176                BuiltInTracer(builtin_tracer) => match builtin_tracer {
177                    GethDebugBuiltInTracerType::FourByteTracer => (),
178                    GethDebugBuiltInTracerType::CallTracer => {
179                        // pre check config
180                        let _ = opts
181                            .tracer_config
182                            .clone()
183                            .into_call_config()
184                            .map_err(|err| {
185                            CoreError::Msg(err.to_string())
186                        })?;
187                        ()
188                    }
189                    GethDebugBuiltInTracerType::PreStateTracer => {
190                        // pre check config
191                        let _ = opts
192                            .tracer_config
193                            .clone()
194                            .into_pre_state_config()
195                            .map_err(|err| CoreError::Msg(err.to_string()))?;
196                        ()
197                    }
198                    GethDebugBuiltInTracerType::NoopTracer => {
199                        return Ok(GethTrace::NoopTracer(NoopFrame::default()))
200                    }
201                    GethDebugBuiltInTracerType::MuxTracer => {
202                        return Err(CoreError::Msg("not supported".to_string()))
203                    }
204                    GethDebugBuiltInTracerType::FlatCallTracer => {
205                        return Err(CoreError::Msg("not supported".to_string()))
206                    }
207                },
208                JsTracer(_) => {
209                    return Err(CoreError::Msg("not supported".to_string()))
210                }
211            }
212        }
213
214        let tx_index = self
215            .consensus
216            .data_manager()
217            .transaction_index_by_hash(&hash, false /* update_cache */)
218            .ok_or(CoreError::Msg("invalid tx hash".to_string()))?;
219
220        let epoch_num = self
221            .consensus
222            .get_block_epoch_number(&tx_index.block_hash)
223            .ok_or(CoreError::Msg("invalid tx hash".to_string()))?;
224
225        let epoch_traces = self.consensus_graph().collect_epoch_geth_trace(
226            epoch_num,
227            Some(hash),
228            opts,
229        )?;
230
231        // filter by tx hash
232        let trace = epoch_traces
233            .into_iter()
234            .find(|val| val.tx_hash == hash)
235            .map(|val| val.trace)
236            .ok_or(CoreError::Msg("trace generation failed".to_string()))?;
237
238        Ok(trace)
239    }
240}
241
242#[async_trait]
243impl DebugApiServer for DebugApi {
244    async fn db_get(&self, _key: String) -> RpcResult<Option<String>> {
245        Ok(Some("To be implemented!".into()))
246    }
247
248    async fn debug_trace_transaction(
249        &self, tx_hash: H256, opts: Option<GethDebugTracingOptions>,
250    ) -> RpcResult<GethTrace> {
251        self.trace_transaction(tx_hash, opts).map_err(|e| e.into())
252    }
253
254    async fn debug_trace_block_by_hash(
255        &self, block_hash: H256, opts: Option<GethDebugTracingOptions>,
256    ) -> RpcResult<Vec<TraceResult>> {
257        let epoch_num = self
258            .consensus_graph()
259            .get_block_epoch_number_with_pivot_check(&block_hash, false)?;
260
261        self.trace_block_by_num(epoch_num, opts)
262            .map_err(|e| e.into())
263    }
264
265    async fn debug_trace_block_by_number(
266        &self, block: BlockId, opts: Option<GethDebugTracingOptions>,
267    ) -> RpcResult<Vec<TraceResult>> {
268        let num = self
269            .get_block_epoch_num(block)
270            .map_err(|e| invalid_params_msg(&e))?;
271
272        self.trace_block_by_num(num, opts).map_err(|e| e.into())
273    }
274
275    async fn debug_trace_call(
276        &self, request: TransactionRequest, block_number: Option<BlockId>,
277        opts: Option<GethDebugTracingCallOptions>,
278    ) -> RpcResult<GethTrace> {
279        self.trace_call(request, block_number, opts)
280            .map_err(|e| e.into())
281    }
282
283    async fn debug_block_properties(
284        &self, block_number: BlockId,
285    ) -> RpcResult<Option<Vec<BlockProperties>>> {
286        let hashes = self
287            .consensus
288            .get_block_hashes_by_epoch_or_block_hash(block_number.into())
289            .map_err(|err| invalid_params_msg(&err.to_string()))?;
290
291        let blocks = self
292            .consensus
293            .data_manager()
294            .blocks_by_hash_list(&hashes, false)
295            .ok_or_else(|| invalid_params_msg("blocks should exist"))?;
296
297        let mut res = vec![];
298
299        for block in blocks {
300            let block_props = BlockProperties {
301                tx_hash: None,
302                inner_block_hash: block.hash(),
303                coinbase: *block.block_header.author(),
304                difficulty: *block.block_header.difficulty(),
305                gas_limit: *block.block_header.gas_limit(),
306                timestamp: block.block_header.timestamp().into(),
307                base_fee_per_gas: block
308                    .block_header
309                    .base_price()
310                    .map(|v| v.in_space(Space::Ethereum).clone()),
311            };
312
313            for tx in block.transactions.iter() {
314                if tx.space() == Space::Ethereum {
315                    let mut props = block_props.clone();
316                    props.tx_hash = Some(tx.hash());
317                    res.push(props);
318                }
319            }
320        }
321
322        Ok(Some(res))
323    }
324}