Skip to content

Commit 2e3c624

Browse files
feat(l2): add ethrex_batchNumber to RPC (#4349)
**Motivation** <!-- Why does this pull request exist? What are its goals? --> We lack a way to get the latest batch sealed, something similar to `eth_blockNumber`. **Description** <!-- A clear and concise general description of the changes this PR introduces --> Add a new RPC method `ethrex_batchNumber` that returns the last sealed batch. How to test: 1. Start an L2 node: ``` COMPILE_CONTRACTS=true cargo run --release -- l2 --dev ``` 3. Wait about 1 minute until a batch is sealed. 4. Request the last batch number: ``` curl localhost:1729 -H 'content-type: application/json' -d '{"jsonrpc": "2.0", "id": "1", "method": "ethrex_batchNumber"}' ``` Should return something like `{"id":"1","jsonrpc":"2.0","result":"0x2"}` <!-- Link to issues: Resolves #111, Resolves #222 --> --------- Co-authored-by: Copilot <[email protected]>
1 parent 5e527c3 commit 2e3c624

File tree

6 files changed

+45
-2
lines changed

6 files changed

+45
-2
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,27 @@ impl RpcHandler for GetBatchByBatchNumberRequest {
100100
serde_json::to_value(&rpc_batch).map_err(|error| RpcErr::Internal(error.to_string()))
101101
}
102102
}
103+
104+
pub struct BatchNumberRequest {}
105+
106+
impl RpcHandler for BatchNumberRequest {
107+
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
108+
if params.as_ref().is_some_and(|params| !params.is_empty()) {
109+
return Err(ethrex_rpc::RpcErr::BadParams(
110+
"Expected 0 params".to_owned(),
111+
))?;
112+
};
113+
114+
Ok(BatchNumberRequest {})
115+
}
116+
117+
async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
118+
debug!("Requested last batch number");
119+
let Some(batch_number) = context.rollup_store.get_batch_number().await? else {
120+
return Ok(Value::Null);
121+
};
122+
123+
serde_json::to_value(format!("{batch_number:#x}"))
124+
.map_err(|error| RpcErr::Internal(error.to_string()))
125+
}
126+
}

crates/l2/networking/rpc/rpc.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::l2::batch::GetBatchByBatchNumberRequest;
1+
use crate::l2::batch::{BatchNumberRequest, GetBatchByBatchNumberRequest};
22
use crate::l2::l1_message::GetL1MessageProof;
33
use crate::utils::{RpcErr, RpcNamespace, resolve_namespace};
44
use axum::extract::State;
@@ -211,6 +211,7 @@ pub async fn map_l2_requests(req: &RpcRequest, context: RpcApiContext) -> Result
211211
match req.method.as_str() {
212212
"ethrex_sendTransaction" => SponsoredTx::call(req, context).await,
213213
"ethrex_getMessageProof" => GetL1MessageProof::call(req, context).await,
214+
"ethrex_batchNumber" => BatchNumberRequest::call(req, context).await,
214215
"ethrex_getBatchByNumber" => GetBatchByBatchNumberRequest::call(req, context).await,
215216
unknown_ethrex_l2_method => {
216217
Err(ethrex_rpc::RpcErr::MethodNotFound(unknown_ethrex_l2_method.to_owned()).into())

crates/l2/storage/src/api.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ pub trait StoreEngineRollup: Debug + Send + Sync {
6060

6161
async fn seal_batch(&self, batch: Batch) -> Result<(), RollupStoreError>;
6262

63+
async fn get_last_batch_number(&self) -> Result<Option<u64>, RollupStoreError>;
64+
6365
async fn get_verify_tx_by_batch(
6466
&self,
6567
batch_number: u64,

crates/l2/storage/src/store.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ impl Store {
150150
.await
151151
}
152152

153+
pub async fn get_batch_number(&self) -> Result<Option<u64>, RollupStoreError> {
154+
self.engine.get_last_batch_number().await
155+
}
156+
153157
pub async fn get_batch(&self, batch_number: u64) -> Result<Option<Batch>, RollupStoreError> {
154158
let Some(blocks) = self.get_block_numbers_by_batch(batch_number).await? else {
155159
return Ok(None);

crates/l2/storage/src/store_db/in_memory.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,10 @@ impl StoreEngineRollup for Store {
347347
inner.batch_proofs.remove(&(proof_type, batch_number));
348348
Ok(())
349349
}
350+
351+
async fn get_last_batch_number(&self) -> Result<Option<u64>, RollupStoreError> {
352+
Ok(self.inner()?.state_roots.keys().max().cloned())
353+
}
350354
}
351355

352356
impl Debug for Store {

crates/l2/storage/src/store_db/sql.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ impl StoreEngineRollup for SQLStore {
483483
messages_inc: u64,
484484
) -> Result<(), RollupStoreError> {
485485
self.execute(
486-
"UPDATE operation_count SET transactions = transactions + ?1, privileged_transactions = privileged_transactions + ?2, messages = messages + ?3",
486+
"UPDATE operation_count SET transactions = transactions + ?1, privileged_transactions = privileged_transactions + ?2, messages = messages + ?3",
487487
(transaction_inc, privileged_transactions_inc, messages_inc)).await?;
488488
Ok(())
489489
}
@@ -798,6 +798,14 @@ impl StoreEngineRollup for SQLStore {
798798
}
799799
self.execute_in_tx(queries, None).await
800800
}
801+
802+
async fn get_last_batch_number(&self) -> Result<Option<u64>, RollupStoreError> {
803+
let mut rows = self.query("SELECT MAX(batch) FROM state_roots", ()).await?;
804+
rows.next()
805+
.await?
806+
.map(|row| read_from_row_int(&row, 0))
807+
.transpose()
808+
}
801809
}
802810

803811
#[cfg(test)]

0 commit comments

Comments
 (0)