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 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 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 let chain_id = self.consensus.best_chain_id();
107 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, )
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 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 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 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 )
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 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}