Skip to content

Commit 8c1812c

Browse files
avilagaston9MegaRedHandjrchatrucCopilotdamiramirez
authored
feat(l2): implement batch endpoint (#3374)
**Motivation** For debugging purposes, it's useful to have an `ethrex_getBatchByNumber` endpoint that returns a `Batch` struct: ```Rust pub struct Batch { pub number: u64, pub first_block: u64, pub last_block: u64, pub state_root: H256, pub deposit_logs_hash: H256, pub message_hashes: Vec<H256>, pub blobs_bundle: BlobsBundle, pub commit_tx: Option<H256>, pub verify_tx: Option<H256>, } ``` **Description** - Modifies the `Batch` struct to incude `commit_tx` and `verify_tx`. - Updates `block_fetcher` to process verify tx logs and extract the verify tx hashes as well. - Fixes a bug found during development: the `rollup_storage::getBatch()` function incorrectly treated batches without `L1Messages` as an error. ## How to test You can run: ```bash curl -X POST http://localhost:1729 \ -H "Content-Type: application/json" \ -d '{ "jsonrpc":"2.0", "method":"ethrex_getBatchByNumber", "params": ["0x1", true], "id":1 }' ``` Closes None --------- Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com> Co-authored-by: Javier Rodríguez Chatruc <49622509+jrchatruc@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Damian Ramirez <damian.ramirez@lambdaclass.com>
1 parent 416581d commit 8c1812c

File tree

15 files changed

+494
-35
lines changed

15 files changed

+494
-35
lines changed

cmd/ethrex/l2/command.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ impl Command {
444444
deposit_logs_hash: H256::zero(),
445445
message_hashes,
446446
blobs_bundle: BlobsBundle::empty(),
447+
commit_tx: None,
448+
verify_tx: None,
447449
};
448450

449451
// Store batch info in L2 storage

crates/common/types/batch.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1+
use serde::Serialize;
2+
13
use crate::H256;
24

35
use super::BlobsBundle;
46

5-
#[derive(Clone)]
7+
#[derive(Clone, Serialize)]
68
pub struct Batch {
79
pub number: u64,
810
pub first_block: u64,
911
pub last_block: u64,
1012
pub state_root: H256,
1113
pub deposit_logs_hash: H256,
1214
pub message_hashes: Vec<H256>,
15+
#[serde(skip_serializing)]
1316
pub blobs_bundle: BlobsBundle,
17+
pub commit_tx: Option<H256>,
18+
pub verify_tx: Option<H256>,
1419
}

crates/l2/based/block_fetcher.rs

Lines changed: 97 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -202,44 +202,27 @@ async fn fetch(state: &mut BlockFetcherState) -> Result<(), BlockFetcherError> {
202202
"Node is {l2_batches_behind} batches behind. Last batch number known: {last_l2_batch_number_known}, last committed batch number: {last_l2_committed_batch_number}"
203203
);
204204

205-
let batch_committed_logs = get_logs(state).await?;
205+
let (batch_committed_logs, batch_verified_logs) = get_logs(state).await?;
206206

207-
let mut missing_batches_logs =
208-
filter_logs(&batch_committed_logs, last_l2_batch_number_known).await?;
209-
210-
missing_batches_logs.sort_by_key(|(_log, batch_number)| *batch_number);
211-
212-
for (batch_committed_log, batch_number) in missing_batches_logs {
213-
let batch_commit_tx_calldata = state
214-
.eth_client
215-
.get_transaction_by_hash(batch_committed_log.transaction_hash)
216-
.await?
217-
.ok_or(BlockFetcherError::InternalError(format!(
218-
"Failed to get the receipt for transaction {:x}",
219-
batch_committed_log.transaction_hash
220-
)))?
221-
.data;
222-
223-
let batch = decode_batch_from_calldata(&batch_commit_tx_calldata)?;
224-
225-
store_batch(state, &batch).await?;
226-
227-
seal_batch(state, &batch, batch_number).await?;
228-
}
207+
process_committed_logs(batch_committed_logs, state, last_l2_batch_number_known).await?;
208+
process_verified_logs(batch_verified_logs, state).await?;
229209
}
230210

231211
info!("Node is up to date");
232212

233213
Ok(())
234214
}
235215

