Skip to content

Commit 09f6d73

Browse files
authored
feat: finalization (#56)
* feat: add batch to block table * feat: introduce `ScrollPayloadAttributesWithBatchInfo` * fix: watcher * feat: add batch to block operations in db * feat: return new finalized block with indexer events * chore: lints + doc strings * fix: integration tests + lints * trigger CI * fix: zepter * fix: answer comments * fix: answer comments
1 parent 4dd679c commit 09f6d73

24 files changed

+586
-86
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ alloy-transport = { version = "0.14.0", default-features = false }
133133
scroll-alloy-consensus = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
134134
scroll-alloy-network = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
135135
scroll-alloy-provider = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
136-
scroll-alloy-rpc-types-engine = { git = "https://github.com/scroll-tech/reth.git" }
136+
scroll-alloy-rpc-types-engine = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
137137

138138
# reth
139139
reth-e2e-test-utils = { git = "https://github.com/scroll-tech/reth.git" }

crates/database/db/src/db.rs

+110-1
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ mod test {
4949
models, operations::DatabaseOperations, test_utils::setup_test_db,
5050
DatabaseConnectionProvider,
5151
};
52+
use alloy_primitives::B256;
53+
use std::sync::Arc;
5254

5355
use arbitrary::{Arbitrary, Unstructured};
5456
use futures::StreamExt;
5557
use rand::Rng;
56-
use rollup_node_primitives::{BatchCommitData, L1MessageWithBlockNumber};
58+
use rollup_node_primitives::{BatchCommitData, BatchInfo, BlockInfo, L1MessageWithBlockNumber};
5759
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
5860

5961
#[tokio::test]
@@ -136,6 +138,113 @@ mod test {
136138
assert!(data.is_some());
137139
}
138140

141+
#[tokio::test]
142+
async fn test_database_batch_to_block_exists() {
143+
// Set up the test database.
144+
let db = setup_test_db().await;
145+
146+
// Generate unstructured bytes.
147+
let mut bytes = [0u8; 1024];
148+
rand::rng().fill(bytes.as_mut_slice());
149+
let mut u = Unstructured::new(&bytes);
150+
151+
// Generate randoms BatchInfo and BlockInfo with increasing block numbers.
152+
let mut block_number = 100;
153+
let data = BatchCommitData { index: 100, ..Arbitrary::arbitrary(&mut u).unwrap() };
154+
let batch_info = data.clone().into();
155+
db.insert_batch(data).await.unwrap();
156+
157+
for _ in 0..10 {
158+
let block_info =
159+
BlockInfo { number: block_number, hash: B256::arbitrary(&mut u).unwrap() };
160+
db.insert_batch_to_block(batch_info, block_info).await.unwrap();
161+
block_number += 1;
162+
}
163+
164+
// Fetch the highest block for the batch and verify number.
165+
let highest_block_info =
166+
db.get_highest_block_for_batch(batch_info.hash).await.unwrap().unwrap();
167+
assert_eq!(highest_block_info.number, block_number - 1);
168+
}
169+
170+
#[tokio::test]
171+
async fn test_database_batch_to_block_missing() {
172+
// Set up the test database.
173+
let db = setup_test_db().await;
174+
175+
// Generate unstructured bytes.
176+
let mut bytes = [0u8; 1024];
177+
rand::rng().fill(bytes.as_mut_slice());
178+
let mut u = Unstructured::new(&bytes);
179+
180+
// Generate randoms BatchInfo and BlockInfo with increasing block numbers.
181+
let mut block_number = 100;
182+
let first_batch = BatchCommitData { index: 100, ..Arbitrary::arbitrary(&mut u).unwrap() };
183+
let first_batch_info = first_batch.clone().into();
184+
185+
let second_batch = BatchCommitData { index: 250, ..Arbitrary::arbitrary(&mut u).unwrap() };
186+
let second_batch_info: BatchInfo = second_batch.clone().into();
187+
188+
db.insert_batch(first_batch).await.unwrap();
189+
db.insert_batch(second_batch).await.unwrap();
190+
191+
for _ in 0..10 {
192+
let block_info =
193+
BlockInfo { number: block_number, hash: B256::arbitrary(&mut u).unwrap() };
194+
db.insert_batch_to_block(first_batch_info, block_info).await.unwrap();
195+
block_number += 1;
196+
}
197+
198+
// Fetch the highest block for the batch and verify number.
199+
let highest_block_info =
200+
db.get_highest_block_for_batch(second_batch_info.hash).await.unwrap().unwrap();
201+
assert_eq!(highest_block_info.number, block_number - 1);
202+
}
203+
204+
#[tokio::test]
205+
async fn test_database_finalized_batch_hash_at_height() {
206+
// Set up the test database.
207+
let db = setup_test_db().await;
208+
209+
// Generate unstructured bytes.
210+
let mut bytes = [0u8; 2048];
211+
rand::rng().fill(bytes.as_mut_slice());
212+
let mut u = Unstructured::new(&bytes);
213+
214+
// Generate randoms BatchInfoCommitData, insert in database and finalize.
215+
let mut block_number = 100;
216+
let mut batch_index = 100;
217+
let mut highest_finalized_batch_hash = B256::ZERO;
218+
219+
for _ in 0..20 {
220+
let data = BatchCommitData {
221+
index: batch_index,
222+
calldata: Arc::new(vec![].into()),
223+
..Arbitrary::arbitrary(&mut u).unwrap()
224+
};
225+
let hash = data.hash;
226+
db.insert_batch(data).await.unwrap();
227+
228+
// save batch hash finalized at block number 109.
229+
if block_number == 109 {
230+
highest_finalized_batch_hash = hash;
231+
}
232+
233+
// Finalize batch up to block number 110.
234+
if block_number <= 110 {
235+
db.finalize_batch(hash, block_number).await.unwrap();
236+
}
237+
238+
block_number += 1;
239+
batch_index += 1;
240+
}
241+
242+
// Fetch the finalized batch for provided height and verify number.
243+
let highest_batch_hash_from_db =
244+
db.get_finalized_batch_hash_at_height(109).await.unwrap().unwrap();
245+
assert_eq!(highest_finalized_batch_hash, highest_batch_hash_from_db);
246+
}
247+
139248
#[tokio::test]
140249
async fn test_database_tx() {
141250
// Setup the test database.

crates/database/db/src/models/batch_commit.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use sea_orm::{entity::prelude::*, ActiveValue};
88
#[sea_orm(table_name = "batch_commit")]
99
pub struct Model {
1010
#[sea_orm(primary_key)]
11-
index: i64,
12-
hash: Vec<u8>,
11+
pub(crate) index: i64,
12+
pub(crate) hash: Vec<u8>,
1313
block_number: i64,
1414
block_timestamp: i64,
1515
calldata: Vec<u8>,
@@ -19,7 +19,17 @@ pub struct Model {
1919

2020
/// The relation for the batch input model.
2121
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
22-
pub enum Relation {}
22+
pub enum Relation {
23+
/// A one-to-many relation with the derived block table.
24+
#[sea_orm(has_many = "super::derived_block::Entity")]
25+
BatchToBlock,
26+
}
27+
28+
impl Related<super::derived_block::Entity> for Entity {
29+
fn to() -> RelationDef {
30+
Relation::BatchToBlock.def()
31+
}
32+
}
2333

2434
/// The active model behavior for the batch input model.
2535
impl ActiveModelBehavior for ActiveModel {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use alloy_primitives::B256;
2+
use rollup_node_primitives::{BatchInfo, BlockInfo};
3+
use sea_orm::{entity::prelude::*, ActiveValue};
4+
5+
/// A database model that represents a derived block.
6+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
7+
#[sea_orm(table_name = "derived_block")]
8+
pub struct Model {
9+
#[sea_orm(primary_key)]
10+
block_number: i64,
11+
block_hash: Vec<u8>,
12+
batch_index: i64,
13+
batch_hash: Vec<u8>,
14+
}
15+
16+
impl Model {
17+
pub(crate) fn block_info(&self) -> BlockInfo {
18+
BlockInfo { number: self.block_number as u64, hash: B256::from_slice(&self.block_hash) }
19+
}
20+
}
21+
22+
/// The relation for the batch input model.
23+
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
24+
pub enum Relation {
25+
/// A relation with the batch commit table, where column batch hash of the
26+
/// batch to block table belongs to the column hash of the batch commit
27+
/// table.
28+
#[sea_orm(
29+
belongs_to = "super::batch_commit::Entity",
30+
from = "Column::BatchHash",
31+
to = "super::batch_commit::Column::Hash"
32+
)]
33+
BatchCommit,
34+
}
35+
36+
impl Related<super::batch_commit::Entity> for Entity {
37+
fn to() -> RelationDef {
38+
Relation::BatchCommit.def()
39+
}
40+
}
41+
42+
/// The active model behavior for the batch input model.
43+
impl ActiveModelBehavior for ActiveModel {}
44+
45+
impl From<(BatchInfo, BlockInfo)> for ActiveModel {
46+
fn from((batch_info, block_info): (BatchInfo, BlockInfo)) -> Self {
47+
Self {
48+
batch_index: ActiveValue::Set(
49+
batch_info.index.try_into().expect("index should fit in i64"),
50+
),
51+
batch_hash: ActiveValue::Set(batch_info.hash.to_vec()),
52+
block_number: ActiveValue::Set(
53+
block_info.number.try_into().expect("block number should fit in i64"),
54+
),
55+
block_hash: ActiveValue::Set(block_info.hash.to_vec()),
56+
}
57+
}
58+
}

crates/database/db/src/models/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/// This module contains the batch commit database model.
22
pub mod batch_commit;
33

4+
/// This module contains the derived block model.
5+
pub mod derived_block;
6+
47
/// This module contains the block data database model.
58
pub mod block_data;
69

crates/database/db/src/operations.rs

+71-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ use crate::DatabaseConnectionProvider;
44
use alloy_eips::{BlockId, BlockNumberOrTag};
55
use alloy_primitives::B256;
66
use futures::{Stream, StreamExt};
7-
use rollup_node_primitives::{BatchCommitData, L1MessageWithBlockNumber};
7+
use rollup_node_primitives::{BatchCommitData, BatchInfo, BlockInfo, L1MessageWithBlockNumber};
88
use scroll_alloy_rpc_types_engine::BlockDataHint;
9-
use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter, Set};
9+
use sea_orm::{
10+
ActiveModelTrait, ColumnTrait, Condition, DbErr, EntityTrait, QueryFilter, QueryOrder,
11+
QuerySelect, Set,
12+
};
1013

1114
/// The [`DatabaseOperations`] trait provides methods for interacting with the database.
1215
#[async_trait::async_trait]
@@ -64,6 +67,26 @@ pub trait DatabaseOperations: DatabaseConnectionProvider {
6467
.map(|x| x.map(Into::into))?)
6568
}
6669

70+
/// Get the newest finalized batch hash up to or at the provided height.
71+
async fn get_finalized_batch_hash_at_height(
72+
&self,
73+
height: u64,
74+
) -> Result<Option<B256>, DatabaseError> {
75+
Ok(models::batch_commit::Entity::find()
76+
.filter(
77+
Condition::all()
78+
.add(models::batch_commit::Column::FinalizedBlockNumber.is_not_null())
79+
.add(models::batch_commit::Column::FinalizedBlockNumber.lte(height)),
80+
)
81+
.order_by_desc(models::batch_commit::Column::Index)
82+
.select_only()
83+
.column(models::batch_commit::Column::Hash)
84+
.into_tuple::<Vec<u8>>()
85+
.one(self.get_connection())
86+
.await
87+
.map(|x| x.map(|x| B256::from_slice(&x)))?)
88+
}
89+
6790
/// Delete all [`BatchCommitData`]s with a block number greater than the provided block number.
6891
async fn delete_batches_gt(&self, block_number: u64) -> Result<(), DatabaseError> {
6992
tracing::trace!(target: "scroll::db", block_number, "Deleting batch inputs greater than block number.");
@@ -146,6 +169,52 @@ pub trait DatabaseOperations: DatabaseConnectionProvider {
146169
.await
147170
.map(|x| x.map(Into::into))?)
148171
}
172+
173+
/// Insert a new batch to block line in the database.
174+
async fn insert_batch_to_block(
175+
&self,
176+
batch_info: BatchInfo,
177+
block_info: BlockInfo,
178+
) -> Result<(), DatabaseError> {
179+
tracing::trace!(
180+
target: "scroll::db",
181+
batch_hash = ?batch_info.hash,
182+
batch_index = batch_info.index,
183+
block_number = block_info.number,
184+
block_hash = ?block_info.hash,
185+
"Inserting batch to block into database."
186+
);
187+
let derived_block: models::derived_block::ActiveModel = (batch_info, block_info).into();
188+
derived_block.insert(self.get_connection()).await?;
189+
190+
Ok(())
191+
}
192+
193+
/// Returns the highest L2 block originating from the provided batch_hash or the highest block
194+
/// for the batch's index.
195+
async fn get_highest_block_for_batch(
196+
&self,
197+
batch_hash: B256,
198+
) -> Result<Option<BlockInfo>, DatabaseError> {
199+
let index = models::batch_commit::Entity::find()
200+
.filter(models::batch_commit::Column::Hash.eq(batch_hash.to_vec()))
201+
.select_only()
202+
.column(models::batch_commit::Column::Index)
203+
.into_tuple::<i32>()
204+
.one(self.get_connection())
205+
.await?;
206+
207+
if let Some(index) = index {
208+
Ok(models::derived_block::Entity::find()
209+
.filter(models::derived_block::Column::BatchIndex.lte(index))
210+
.order_by_desc(models::derived_block::Column::BlockNumber)
211+
.one(self.get_connection())
212+
.await?
213+
.map(|model| model.block_info()))
214+
} else {
215+
Ok(None)
216+
}
217+
}
149218
}
150219

