Skip to content

Commit

Permalink
feat: adds debug_traceBlockByNumber.callFlatTracer (#1413)
Browse files Browse the repository at this point in the history
## What ❔

This PR adds support for ‘Parity style’ output for
`debug_traceBlockByNumber` tracer.
Specifically adds `.callFlatTracer` to modify the format of the tracer
output so that it’s the same format as Parity’s tracer output.

## Why ❔

We want to provide a way to get the the same format as parity in
`debug_traceBlockByNumber`

## Checklist

<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->

- [X] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [X] Tests for the changes have been added / updated.
- [X] Documentation comments have been added / updated.
- [x] Code has been formatted via `zk fmt` and `zk lint`.
- [x] Spellcheck has been run via `zk spellcheck`.
- [X] Linkcheck has been run via `zk linkcheck`.
  • Loading branch information
HermanObst committed Mar 15, 2024
1 parent e6fa936 commit d2a5e36
Show file tree
Hide file tree
Showing 6 changed files with 365 additions and 0 deletions.
258 changes: 258 additions & 0 deletions core/lib/types/src/debug_flat_call.rs
@@ -0,0 +1,258 @@
use serde::{Deserialize, Serialize};
use zksync_basic_types::web3::types::{Bytes, U256};

use crate::{
api::{DebugCall, DebugCallType, ResultDebugCall},
Address,
};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DebugCallFlat {
pub action: Action,
pub result: CallResult,
pub subtraces: usize,
pub traceaddress: Vec<usize>,
pub error: Option<String>,
pub revert_reason: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Action {
pub r#type: DebugCallType,
pub from: Address,
pub to: Address,
pub gas: U256,
pub value: U256,
pub input: Bytes,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CallResult {
pub output: Bytes,
pub gas_used: U256,
}

pub fn flatten_debug_calls(calls: Vec<ResultDebugCall>) -> Vec<DebugCallFlat> {
let mut flattened_calls = Vec::new();
for (index, result_debug_call) in calls.into_iter().enumerate() {
let mut trace_address = vec![index]; // Initialize the trace addressees with the index of the top-level call
flatten_call_recursive(
&result_debug_call.result,
&mut flattened_calls,
&mut trace_address,
);
}
flattened_calls
}

fn flatten_call_recursive(
call: &DebugCall,
flattened_calls: &mut Vec<DebugCallFlat>,
trace_address: &mut Vec<usize>,
) {
let flat_call = DebugCallFlat {
action: Action {
r#type: call.r#type.clone(),
from: call.from,
to: call.to,
gas: call.gas,
value: call.value,
input: call.input.clone(),
},
result: CallResult {
output: call.output.clone(),
gas_used: call.gas_used,
},
subtraces: call.calls.len(),
traceaddress: trace_address.clone(), // Clone the current trace address
error: call.error.clone(),
revert_reason: call.revert_reason.clone(),
};
flattened_calls.push(flat_call);

// Process nested calls
for (index, nested_call) in call.calls.iter().enumerate() {
trace_address.push(index); // Update trace addressees for the nested call
flatten_call_recursive(nested_call, flattened_calls, trace_address);
trace_address.pop(); // Reset trace addressees after processing the nested call (prevent to keep filling the vector)
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::{
api::{DebugCall, DebugCallType, ResultDebugCall},
vm_trace::Call,
Address, BOOTLOADER_ADDRESS,
};

#[test]
fn test_flatten_debug_call() {
let result_debug_trace: Vec<ResultDebugCall> = [1, 1]
.map(|_| ResultDebugCall {
result: new_testing_debug_call(),
})
.into();

let debug_call_flat = flatten_debug_calls(result_debug_trace);
let expected_debug_call_flat = expected_flat_trace();
assert_eq!(debug_call_flat, expected_debug_call_flat);
}

fn new_testing_debug_call() -> DebugCall {
DebugCall {
r#type: DebugCallType::Call,
from: Address::zero(),
to: BOOTLOADER_ADDRESS,
gas: 1000.into(),
gas_used: 1000.into(),
value: 0.into(),
output: vec![].into(),
input: vec![].into(),
error: None,
revert_reason: None,
calls: new_testing_trace(),
}
}

fn new_testing_trace() -> Vec<DebugCall> {
let first_call_trace = Call {
from: Address::zero(),
to: Address::zero(),
gas: 100,
gas_used: 42,
..Call::default()
};
let second_call_trace = Call {
from: Address::zero(),
to: Address::zero(),
value: 123.into(),
gas: 58,
gas_used: 10,
input: b"input".to_vec(),
output: b"output".to_vec(),
..Call::default()
};
[first_call_trace, second_call_trace]
.map(|call_trace| call_trace.into())
.into()
}

fn expected_flat_trace() -> Vec<DebugCallFlat> {
[
DebugCallFlat {
action: Action {
r#type: DebugCallType::Call,
from: Address::zero(),
to: BOOTLOADER_ADDRESS,
gas: 1000.into(),
value: 0.into(),
input: vec![].into(),
},
result: CallResult {
output: vec![].into(),
gas_used: 1000.into(),
},
subtraces: 2,
traceaddress: [0].into(),
error: None,
revert_reason: None,
},
DebugCallFlat {
action: Action {
r#type: DebugCallType::Call,
from: Address::zero(),
to: Address::zero(),
gas: 100.into(),
value: 0.into(),
input: vec![].into(),
},
result: CallResult {
output: vec![].into(),
gas_used: 42.into(),
},
subtraces: 0,
traceaddress: [0, 0].into(),
error: None,
revert_reason: None,
},
DebugCallFlat {
action: Action {
r#type: DebugCallType::Call,
from: Address::zero(),
to: Address::zero(),
gas: 58.into(),
value: 123.into(),
input: b"input".to_vec().into(),
},
result: CallResult {
output: b"output".to_vec().into(),
gas_used: 10.into(),
},
subtraces: 0,
traceaddress: [0, 1].into(),
error: None,
revert_reason: None,
},
DebugCallFlat {
action: Action {
r#type: DebugCallType::Call,
from: Address::zero(),
to: BOOTLOADER_ADDRESS,
gas: 1000.into(),
value: 0.into(),
input: vec![].into(),
},
result: CallResult {
output: vec![].into(),
gas_used: 1000.into(),
},
subtraces: 2,
traceaddress: [1].into(),
error: None,
revert_reason: None,
},
DebugCallFlat {
action: Action {
r#type: DebugCallType::Call,
from: Address::zero(),
to: Address::zero(),
gas: 100.into(),
value: 0.into(),
input: vec![].into(),
},
result: CallResult {
output: vec![].into(),
gas_used: 42.into(),
},
subtraces: 0,
traceaddress: [1, 0].into(),
error: None,
revert_reason: None,
},
DebugCallFlat {
action: Action {
r#type: DebugCallType::Call,
from: Address::zero(),
to: Address::zero(),
gas: 58.into(),
value: 123.into(),
input: b"input".to_vec().into(),
},
result: CallResult {
output: b"output".to_vec().into(),
gas_used: 10.into(),
},
subtraces: 0,
traceaddress: [1, 1].into(),
error: None,
revert_reason: None,
},
]
.into()
}
}
1 change: 1 addition & 0 deletions core/lib/types/src/lib.rs
Expand Up @@ -28,6 +28,7 @@ pub mod block;
pub mod circuit;
pub mod commitment;
pub mod contract_verification_api;
pub mod debug_flat_call;
pub mod event;
pub mod fee;
pub mod fee_model;
Expand Down
7 changes: 7 additions & 0 deletions core/lib/web3_decl/src/namespaces/debug.rs
@@ -1,6 +1,7 @@
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use zksync_types::{
api::{BlockId, BlockNumber, DebugCall, ResultDebugCall, TracerConfig},
debug_flat_call::DebugCallFlat,
transaction_request::CallRequest,
};

Expand All @@ -25,6 +26,12 @@ pub trait DebugNamespace {
block: BlockNumber,
options: Option<TracerConfig>,
) -> RpcResult<Vec<ResultDebugCall>>;
#[method(name = "traceBlockByNumber.callFlatTracer")]
async fn trace_block_by_number_flat(
&self,
block: BlockNumber,
options: Option<TracerConfig>,
) -> RpcResult<Vec<DebugCallFlat>>;
#[method(name = "traceBlockByHash")]
async fn trace_block_by_hash(
&self,
Expand Down
@@ -1,5 +1,6 @@
use zksync_types::{
api::{BlockId, BlockNumber, DebugCall, ResultDebugCall, TracerConfig},
debug_flat_call::DebugCallFlat,
transaction_request::CallRequest,
H256,
};
Expand All @@ -22,6 +23,16 @@ impl DebugNamespaceServer for DebugNamespace {
.map_err(|err| self.current_method().map_err(err))
}

async fn trace_block_by_number_flat(
&self,
block: BlockNumber,
options: Option<TracerConfig>,
) -> RpcResult<Vec<DebugCallFlat>> {
self.debug_trace_block_flat_impl(BlockId::Number(block), options)
.await
.map_err(|err| self.current_method().map_err(err))
}

async fn trace_block_by_hash(
&self,
hash: H256,
Expand Down
12 changes: 12 additions & 0 deletions core/lib/zksync_core/src/api_server/web3/namespaces/debug.rs
Expand Up @@ -6,6 +6,7 @@ use once_cell::sync::OnceCell;
use zksync_system_constants::MAX_ENCODED_TX_SIZE;
use zksync_types::{
api::{BlockId, BlockNumber, DebugCall, ResultDebugCall, TracerConfig},
debug_flat_call::{flatten_debug_calls, DebugCallFlat},
fee_model::BatchFeeInput,
l2::L2Tx,
transaction_request::CallRequest,
Expand Down Expand Up @@ -92,6 +93,17 @@ impl DebugNamespace {
Ok(call_trace)
}

#[tracing::instrument(skip(self))]
pub async fn debug_trace_block_flat_impl(
&self,
block_id: BlockId,
options: Option<TracerConfig>,
) -> Result<Vec<DebugCallFlat>, Web3Error> {
let call_trace = self.debug_trace_block_impl(block_id, options).await?;
let call_trace_flat = flatten_debug_calls(call_trace);
Ok(call_trace_flat)
}

#[tracing::instrument(skip(self))]
pub async fn debug_trace_transaction_impl(
&self,
Expand Down

0 comments on commit d2a5e36

Please sign in to comment.