Skip to content

Commit a91ef9f

Browse files
committed
Implement gradual gas limit increase
1 parent 079764e commit a91ef9f

File tree

2 files changed

+109
-10
lines changed

2 files changed

+109
-10
lines changed

crates/scroll/payload/src/builder.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Scroll's payload builder implementation.
22
33
use super::ScrollPayloadBuilderError;
4-
use crate::config::{PayloadBuildingBreaker, ScrollBuilderConfig};
4+
use crate::config::{calculate_block_gas_limit, PayloadBuildingBreaker, ScrollBuilderConfig};
55

66
use alloy_consensus::{Transaction, Typed2718};
77
use alloy_primitives::U256;
@@ -239,12 +239,18 @@ impl<Txs> ScrollBuilder<'_, Txs> {
239239
{
240240
let Self { best } = self;
241241
tracing::debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload");
242-
let breaker = builder_config.breaker();
243-
244242
let mut db = State::builder().with_database(db).with_bundle_update().build();
245243

246244
let mut builder = ctx.block_builder(&mut db, builder_config)?;
247245

246+
// Create the breaker with the actual block gas limit (after clamping based on parent).
247+
// The configured gas_limit may differ from the actual block gas limit because it gets
248+
// clamped to not exceed parent_gas_limit ± parent_gas_limit/1024. Using the actual
249+
// block gas limit ensures the breaker exits the transaction loop at the right time,
250+
// avoiding wasted work trying to add transactions that won't fit.
251+
let block_gas_limit = builder.evm().block().gas_limit();
252+
let breaker = builder_config.breaker_with_gas_limit(block_gas_limit);
253+
248254
// 1. apply pre-execution changes
249255
builder.apply_pre_execution_changes().map_err(|err| {
250256
tracing::warn!(target: "payload_builder", %err, "failed to apply pre-execution changes");
@@ -394,6 +400,9 @@ where
394400
}
395401

396402
/// Prepares a [`BlockBuilder`] for the next block.
403+
///
404+
/// The gas limit is clamped based on the parent block's gas limit to ensure it does not
405+
/// increase or decrease by more than `parent_gas_limit / 1024` per block.
397406
pub fn block_builder<'a, DB: Database>(
398407
&'a self,
399408
db: &'a mut State<DB>,
@@ -405,17 +414,25 @@ where
405414
.next_block_base_fee(db, self.parent().header(), self.attributes().timestamp())
406415
.map_err(|err| PayloadBuilderError::Other(Box::new(err)))?;
407416

417+
// Get the desired gas limit from attributes or config
418+
let desired_gas_limit = self
419+
.attributes()
420+
.gas_limit
421+
.unwrap_or_else(|| builder_config.gas_limit.unwrap_or_default());
422+
423+
// Clamp the gas limit based on parent's gas limit.
424+
// The gas limit can only change by at most `parent_gas_limit / 1024` per block.
425+
let parent_gas_limit = self.parent().gas_limit();
426+
let gas_limit = calculate_block_gas_limit(parent_gas_limit, desired_gas_limit);
427+
408428
self.evm_config
409429
.builder_for_next_block(
410430
db,
411431
self.parent(),
412432
ScrollNextBlockEnvAttributes {
413433
timestamp: self.attributes().timestamp(),
414434
suggested_fee_recipient: self.attributes().suggested_fee_recipient(),
415-
gas_limit: self
416-
.attributes()
417-
.gas_limit
418-
.unwrap_or_else(|| builder_config.gas_limit.unwrap_or_default()),
435+
gas_limit,
419436
base_fee,
420437
},
421438
)

crates/scroll/payload/src/config.rs

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use core::time::Duration;
44
use reth_chainspec::MIN_TRANSACTION_GAS;
5+
use reth_primitives_traits::constants::GAS_LIMIT_BOUND_DIVISOR;
56
use std::{fmt::Debug, time::Instant};
67

78
/// Settings for the Scroll builder.
@@ -28,9 +29,11 @@ impl ScrollBuilderConfig {
2829
Self { gas_limit, time_limit, max_da_block_size }
2930
}
3031

31-
/// Returns the [`PayloadBuildingBreaker`] for the config.
32-
pub(super) fn breaker(&self) -> PayloadBuildingBreaker {
33-
PayloadBuildingBreaker::new(self.time_limit, self.gas_limit, self.max_da_block_size)
32+
/// Returns the [`PayloadBuildingBreaker`] for the config with the actual gas limit used.
33+
///
34+
/// The `actual_gas_limit` should be the gas limit after clamping based on parent's gas limit.
35+
pub(super) fn breaker_with_gas_limit(&self, actual_gas_limit: u64) -> PayloadBuildingBreaker {
36+
PayloadBuildingBreaker::new(self.time_limit, Some(actual_gas_limit), self.max_da_block_size)
3437
}
3538
}
3639

@@ -78,6 +81,17 @@ impl PayloadBuildingBreaker {
7881
}
7982
}
8083

84+
/// Calculate the gas limit for the next block based on parent and desired gas limits.
85+
///
86+
/// The gas limit can only change by at most `parent_gas_limit / 1024` per block.
87+
/// Ref: <https://github.com/ethereum/go-ethereum/blob/88cbfab332c96edfbe99d161d9df6a40721bd786/core/block_validator.go#L166>
88+
pub fn calculate_block_gas_limit(parent_gas_limit: u64, desired_gas_limit: u64) -> u64 {
89+
let delta = (parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR).saturating_sub(1);
90+
let min_gas_limit = parent_gas_limit.saturating_sub(delta);
91+
let max_gas_limit = parent_gas_limit.saturating_add(delta);
92+
desired_gas_limit.clamp(min_gas_limit, max_gas_limit)
93+
}
94+
8195
#[cfg(test)]
8296
mod tests {
8397
use super::*;
@@ -128,4 +142,72 @@ mod tests {
128142
// But should still break on gas limit
129143
assert!(breaker.should_break(MIN_TRANSACTION_GAS + 1, u64::MAX));
130144
}
145+
146+
#[test]
147+
fn test_calculate_block_gas_limit_within_bounds() {
148+
let parent_gas_limit = GAS_LIMIT_BOUND_DIVISOR * 10; // 10240
149+
let delta = parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1; // 9
150+
151+
// Desired equals parent - should return parent
152+
assert_eq!(calculate_block_gas_limit(parent_gas_limit, parent_gas_limit), parent_gas_limit);
153+
154+
// Small increase within bounds
155+
assert_eq!(
156+
calculate_block_gas_limit(parent_gas_limit, parent_gas_limit + 5),
157+
parent_gas_limit + 5
158+
);
159+
160+
// Small decrease within bounds
161+
assert_eq!(
162+
calculate_block_gas_limit(parent_gas_limit, parent_gas_limit - 5),
163+
parent_gas_limit - 5
164+
);
165+
166+
// Exactly at max allowed increase
167+
assert_eq!(
168+
calculate_block_gas_limit(parent_gas_limit, parent_gas_limit + delta),
169+
parent_gas_limit + delta
170+
);
171+
172+
// Exactly at max allowed decrease
173+
assert_eq!(
174+
calculate_block_gas_limit(parent_gas_limit, parent_gas_limit - delta),
175+
parent_gas_limit - delta
176+
);
177+
}
178+
179+
#[test]
180+
fn test_calculate_block_gas_limit_clamped_increase() {
181+
let parent_gas_limit = GAS_LIMIT_BOUND_DIVISOR * 10; // 10240
182+
let delta = parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1; // 9
183+
let max_gas_limit = parent_gas_limit + delta;
184+
185+
// Desired exceeds max - should clamp to max
186+
assert_eq!(
187+
calculate_block_gas_limit(parent_gas_limit, parent_gas_limit + delta + 1),
188+
max_gas_limit
189+
);
190+
191+
// Large increase - should clamp
192+
assert_eq!(
193+
calculate_block_gas_limit(parent_gas_limit, parent_gas_limit * 2),
194+
max_gas_limit
195+
);
196+
}
197+
198+
#[test]
199+
fn test_calculate_block_gas_limit_clamped_decrease() {
200+
let parent_gas_limit = GAS_LIMIT_BOUND_DIVISOR * 10; // 10240
201+
let delta = parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR - 1; // 9
202+
let min_gas_limit = parent_gas_limit - delta;
203+
204+
// Desired below min - should clamp to min
205+
assert_eq!(
206+
calculate_block_gas_limit(parent_gas_limit, parent_gas_limit - delta - 1),
207+
min_gas_limit
208+
);
209+
210+
// Much lower than allowed - should clamp
211+
assert_eq!(calculate_block_gas_limit(parent_gas_limit, 0), min_gas_limit);
212+
}
131213
}

0 commit comments

Comments
 (0)