236-
/// Fetch logs from the L1 chain for the BatchCommitted event.
216+
/// Fetch logs from the L1 chain for the BatchCommitted and BatchVerified events.
237217
/// This function fetches logs, starting from the last fetched block number (aka the last block that was processed)
238218
/// and going up to the current block number.
239-
async fn get_logs(state: &mut BlockFetcherState) -> Result<Vec<RpcLog>, BlockFetcherError> {
219+
async fn get_logs(
220+
state: &mut BlockFetcherState,
221+
) -> Result<(Vec<RpcLog>, Vec<RpcLog>), BlockFetcherError> {
240222
let last_l1_block_number = state.eth_client.get_block_number().await?;
241223

242224
let mut batch_committed_logs = Vec::new();
225+
let mut batch_verified_logs = Vec::new();
243226
while state.last_l1_block_fetched < last_l1_block_number {
244227
let new_last_l1_fetched_block = min(
245228
state.last_l1_block_fetched + state.fetch_block_step,
@@ -253,7 +236,7 @@ async fn get_logs(state: &mut BlockFetcherState) -> Result<Vec<RpcLog>, BlockFet
253236
);
254237

255238
// Fetch logs from the L1 chain for the BatchCommitted event.
256-
let logs = state
239+
let committed_logs = state
257240
.eth_client
258241
.get_logs(
259242
state.last_l1_block_fetched + 1,
@@ -263,13 +246,64 @@ async fn get_logs(state: &mut BlockFetcherState) -> Result<Vec<RpcLog>, BlockFet
263246
)
264247
.await?;
265248

249+
// Fetch logs from the L1 chain for the BatchVerified event.
250+
let verified_logs = state
251+
.eth_client
252+
.get_logs(
253+
state.last_l1_block_fetched + 1,
254+
new_last_l1_fetched_block,
255+
state.on_chain_proposer_address,
256+
keccak(b"BatchVerified(uint256)"),
257+
)
258+
.await?;
259+
266260
// Update the last L1 block fetched.
267261
state.last_l1_block_fetched = new_last_l1_fetched_block;
268262

269-
batch_committed_logs.extend_from_slice(&logs);
263+
batch_committed_logs.extend_from_slice(&committed_logs);
264+
batch_verified_logs.extend_from_slice(&verified_logs);
270265
}
271266

272-
Ok(batch_committed_logs)
267+
Ok((batch_committed_logs, batch_verified_logs))
268+
}
269+
270+
/// Process the logs from the event `BatchCommitted`.
271+
/// Gets the committed batches that are missing in the local store from the logs,
272+
/// and seals the batch in the rollup store.
273+
async fn process_committed_logs(
274+
batch_committed_logs: Vec<RpcLog>,
275+
state: &mut BlockFetcherState,
276+
last_l2_batch_number_known: u64,
277+
) -> Result<(), BlockFetcherError> {
278+
let mut missing_batches_logs =
279+
filter_logs(&batch_committed_logs, last_l2_batch_number_known).await?;
280+
281+
missing_batches_logs.sort_by_key(|(_log, batch_number)| *batch_number);
282+
283+
for (batch_committed_log, batch_number) in missing_batches_logs {
284+
let batch_commit_tx_calldata = state
285+
.eth_client
286+
.get_transaction_by_hash(batch_committed_log.transaction_hash)
287+
.await?
288+
.ok_or(BlockFetcherError::InternalError(format!(
289+
"Failed to get the receipt for transaction {:x}",
290+
batch_committed_log.transaction_hash
291+
)))?
292+
.data;
293+
294+
let batch = decode_batch_from_calldata(&batch_commit_tx_calldata)?;
295+
296+
store_batch(state, &batch).await?;
297+
298+
seal_batch(
299+
state,
300+
&batch,
301+
batch_number,
302+
batch_committed_log.transaction_hash,
303+
)
304+
.await?;
305+
}
306+
Ok(())
273307
}
274308

275309
/// Given the logs from the event `BatchCommitted`,
@@ -390,8 +424,9 @@ async fn seal_batch(
390424
state: &mut BlockFetcherState,
391425
batch: &[Block],
392426
batch_number: U256,
427+
commit_tx: H256,
393428
) -> Result<(), BlockFetcherError> {
394-
let batch = get_batch(state, batch, batch_number).await?;
429+
let batch = get_batch(state, batch, batch_number, commit_tx).await?;
395430

396431
state.rollup_store.seal_batch(batch).await?;
397432

@@ -452,6 +487,7 @@ async fn get_batch(
452487
state: &mut BlockFetcherState,
453488
batch: &[Block],
454489
batch_number: U256,
490+
commit_tx: H256,
455491
) -> Result<Batch, BlockFetcherError> {
456492
let deposits: Vec<PrivilegedL2Transaction> = batch
457493
.iter()
@@ -538,5 +574,37 @@ async fn get_batch(
538574
deposit_logs_hash,
539575
message_hashes: get_batch_message_hashes(state, batch).await?,
540576
blobs_bundle,
577+
commit_tx: Some(commit_tx),
578+
verify_tx: None,
541579
})
542580
}
581+
582+
/// Process the logs from the event `BatchVerified`.
583+
/// Gets the batch number from the logs and stores the verify transaction hash in the rollup store
584+
async fn process_verified_logs(
585+
batch_verified_logs: Vec<RpcLog>,
586+
state: &mut BlockFetcherState,
587+
) -> Result<(), BlockFetcherError> {
588+
for batch_verified_log in batch_verified_logs {
589+
let batch_number = U256::from_big_endian(
590+
batch_verified_log
591+
.log
592+
.topics
593+
.get(1)
594+
.ok_or(BlockFetcherError::InternalError(
595+
"Failed to get verified batch number from BatchVerified log".to_string(),
596+
))?
597+
.as_bytes(),
598+
);
599+
600+
let verify_tx_hash = batch_verified_log.transaction_hash;
601+
602+
state
603+
.rollup_store
604+
.store_verify_tx_by_batch(batch_number.as_u64(), verify_tx_hash)
605+
.await?;
606+
607+
info!("Stored verify transaction hash {verify_tx_hash:#x} for batch {batch_number}");
608+
}
609+
Ok(())
610+
}

crates/l2/networking/rpc/l2/batch.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use ethrex_common::types::{BlockHash, batch::Batch};
2+
use ethrex_storage::Store;
3+
use serde::Serialize;
4+
use serde_json::Value;
5+
use tracing::info;
6+
7+
use crate::{
8+
rpc::{RpcApiContext, RpcHandler},
9+
utils::RpcErr,
10+
};
11+
12+
#[derive(Serialize)]
13+
pub struct RpcBatch {
14+
#[serde(flatten)]
15+
pub batch: Batch,
16+
#[serde(skip_serializing_if = "Option::is_none")]
17+
pub block_hashes: Option<Vec<BlockHash>>,
18+
}
19+
20+
impl RpcBatch {
21+
pub async fn build(batch: Batch, block_hashes: bool, store: &Store) -> Result<Self, RpcErr> {
22+
let block_hashes = if block_hashes {
23+
Some(get_block_hashes(
24+
batch.first_block,
25+
batch.last_block,
26+
store,
27+
)?)
28+
} else {
29+
None
30+
};
31+
32+
Ok(RpcBatch {
33+
batch,
34+
block_hashes,
35+
})
36+
}
37+
}
38+
39+
fn get_block_hashes(
40+
first_block: u64,
41+
last_block: u64,
42+
store: &Store,
43+
) -> Result<Vec<BlockHash>, RpcErr> {
44+
let mut block_hashes = Vec::new();
45+
for block_number in first_block..=last_block {
46+
let header = store
47+
.get_block_header(block_number)?
48+
.ok_or(RpcErr::Internal(format!(
49+
"Failed to retrieve block header for block number {block_number}"
50+
)))?;
51+
let hash = header.hash();
52+
block_hashes.push(hash);
53+
}
54+
Ok(block_hashes)
55+
}
56+
57+
pub struct GetBatchByBatchNumberRequest {
58+
pub batch_number: u64,
59+
pub block_hashes: bool,
60+
}
61+
62+
impl RpcHandler for GetBatchByBatchNumberRequest {
63+
fn parse(params: &Option<Vec<Value>>) -> Result<GetBatchByBatchNumberRequest, RpcErr> {
64+
let params = params.as_ref().ok_or(ethrex_rpc::RpcErr::BadParams(
65+
"No params provided".to_owned(),
66+
))?;
67+
if params.len() != 2 {
68+
return Err(ethrex_rpc::RpcErr::BadParams(
69+
"Expected 2 params".to_owned(),
70+
))?;
71+
};
72+
// Parse BatchNumber
73+
let hex_str = serde_json::from_value::<String>(params[0].clone())
74+
.map_err(|e| ethrex_rpc::RpcErr::BadParams(e.to_string()))?;
75+
76+
// Check that the BatchNumber is 0x prefixed
77+
let hex_str = hex_str
78+
.strip_prefix("0x")
79+
.ok_or(ethrex_rpc::RpcErr::BadHexFormat(0))?;
80+
81+
// Parse hex string
82+
let batch_number =
83+
u64::from_str_radix(hex_str, 16).map_err(|_| ethrex_rpc::RpcErr::BadHexFormat(0))?;
84+
85+
let block_hashes = serde_json::from_value(params[1].clone())?;
86+
87+
Ok(GetBatchByBatchNumberRequest {
88+
batch_number,
89+
block_hashes,
90+
})
91+
}
92+
93+
async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
94+
info!("Requested batch with number: {}", self.batch_number);
95+
let Some(batch) = context.rollup_store.get_batch(self.batch_number).await? else {
96+
return Ok(Value::Null);
97+
};
98+
let rpc_batch = RpcBatch::build(batch, self.block_hashes, &context.l1_ctx.storage).await?;
99+
100+
serde_json::to_value(&rpc_batch).map_err(|error| RpcErr::Internal(error.to_string()))
101+
}
102+
}