151220
impl<T> DatabaseOperations for T where T: DatabaseConnectionProvider {}

crates/database/migration/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod m20220101_000001_create_batch_commit_table;
44
mod m20250304_125946_add_l1_msg_table;
55
mod m20250408_132123_add_block_data_table;
66
mod m20250408_150338_seed_block_data_table;
7+
mod m20250411_072004_add_derived_block;
78

89
pub struct Migrator;
910

@@ -15,6 +16,7 @@ impl MigratorTrait for Migrator {
1516
Box::new(m20250304_125946_add_l1_msg_table::Migration),
1617
Box::new(m20250408_132123_add_block_data_table::Migration),
1718
Box::new(m20250408_150338_seed_block_data_table::Migration),
19+
Box::new(m20250411_072004_add_derived_block::Migration),
1820
]
1921
}
2022
}

crates/database/migration/src/m20220101_000001_create_batch_commit_table.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ impl MigrationTrait for Migration {
1515
.table(BatchCommit::Table)
1616
.if_not_exists()
1717
.col(pk_auto(BatchCommit::Index))
18-
.col(binary_len(BatchCommit::Hash, HASH_LENGTH))
18+
.col(binary_len(BatchCommit::Hash, HASH_LENGTH).unique_key())
1919
.col(big_unsigned(BatchCommit::BlockNumber))
2020
.col(big_unsigned(BatchCommit::BlockTimestamp))
2121
.col(binary(BatchCommit::Calldata))
@@ -32,7 +32,7 @@ impl MigrationTrait for Migration {
3232
}
3333

3434
#[derive(DeriveIden)]
35-
enum BatchCommit {
35+
pub(crate) enum BatchCommit {
3636
Table,
3737
Index,
3838
Hash,

0 commit comments

Comments
 (0)