use zksync_types::{tx::TransactionExecutionResult, vm_trace::Call, BOOTLOADER_ADDRESS};
use zksync_web3_decl::{
client::{DynClient, L2},
namespaces::DebugNamespaceClient,
};
use super::*;
fn execute_l2_transaction_with_traces(index_in_block: u8) -> TransactionExecutionResult {
let first_call_trace = Call {
from: Address::repeat_byte(index_in_block),
to: Address::repeat_byte(index_in_block + 1),
gas: 100,
gas_used: 42,
..Call::default()
};
let second_call_trace = Call {
from: Address::repeat_byte(0xff - index_in_block),
to: Address::repeat_byte(0xab - index_in_block),
value: 123.into(),
gas: 58,
gas_used: 10,
input: b"input".to_vec(),
output: b"output".to_vec(),
..Call::default()
};
TransactionExecutionResult {
call_traces: vec![first_call_trace, second_call_trace],
..execute_l2_transaction(create_l2_transaction(1, 2))
}
}
#[derive(Debug)]
struct TraceBlockTest(L2BlockNumber);
#[async_trait]
impl HttpTest for TraceBlockTest {
async fn test(
&self,
client: &DynClient<L2>,
pool: &ConnectionPool<Core>,
) -> anyhow::Result<()> {
let tx_results = [0, 1, 2].map(execute_l2_transaction_with_traces);
let mut storage = pool.connection().await?;
let new_l2_block = store_l2_block(&mut storage, self.0, &tx_results).await?;
drop(storage);
let block_ids = [
api::BlockId::Number((*self.0).into()),
api::BlockId::Number(api::BlockNumber::Latest),
api::BlockId::Hash(new_l2_block.hash),
];
for block_id in block_ids {
let block_traces = match block_id {
api::BlockId::Number(number) => client.trace_block_by_number(number, None).await?,
api::BlockId::Hash(hash) => client.trace_block_by_hash(hash, None).await?,
};
assert_eq!(block_traces.len(), tx_results.len()); for (trace, tx_result) in block_traces.iter().zip(&tx_results) {
let api::ResultDebugCall { result } = trace;
assert_eq!(result.from, Address::zero());
assert_eq!(result.to, BOOTLOADER_ADDRESS);
assert_eq!(result.gas, tx_result.transaction.gas_limit());
let expected_calls: Vec<_> = tx_result
.call_traces
.iter()
.map(|call| api::DebugCall::from(call.clone()))
.collect();
assert_eq!(result.calls, expected_calls);
}
}
let missing_block_number = api::BlockNumber::from(*self.0 + 100);
let error = client
.trace_block_by_number(missing_block_number, None)
.await
.unwrap_err();
if let ClientError::Call(error) = error {
assert_eq!(error.code(), ErrorCode::InvalidParams.code());
assert!(
error.message().contains("Block") && error.message().contains("doesn't exist"),
"{error:?}"
);
assert!(error.data().is_none(), "{error:?}");
} else {
panic!("Unexpected error: {error:?}");
}
Ok(())
}
}
#[tokio::test]
async fn tracing_block() {
test_http_server(TraceBlockTest(L2BlockNumber(1))).await;
}
#[derive(Debug)]
struct TraceBlockFlatTest(L2BlockNumber);
#[async_trait]
impl HttpTest for TraceBlockFlatTest {
async fn test(
&self,
client: &DynClient<L2>,
pool: &ConnectionPool<Core>,
) -> anyhow::Result<()> {
let tx_results = [0, 1, 2].map(execute_l2_transaction_with_traces);
let mut storage = pool.connection().await?;
store_l2_block(&mut storage, self.0, &tx_results).await?;
drop(storage);
let block_ids = [
api::BlockId::Number((*self.0).into()),
api::BlockId::Number(api::BlockNumber::Latest),
];
for block_id in block_ids {
if let api::BlockId::Number(number) = block_id {
let block_traces = client.trace_block_by_number_flat(number, None).await?;
assert_eq!(
block_traces.len(),
tx_results.len() * (tx_results[0].call_traces.len() + 1)
);
assert_eq!(block_traces[0].subtraces, 2);
assert_eq!(block_traces[0].traceaddress, [0]);
assert_eq!(block_traces[1].subtraces, 0);
assert_eq!(block_traces[1].traceaddress, [0, 0]);
let top_level_call_indexes = [0, 3, 6];
let top_level_traces = top_level_call_indexes
.iter()
.map(|&i| block_traces[i].clone());
for (top_level_trace, tx_result) in top_level_traces.zip(&tx_results) {
assert_eq!(top_level_trace.action.from, Address::zero());
assert_eq!(top_level_trace.action.to, BOOTLOADER_ADDRESS);
assert_eq!(
top_level_trace.action.gas,
tx_result.transaction.gas_limit()
);
}
}
}
let missing_block_number = api::BlockNumber::from(*self.0 + 100);
let error = client
.trace_block_by_number_flat(missing_block_number, None)
.await
.unwrap_err();
if let ClientError::Call(error) = error {
assert_eq!(error.code(), ErrorCode::InvalidParams.code());
assert!(
error.message().contains("Block") && error.message().contains("doesn't exist"),
"{error:?}"
);
assert!(error.data().is_none(), "{error:?}");
} else {
panic!("Unexpected error: {error:?}");
}
Ok(())
}
}
#[tokio::test]
async fn tracing_block_flat() {
test_http_server(TraceBlockFlatTest(L2BlockNumber(1))).await;
}
#[derive(Debug)]
struct TraceTransactionTest;
#[async_trait]
impl HttpTest for TraceTransactionTest {
async fn test(
&self,
client: &DynClient<L2>,
pool: &ConnectionPool<Core>,
) -> anyhow::Result<()> {
let tx_results = [execute_l2_transaction_with_traces(0)];
let mut storage = pool.connection().await?;
store_l2_block(&mut storage, L2BlockNumber(1), &tx_results).await?;
drop(storage);
let expected_calls: Vec<_> = tx_results[0]
.call_traces
.iter()
.map(|call| api::DebugCall::from(call.clone()))
.collect();
let result = client
.trace_transaction(tx_results[0].hash, None)
.await?
.context("no transaction traces")?;
assert_eq!(result.from, Address::zero());
assert_eq!(result.to, BOOTLOADER_ADDRESS);
assert_eq!(result.gas, tx_results[0].transaction.gas_limit());
assert_eq!(result.calls, expected_calls);
Ok(())
}
}
#[tokio::test]
async fn tracing_transaction() {
test_http_server(TraceTransactionTest).await;
}
#[derive(Debug)]
struct TraceBlockTestWithSnapshotRecovery;
#[async_trait]
impl HttpTest for TraceBlockTestWithSnapshotRecovery {
fn storage_initialization(&self) -> StorageInitialization {
StorageInitialization::empty_recovery()
}
async fn test(
&self,
client: &DynClient<L2>,
pool: &ConnectionPool<Core>,
) -> anyhow::Result<()> {
let snapshot_l2_block_number = StorageInitialization::SNAPSHOT_RECOVERY_BLOCK;
let missing_l2_block_numbers = [
L2BlockNumber(0),
snapshot_l2_block_number - 1,
snapshot_l2_block_number,
];
for number in missing_l2_block_numbers {
let error = client
.trace_block_by_number(number.0.into(), None)
.await
.unwrap_err();
assert_pruned_block_error(&error, snapshot_l2_block_number + 1);
}
TraceBlockTest(snapshot_l2_block_number + 2)
.test(client, pool)
.await?;
Ok(())
}
}
#[tokio::test]
async fn tracing_block_after_snapshot_recovery() {
test_http_server(TraceBlockTestWithSnapshotRecovery).await;
}