crates/l2/networking/rpc/l2/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pub mod batch;
12
pub mod l1_message;
23
pub mod transaction;

crates/l2/networking/rpc/rpc.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::l2::batch::GetBatchByBatchNumberRequest;
12
use crate::l2::l1_message::GetL1MessageProof;
23
use crate::utils::{RpcErr, RpcNamespace, resolve_namespace};
34
use axum::extract::State;
@@ -207,6 +208,7 @@ pub async fn map_l2_requests(req: &RpcRequest, context: RpcApiContext) -> Result
207208
match req.method.as_str() {
208209
"ethrex_sendTransaction" => SponsoredTx::call(req, context).await,
209210
"ethrex_getMessageProof" => GetL1MessageProof::call(req, context).await,
211+
"ethrex_getBatchByNumber" => GetBatchByBatchNumberRequest::call(req, context).await,
210212
unknown_ethrex_l2_method => {
211213
Err(ethrex_rpc::RpcErr::MethodNotFound(unknown_ethrex_l2_method.to_owned()).into())
212214
}

crates/l2/sequencer/l1_committer.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ async fn commit_next_batch_to_l1(state: &mut CommitterState) -> Result<(), Commi
219219
deposit_logs_hash,
220220
message_hashes,
221221
blobs_bundle,
222+
commit_tx: None,
223+
verify_tx: None,
222224
};
223225

