Skip to content

Commit

Permalink
feat: impl debug_traceBlockByHash and debug_traceBlockByNumber (#168
Browse files Browse the repository at this point in the history
)
  • Loading branch information
grw-ms authored Oct 11, 2023
1 parent f806bb9 commit 3ff215f
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 12 deletions.
78 changes: 76 additions & 2 deletions SUPPORTED_APIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ The `status` options are:
| [`CONFIG`](#config-namespace) | [`config_setShowVmDetails`](#config_setshowvmdetails) | `SUPPORTED` | Updates `show_vm_details` to print more detailed results from vm execution |
| [`CONFIG`](#config-namespace) | [`config_setShowGasDetails`](#config_setshowgasdetails) | `SUPPORTED` | Updates `show_gas_details` to print more details about gas estimation and usage |
| [`DEBUG`](#debug-namespace) | [`debug_traceCall`](#debug_tracecall) | `SUPPORTED` | Performs a call and returns structured traces of the execution |
| `DEBUG` | `debug_traceBlockByHash` | `NOT IMPLEMENTED`<br />[GitHub Issue #63](https://github.com/matter-labs/era-test-node/issues/63) | Returns structured traces for operations within the block of the specified block hash |
| `DEBUG` | `debug_traceBlockByNumber` | `NOT IMPLEMENTED`<br />[GitHub Issue #64](https://github.com/matter-labs/era-test-node/issues/64) | Returns structured traces for operations within the block of the specified block number |
| [`DEBUG`](#debug-namespace) | [`debug_traceBlockByHash`](#debug_traceblockbyhash) | `SUPPORTED` | Returns structured traces for operations within the block of the specified block hash |
| [`DEBUG`](#debug-namespace) | [`debug_traceBlockByNumber`](#debug_traceblockbynumber) | `SUPPORTED` | Returns structured traces for operations within the block of the specified block number |
| [`DEBUG`](#debug-namespace) | [`debug_traceTransaction`](#debug_tracetransaction) | `SUPPORTED` | Returns a structured trace of the execution of the specified transaction |
| `ETH` | `eth_accounts` | `SUPPORTED` | Returns a list of addresses owned by client |
| [`ETH`](#eth-namespace) | [`eth_chainId`](#eth_chainid) | `SUPPORTED` | Returns the currently configured chain id <br />_(default is `260`)_ |
Expand Down Expand Up @@ -374,6 +374,80 @@ curl --request POST \
}'
```

### `debug_traceBlockByHash`

[source](src/debug.rs)

Returns call traces for each transaction within a given block.

Currently only transactions from blocks mined on the dev node itself (ie, not from upstream when using fork mode) can be traced.

The third argument mirrors the [`TraceConfig` of go-ethereum](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#traceconfig), but with the restriction that the only supported tracer is `CallTracer`. Memory, Stack and Storage traces are not supported.

#### Arguments

- `blockHash: H256`

- `options: TracerConfig` (optional)

#### Status

`SUPPORTED`

#### Example

```bash
curl --request POST \
--url http://localhost:8011/ \
--header 'content-type: application/json' \
--data '{
"jsonrpc": "2.0",
"id": "2",
"method": "debug_traceBlockByHash",
"params": [
"0xd3a94ff697a573cb174ecce05126e952ecea6dee051526a3e389747ff86b0d99",
{ "tracer": "callTracer", "tracerConfig": { "onlyTopCall": true } }
]
}'
```

### `debug_traceBlockByNumber`

[source](src/debug.rs)

Returns call traces for each transaction within a given block.

Currently only transactions from blocks mined on the dev node itself (ie, not from upstream when using fork mode) can be traced.

The third argument mirrors the [`TraceConfig` of go-ethereum](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#traceconfig), but with the restriction that the only supported tracer is `CallTracer`. Memory, Stack and Storage traces are not supported.

#### Arguments

- `blockNumber: BlockNumber`

- `options: TracerConfig` (optional)

#### Status

`SUPPORTED`

#### Example

```bash
curl --request POST \
--url http://localhost:8011/ \
--header 'content-type: application/json' \
--data '{
"jsonrpc": "2.0",
"id": "2",
"method": "debug_traceBlockByNumber",
"params": [
"latest",
{ "tracer": "callTracer", "tracerConfig": { "onlyTopCall": true } }
]
}'
```

## `NETWORK NAMESPACE`

### `net_version`
Expand Down
48 changes: 48 additions & 0 deletions e2e-tests/test/debug-apis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,51 @@ describe("debug_traceTransaction", function () {
expect(trace.calls.length).to.equal(0);
});
});

describe("debug_traceBlockByHash", function () {
it("Should trace prior blocks", async function () {
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

const greeter = await deployContract(deployer, "Greeter", ["Hi"]);

const txReceipt = await greeter.setGreeting("Luke Skywalker");
const latestBlock = await provider.getBlock("latest");
const block = await provider.getBlock(latestBlock.number - 1);

const traces = await provider.send("debug_traceBlockByHash", [block.hash]);

// block should have 1 traces
expect(traces.length).to.equal(1);

// should contain trace for our tx
const trace = traces[0].result;
expect(trace.input).to.equal(txReceipt.data);
});
});

describe("debug_traceBlockByNumber", function () {
it("Should trace prior blocks", async function () {
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

const greeter = await deployContract(deployer, "Greeter", ["Hi"]);

const txReceipt = await greeter.setGreeting("Luke Skywalker");

// latest block will be empty, check we get no traces for it
const empty_traces = await provider.send("debug_traceBlockByNumber", ["latest"]);
expect(empty_traces.length).to.equal(0);

// latest - 1 should contain our traces
const latestBlock = await provider.getBlock("latest");
const traces = await provider.send("debug_traceBlockByNumber", [(latestBlock.number - 1).toString(16)]);

// block should have 1 traces
expect(traces.length).to.equal(1);

// should contain trace for our tx
const trace = traces[0].result;
expect(trace.input).to.equal(txReceipt.data);
});
});
192 changes: 182 additions & 10 deletions src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::{
fork::ForkSource,
node::{InMemoryNodeInner, MAX_TX_SIZE},
utils::{create_debug_output, not_implemented},
utils::{create_debug_output, to_real_block_number},
};
use itertools::Itertools;
use jsonrpc_core::{BoxFuture, Result};
use once_cell::sync::OnceCell;
use std::sync::{Arc, RwLock};
Expand All @@ -13,10 +14,10 @@ use zksync_core::api_server::web3::backend_jsonrpc::{
};
use zksync_state::StorageView;
use zksync_types::{
api::{BlockId, BlockNumber, DebugCall, ResultDebugCall, TracerConfig},
api::{BlockId, BlockNumber, DebugCall, ResultDebugCall, TracerConfig, TransactionVariant},
l2::L2Tx,
transaction_request::CallRequest,
PackedEthSignature, Transaction,
PackedEthSignature, Transaction, U64,
};
use zksync_web3_decl::error::Web3Error;

Expand All @@ -37,18 +38,107 @@ impl<S: Send + Sync + 'static + ForkSource + std::fmt::Debug> DebugNamespaceT
{
fn trace_block_by_number(
&self,
_block: BlockNumber,
_options: Option<TracerConfig>,
block: BlockNumber,
options: Option<TracerConfig>,
) -> BoxFuture<Result<Vec<ResultDebugCall>>> {
not_implemented("debug_traceBlockByNumber")
let only_top = options.is_some_and(|o| o.tracer_config.only_top_call);
let inner = Arc::clone(&self.node);
Box::pin(async move {
let inner = inner
.read()
.map_err(|_| into_jsrpc_error(Web3Error::InternalError))?;

let block = {
let number =
to_real_block_number(block, U64::from(inner.current_miniblock)).as_u64();

inner
.block_hashes
.get(&number)
.and_then(|hash| inner.blocks.get(hash))
.ok_or_else(|| {
into_jsrpc_error(Web3Error::SubmitTransactionError(
"Block not found".to_string(),
vec![],
))
})?
};

let tx_hashes = block
.transactions
.iter()
.map(|tx| match tx {
TransactionVariant::Full(tx) => tx.hash,
TransactionVariant::Hash(hash) => *hash,
})
.collect_vec();

let debug_calls = tx_hashes
.into_iter()
.map(|tx_hash| {
let tx = inner.tx_results.get(&tx_hash).ok_or_else(|| {
into_jsrpc_error(Web3Error::SubmitTransactionError(
"Transaction not found".to_string(),
vec![],
))
})?;
Ok(tx.debug_info(only_top))
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.map(|result| ResultDebugCall { result })
.collect_vec();

Ok(debug_calls)
})
}

fn trace_block_by_hash(
&self,
_hash: H256,
_options: Option<TracerConfig>,
hash: H256,
options: Option<TracerConfig>,
) -> BoxFuture<Result<Vec<ResultDebugCall>>> {
not_implemented("debug_traceBlockByHash")
let only_top = options.is_some_and(|o| o.tracer_config.only_top_call);
let inner = Arc::clone(&self.node);
Box::pin(async move {
let inner = inner
.read()
.map_err(|_| into_jsrpc_error(Web3Error::InternalError))?;

let block = inner.blocks.get(&hash).ok_or_else(|| {
into_jsrpc_error(Web3Error::SubmitTransactionError(
"Block not found".to_string(),
vec![],
))
})?;

let tx_hashes = block
.transactions
.iter()
.map(|tx| match tx {
TransactionVariant::Full(tx) => tx.hash,
TransactionVariant::Hash(hash) => *hash,
})
.collect_vec();

let debug_calls = tx_hashes
.into_iter()
.map(|tx_hash| {
let tx = inner.tx_results.get(&tx_hash).ok_or_else(|| {
into_jsrpc_error(Web3Error::SubmitTransactionError(
"Transaction not found".to_string(),
vec![],
))
})?;
Ok(tx.debug_info(only_top))
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.map(|result| ResultDebugCall { result })
.collect_vec();

Ok(debug_calls)
})
}

/// Trace execution of a transaction.
Expand Down Expand Up @@ -158,7 +248,7 @@ mod tests {
use ethers::abi::{short_signature, AbiEncode, HumanReadableParser, ParamType, Token};
use zksync_basic_types::{Address, Nonce, H160, U256};
use zksync_types::{
api::{CallTracerConfig, SupportedTracers, TransactionReceipt},
api::{Block, CallTracerConfig, SupportedTracers, TransactionReceipt},
transaction_request::CallRequestBuilder,
utils::deployed_address_create,
};
Expand Down Expand Up @@ -401,4 +491,86 @@ mod tests {
.unwrap();
assert!(result.is_none());
}

#[tokio::test]
async fn test_trace_block_by_hash_empty() {
let node = InMemoryNode::<HttpForkSource>::default();
let inner = node.get_inner();
{
let mut writer = inner.write().unwrap();
let block = Block::<TransactionVariant>::default();
writer.blocks.insert(H256::repeat_byte(0x1), block);
}
let result = DebugNamespaceImpl::new(inner)
.trace_block_by_hash(H256::repeat_byte(0x1), None)
.await
.unwrap();
assert_eq!(result.len(), 0);
}

#[tokio::test]
async fn test_trace_block_by_hash() {
let node = InMemoryNode::<HttpForkSource>::default();
let inner = node.get_inner();
{
let mut writer = inner.write().unwrap();
let tx = zksync_types::api::Transaction::default();
let tx_hash = tx.hash;
let mut block = Block::<TransactionVariant>::default();
block.transactions.push(TransactionVariant::Full(tx));
writer.blocks.insert(H256::repeat_byte(0x1), block);
writer.tx_results.insert(
tx_hash,
TransactionResult {
info: testing::default_tx_execution_info(),
receipt: TransactionReceipt::default(),
debug: testing::default_tx_debug_info(),
},
);
}
let result = DebugNamespaceImpl::new(inner)
.trace_block_by_hash(H256::repeat_byte(0x1), None)
.await
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].result.calls.len(), 1);
}

#[tokio::test]
async fn test_trace_block_by_number() {
let node = InMemoryNode::<HttpForkSource>::default();
let inner = node.get_inner();
{
let mut writer = inner.write().unwrap();
let tx = zksync_types::api::Transaction::default();
let tx_hash = tx.hash;
let mut block = Block::<TransactionVariant>::default();
block.transactions.push(TransactionVariant::Full(tx));
writer.blocks.insert(H256::repeat_byte(0x1), block);
writer.block_hashes.insert(0, H256::repeat_byte(0x1));
writer.tx_results.insert(
tx_hash,
TransactionResult {
info: testing::default_tx_execution_info(),
receipt: TransactionReceipt::default(),
debug: testing::default_tx_debug_info(),
},
);
}
// check `latest` alias
let result = DebugNamespaceImpl::new(node.get_inner())
.trace_block_by_number(BlockNumber::Latest, None)
.await
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].result.calls.len(), 1);

// check block number
let result = DebugNamespaceImpl::new(node.get_inner())
.trace_block_by_number(BlockNumber::Number(0.into()), None)
.await
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].result.calls.len(), 1);
}
}
Loading

0 comments on commit 3ff215f

Please sign in to comment.