Skip to content

Commit a2d03e1

Browse files
committed
Improve block reexecutor functionality
1 parent e50358b commit a2d03e1

File tree

8 files changed

+529
-42
lines changed

8 files changed

+529
-42
lines changed

forward_system/src/system/tracers/call_tracer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ impl CallLogFrame {
233233
),
234234
data: Some(Bytes::from(self.data.clone())),
235235
position: None, // TODO
236-
index: None, // TODO
236+
index: None, // TODO
237237
}
238238
}
239239
}

tests/instances/block_reexecutor/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,39 @@ Re-executes a single L2 block against RPC state, validates transaction outputs a
1919
RUST_LOG=info cargo run -p block_reexecutor -- --endpoint <rpc> --block-hash <block_hash>
2020
```
2121

22+
### Run With Predefined Transactions
23+
24+
Use state/block metadata from `--block-number`, but execute transactions loaded from a JSON file:
25+
26+
```bash
27+
RUST_LOG=info cargo run -p block_reexecutor -- \
28+
--endpoint <rpc> \
29+
--block-number <block_number> \
30+
--transactions-file <path/to/transactions.json>
31+
```
32+
33+
`transactions.json` must contain RLP tx payload + signer, with tx bytes encoded as hex string:
34+
35+
```json
36+
[
37+
{
38+
"tx": "0x02f86e82853901843b...",
39+
"signer": "0x1111111111111111111111111111111111111111",
40+
"hash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
41+
}
42+
]
43+
```
44+
45+
Also accepted for compatibility:
46+
47+
- `{"rlp":"0x...", "signer":"0x...", "hash":"0x..."}`
48+
- `{"Rlp":["0x...", "0x..."], "hash":"0x..."}`
49+
- previous `EncodedTx` JSON with byte arrays
50+
51+
The tool decodes every RLP payload into an Ethereum transaction for REVM tracing and uses the same `EncodedTx::Rlp` values for block reexecution.
52+
If `hash` is provided for predefined txs, execution results are validated against RPC receipts matched by hash.
53+
If RPC returns `null` for any provided hash, the tool logs it and skips receipt checks.
54+
2255
## Output files
2356

2457
- `tracer_output_<block_number>.json`

tests/instances/block_reexecutor/src/cache.rs

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
use alloy::{hex, primitives::B256};
77
use anyhow::{Context, Result};
88
use rig::zk_ee::utils::Bytes32;
9-
use ruint::aliases::B160;
9+
use ruint::aliases::{B160, U256};
1010

1111
use crate::rpc_client::{Block, BlockMetadataResult, RpcClient, TransactionReceipt};
1212

@@ -16,6 +16,7 @@ pub struct LoadedBlockParams {
1616
pub block_metadata: BlockMetadataResult,
1717
pub chain_id: u64,
1818
pub receipts: Vec<TransactionReceipt>,
19+
pub historical_block_hashes: [U256; 256],
1920
}
2021

2122
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -39,6 +40,8 @@ struct DiskBlockParams {
3940
chain_id: u64,
4041
#[serde(default)]
4142
receipts: Vec<TransactionReceipt>,
43+
#[serde(default)]
44+
historical_block_hashes: Vec<String>,
4245
}
4346

4447
pub fn block_params_cache_path(block_hash: B256) -> PathBuf {
@@ -73,20 +76,36 @@ pub fn load_or_fetch_block_params(
7376
cached.receipts.len(),
7477
cached_block_tx_count
7578
);
76-
} else {
77-
println!(
78-
"Loaded block params from disk cache: {:?} (block_number={}, chain_id={}, receipts={})",
79-
cache_path,
80-
cached.block.result.header.number,
81-
cached.chain_id,
82-
cached.receipts.len()
79+
} else if cached.historical_block_hashes.len() != 256 {
80+
eprintln!(
81+
"Block params cache is stale (historical block hash count mismatch: expected 256, got {}), refetching",
82+
cached.historical_block_hashes.len()
8383
);
84-
return Ok(LoadedBlockParams {
85-
block: cached.block,
86-
block_metadata: cached.block_metadata,
87-
chain_id: cached.chain_id,
88-
receipts: cached.receipts,
89-
});
84+
} else {
85+
match decode_historical_block_hashes(&cached.historical_block_hashes) {
86+
Ok(historical_block_hashes) => {
87+
println!(
88+
"Loaded block params from disk cache: {:?} (block_number={}, chain_id={}, receipts={}, historical_block_hashes={})",
89+
cache_path,
90+
cached.block.result.header.number,
91+
cached.chain_id,
92+
cached.receipts.len(),
93+
cached.historical_block_hashes.len()
94+
);
95+
return Ok(LoadedBlockParams {
96+
block: cached.block,
97+
block_metadata: cached.block_metadata,
98+
chain_id: cached.chain_id,
99+
receipts: cached.receipts,
100+
historical_block_hashes,
101+
});
102+
}
103+
Err(err) => {
104+
eprintln!(
105+
"Block params cache is stale (invalid historical block hashes), refetching: {err}"
106+
);
107+
}
108+
}
90109
}
91110
}
92111
Ok(None) => {
@@ -108,6 +127,7 @@ pub fn load_or_fetch_block_params(
108127
let block_metadata = rpc_client.get_block_metadata(block_number)?;
109128
let chain_id = rpc_client.get_chain_id()?;
110129
let receipts = rpc_client.get_block_receipts(block_number)?.result;
130+
let historical_block_hashes = fetch_historical_block_hashes(rpc_client, block_number)?;
111131

112132
if let Err(err) = save_block_params_cache(
113133
cache_path,
@@ -116,15 +136,17 @@ pub fn load_or_fetch_block_params(
116136
&block_metadata,
117137
chain_id,
118138
&receipts,
139+
&historical_block_hashes,
119140
) {
120141
eprintln!("Failed to save block params cache: {err}");
121142
} else {
122143
println!(
123-
"Saved block params cache to disk: {:?} (block_number={}, chain_id={}, receipts={})",
144+
"Saved block params cache to disk: {:?} (block_number={}, chain_id={}, receipts={}, historical_block_hashes={})",
124145
cache_path,
125146
block_number,
126147
chain_id,
127-
receipts.len()
148+
receipts.len(),
149+
historical_block_hashes.len()
128150
);
129151
}
130152

@@ -133,6 +155,7 @@ pub fn load_or_fetch_block_params(
133155
block_metadata,
134156
chain_id,
135157
receipts,
158+
historical_block_hashes,
136159
})
137160
}
138161

@@ -264,6 +287,7 @@ fn save_block_params_cache(
264287
block_metadata: &BlockMetadataResult,
265288
chain_id: u64,
266289
receipts: &[TransactionReceipt],
290+
historical_block_hashes: &[U256; 256],
267291
) -> Result<()> {
268292
let cache_dir = cache_path
269293
.parent()
@@ -277,6 +301,7 @@ fn save_block_params_cache(
277301
block_metadata: block_metadata.clone(),
278302
chain_id,
279303
receipts: receipts.to_vec(),
304+
historical_block_hashes: encode_historical_block_hashes(historical_block_hashes),
280305
};
281306

282307
std::fs::write(cache_path, serde_json::to_vec_pretty(&payload)?)
@@ -300,3 +325,59 @@ fn decode_bytes_hex(value: &str) -> Result<Vec<u8>> {
300325
hex::decode(stripped).with_context(|| format!("failed to decode hex value `{value}`"))?;
301326
Ok(raw)
302327
}
328+
329+
fn fetch_historical_block_hashes(rpc_client: &RpcClient, block_number: u64) -> Result<[U256; 256]> {
330+
let mut block_hashes = [U256::ZERO; 256];
331+
let mut loaded = 0usize;
332+
333+
// Chain expects newest historical block hash at index 255.
334+
for depth in 1u64..=256 {
335+
println!(
336+
"Fetching historical block hash for block #{}, depth {}",
337+
block_number - depth,
338+
depth
339+
);
340+
let Some(target_block_number) = block_number.checked_sub(depth) else {
341+
break;
342+
};
343+
344+
let idx = 256 - depth as usize;
345+
match rpc_client.get_block_hash_by_number(target_block_number)? {
346+
Some(hash) => {
347+
block_hashes[idx] = U256::from_be_bytes(hash.0);
348+
loaded += 1;
349+
}
350+
None => {
351+
println!(
352+
"RPC returned null for historical block #{target_block_number}; leaving remaining block hashes as zeroes"
353+
);
354+
break;
355+
}
356+
}
357+
}
358+
359+
println!("Fetched {} historical block hashes from RPC", loaded);
360+
Ok(block_hashes)
361+
}
362+
363+
fn encode_historical_block_hashes(hashes: &[U256; 256]) -> Vec<String> {
364+
hashes
365+
.iter()
366+
.map(|hash| hex::encode_prefixed(hash.to_be_bytes::<32>()))
367+
.collect()
368+
}
369+
370+
fn decode_historical_block_hashes(raw_hashes: &[String]) -> Result<[U256; 256]> {
371+
if raw_hashes.len() != 256 {
372+
return Err(anyhow::anyhow!(
373+
"expected 256 historical block hashes, got {}",
374+
raw_hashes.len()
375+
));
376+
}
377+
378+
let mut hashes = [U256::ZERO; 256];
379+
for (idx, raw_hash) in raw_hashes.iter().enumerate() {
380+
hashes[idx] = U256::from_be_bytes(decode_fixed_hex::<32>(raw_hash)?);
381+
}
382+
Ok(hashes)
383+
}

0 commit comments

Comments
 (0)