224226
state.rollup_store.seal_batch(batch.clone()).await?;
@@ -257,6 +259,11 @@ async fn commit_next_batch_to_l1(state: &mut CommitterState) -> Result<(), Commi
257259
});
258260
);
259261

262+
state
263+
.rollup_store
264+
.store_commit_tx_by_batch(batch.number, commit_tx_hash)
265+
.await?;
266+
260267
info!(
261268
"Commitment sent for batch {}, with tx hash {commit_tx_hash:#x}.",
262269
batch.number

crates/l2/sequencer/l1_proof_sender.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@ pub async fn send_proof_to_contract(
343343
)
344344
.await?;
345345

346+
state
347+
.rollup_store
348+
.store_verify_tx_by_batch(batch_number, verify_tx_hash)
349+
.await?;
350+
346351
info!(
347352
?batch_number,
348353
?verify_tx_hash,

crates/l2/sequencer/l1_proof_verifier.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ impl L1ProofVerifier {
185185
)
186186
.await?;
187187

188+
// Store the verify transaction hash for each batch that was aggregated.
189+
for i in 0..aggregated_proofs_count {
190+
let batch_number = first_batch_number + i;
191+
self.rollup_store
192+
.store_verify_tx_by_batch(batch_number, verify_tx_hash)
193+
.await?;
194+
}
195+
188196
Ok(Some(verify_tx_hash))
189197
}
190198

0 commit comments

Comments
 (0)