Skip to content

Commit 357655f

Browse files
authored
feat: add block payload size validation (#287)
* feat: payloadBuilder add block da size validation * fix: ci * fix: test * add SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT * change block_da_size_limit to Option<u64>
1 parent 18bff3a commit 357655f

File tree

3 files changed

+101
-19
lines changed

3 files changed

+101
-19
lines changed

crates/scroll/node/src/builder/payload.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,23 @@ pub struct ScrollPayloadBuilderBuilder<Txs = ()> {
1919
pub best_transactions: Txs,
2020
/// The payload building time limit.
2121
pub payload_building_time_limit: Duration,
22+
/// The block DA size limit.
23+
pub block_da_size_limit: Option<u64>,
2224
}
2325

2426
impl Default for ScrollPayloadBuilderBuilder {
2527
fn default() -> Self {
2628
Self {
2729
best_transactions: (),
2830
payload_building_time_limit: SCROLL_PAYLOAD_BUILDING_DURATION,
31+
block_da_size_limit: Some(SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT),
2932
}
3033
}
3134
}
3235

3336
const SCROLL_GAS_LIMIT: u64 = 20_000_000;
3437
const SCROLL_PAYLOAD_BUILDING_DURATION: Duration = Duration::from_secs(1);
38+
const SCROLL_DEFAULT_PAYLOAD_SIZE_LIMIT: u64 = 122_880;
3539

3640
impl<Txs> ScrollPayloadBuilderBuilder<Txs> {
3741
/// A helper method to initialize [`reth_scroll_payload::ScrollPayloadBuilder`] with the
@@ -65,7 +69,11 @@ impl<Txs> ScrollPayloadBuilderBuilder<Txs> {
6569
pool,
6670
evm_config,
6771
ctx.provider().clone(),
68-
ScrollBuilderConfig::new(gas_limit, self.payload_building_time_limit),
72+
ScrollBuilderConfig::new(
73+
gas_limit,
74+
self.payload_building_time_limit,
75+
self.block_da_size_limit,
76+
),
6977
)
7078
.with_transactions(self.best_transactions);
7179

crates/scroll/payload/src/builder.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,15 @@ impl<Txs> ScrollBuilder<'_, Txs> {
259259
// 3. if mem pool transactions are requested we execute them
260260
if !ctx.attributes().no_tx_pool {
261261
let best_txs = best(ctx.best_transaction_attributes(builder.evm_mut().block()));
262-
if ctx.execute_best_transactions(&mut info, &mut builder, best_txs, breaker)?.is_some()
262+
if ctx
263+
.execute_best_transactions(
264+
&mut info,
265+
&mut builder,
266+
best_txs,
267+
builder_config,
268+
breaker,
269+
)?
270+
.is_some()
263271
{
264272
return Ok(BuildOutcomeKind::Cancelled);
265273
}
@@ -475,14 +483,16 @@ where
475483
mut best_txs: impl PayloadTransactions<
476484
Transaction: PoolTransaction<Consensus = TxTy<Evm::Primitives>>,
477485
>,
486+
builder_config: &ScrollBuilderConfig,
478487
breaker: PayloadBuildingBreaker,
479488
) -> Result<Option<()>, PayloadBuilderError> {
480489
let block_gas_limit = builder.evm_mut().block().gas_limit;
481490
let base_fee = builder.evm_mut().block().basefee;
482491

483492
while let Some(tx) = best_txs.next(()) {
484493
let tx = tx.into_consensus();
485-
if info.is_tx_over_limits(tx.inner(), block_gas_limit) {
494+
if info.is_tx_over_limits(tx.inner(), block_gas_limit, builder_config.max_da_block_size)
495+
{
486496
// we can't fit this transaction into the block, so we need to mark it as
487497
// invalid which also removes all dependent transaction from
488498
// the iterator before we can continue
@@ -502,7 +512,7 @@ where
502512
}
503513

504514
// check if the execution needs to be halted.
505-
if breaker.should_break(info.cumulative_gas_used) {
515+
if breaker.should_break(info.cumulative_gas_used, info.cumulative_da_bytes_used) {
506516
tracing::trace!(target: "scroll::payload_builder", ?info, "breaking execution loop");
507517
return Ok(None);
508518
}
@@ -582,7 +592,14 @@ impl ExecutionInfo {
582592
&self,
583593
tx: &(impl Encodable + Transaction),
584594
block_gas_limit: u64,
595+
block_data_limit: Option<u64>,
585596
) -> bool {
597+
if block_data_limit
598+
.is_some_and(|da_limit| self.cumulative_da_bytes_used + tx.length() as u64 > da_limit)
599+
{
600+
return true;
601+
}
602+
586603
self.cumulative_gas_used + tx.gas_limit() > block_gas_limit
587604
}
588605
}

crates/scroll/payload/src/config.rs

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@ pub struct ScrollBuilderConfig {
1111
pub gas_limit: u64,
1212
/// Time limit for payload building.
1313
pub time_limit: Duration,
14+
/// Maximum total data availability size for a block.
15+
pub max_da_block_size: Option<u64>,
1416
}
1517

18+
/// Minimal data bytes size per transaction.
19+
pub const MIN_TRANSACTION_DATA_SIZE: u64 = 115u64;
20+
1621
impl ScrollBuilderConfig {
1722
/// Returns a new instance of [`ScrollBuilderConfig`].
18-
pub const fn new(gas_limit: u64, time_limit: Duration) -> Self {
19-
Self { gas_limit, time_limit }
23+
pub const fn new(gas_limit: u64, time_limit: Duration, max_da_block_size: Option<u64>) -> Self {
24+
Self { gas_limit, time_limit, max_da_block_size }
2025
}
2126

2227
/// Returns the [`PayloadBuildingBreaker`] for the config.
2328
pub(super) fn breaker(&self) -> PayloadBuildingBreaker {
24-
PayloadBuildingBreaker::new(self.time_limit, self.gas_limit)
29+
PayloadBuildingBreaker::new(self.time_limit, self.gas_limit, self.max_da_block_size)
2530
}
2631
}
2732

@@ -31,18 +36,39 @@ pub struct PayloadBuildingBreaker {
3136
start: Instant,
3237
time_limit: Duration,
3338
gas_limit: u64,
39+
max_da_block_size: Option<u64>,
3440
}
3541

3642
impl PayloadBuildingBreaker {
3743
/// Returns a new instance of the [`PayloadBuildingBreaker`].
38-
fn new(time_limit: Duration, gas_limit: u64) -> Self {
39-
Self { start: Instant::now(), time_limit, gas_limit }
44+
fn new(time_limit: Duration, gas_limit: u64, max_da_block_size: Option<u64>) -> Self {
45+
Self { start: Instant::now(), time_limit, gas_limit, max_da_block_size }
4046
}
4147

4248
/// Returns whether the payload building should stop.
43-
pub(super) fn should_break(&self, cumulative_gas_used: u64) -> bool {
44-
self.start.elapsed() >= self.time_limit ||
45-
cumulative_gas_used > self.gas_limit.saturating_sub(MIN_TRANSACTION_GAS)
49+
pub(super) fn should_break(
50+
&self,
51+
cumulative_gas_used: u64,
52+
cumulative_da_size_used: u64,
53+
) -> bool {
54+
// Check time limit
55+
if self.start.elapsed() >= self.time_limit {
56+
return true;
57+
}
58+
59+
// Check gas limit
60+
if cumulative_gas_used > self.gas_limit.saturating_sub(MIN_TRANSACTION_GAS) {
61+
return true;
62+
}
63+
64+
// Check data availability size limit if configured
65+
if let Some(max_size) = self.max_da_block_size {
66+
if cumulative_da_size_used > max_size.saturating_sub(MIN_TRANSACTION_DATA_SIZE) {
67+
return true;
68+
}
69+
}
70+
71+
false
4672
}
4773
}
4874

@@ -52,17 +78,48 @@ mod tests {
5278

5379
#[test]
5480
fn test_should_break_on_time_limit() {
55-
let breaker =
56-
PayloadBuildingBreaker::new(Duration::from_millis(200), 2 * MIN_TRANSACTION_GAS);
57-
assert!(!breaker.should_break(MIN_TRANSACTION_GAS));
81+
let breaker = PayloadBuildingBreaker::new(
82+
Duration::from_millis(200),
83+
2 * MIN_TRANSACTION_GAS,
84+
Some(2 * MIN_TRANSACTION_DATA_SIZE),
85+
);
86+
assert!(!breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE));
5887
std::thread::sleep(Duration::from_millis(201));
59-
assert!(breaker.should_break(MIN_TRANSACTION_GAS));
88+
assert!(breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE));
6089
}
6190

6291
#[test]
6392
fn test_should_break_on_gas_limit() {
64-
let breaker = PayloadBuildingBreaker::new(Duration::from_secs(1), 2 * MIN_TRANSACTION_GAS);
65-
assert!(!breaker.should_break(MIN_TRANSACTION_GAS));
66-
assert!(breaker.should_break(MIN_TRANSACTION_GAS + 1));
93+
let breaker = PayloadBuildingBreaker::new(
94+
Duration::from_secs(1),
95+
2 * MIN_TRANSACTION_GAS,
96+
Some(2 * MIN_TRANSACTION_DATA_SIZE),
97+
);
98+
assert!(!breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE));
99+
assert!(breaker.should_break(MIN_TRANSACTION_GAS + 1, MIN_TRANSACTION_DATA_SIZE));
100+
}
101+
102+
#[test]
103+
fn test_should_break_on_data_size_limit() {
104+
let breaker = PayloadBuildingBreaker::new(
105+
Duration::from_secs(1),
106+
2 * MIN_TRANSACTION_GAS,
107+
Some(2 * MIN_TRANSACTION_DATA_SIZE),
108+
);
109+
assert!(!breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE));
110+
assert!(breaker.should_break(MIN_TRANSACTION_GAS, MIN_TRANSACTION_DATA_SIZE + 1));
111+
}
112+
113+
#[test]
114+
fn test_should_break_with_no_da_limit() {
115+
let breaker = PayloadBuildingBreaker::new(
116+
Duration::from_secs(1),
117+
2 * MIN_TRANSACTION_GAS,
118+
None, // No DA limit
119+
);
120+
// Should not break on large DA size when no limit is set
121+
assert!(!breaker.should_break(MIN_TRANSACTION_GAS, u64::MAX));
122+
// But should still break on gas limit
123+
assert!(breaker.should_break(MIN_TRANSACTION_GAS + 1, u64::MAX));
67124
}
68125
}

0 commit comments

Comments
 (0)