Skip to content

Commit 4fd69a4

Browse files
authored
feat: l1 data fee buffer (#376)
* l1 data fee buffer * fix deny
1 parent b4a1fc5 commit 4fd69a4

File tree

7 files changed

+294
-42
lines changed

7 files changed

+294
-42
lines changed

Cargo.lock

Lines changed: 15 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -487,18 +487,18 @@ reth-ress-protocol = { path = "crates/ress/protocol" }
487487
reth-ress-provider = { path = "crates/ress/provider" }
488488

489489
# revm
490-
revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false, features = ["enable_eip7702", "enable_eip7623"] }
491-
revm-bytecode = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
492-
revm-database = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
493-
revm-state = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
494-
revm-primitives = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
495-
revm-interpreter = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
496-
revm-inspector = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
497-
revm-context = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
498-
revm-context-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
499-
revm-database-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
500-
op-revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false }
501-
revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", tag = "scroll-v91", default-features = false }
490+
revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false, features = ["enable_eip7702", "enable_eip7623"] }
491+
revm-bytecode = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
492+
revm-database = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
493+
revm-state = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
494+
revm-primitives = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
495+
revm-interpreter = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
496+
revm-inspector = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
497+
revm-context = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
498+
revm-context-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
499+
revm-database-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
500+
op-revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false }
501+
revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", tag = "scroll-v91.1", default-features = false }
502502
revm-inspectors = "0.31.0"
503503

504504
# eth
@@ -771,8 +771,8 @@ walkdir = "2.3.3"
771771
vergen-git2 = "1.0.5"
772772

773773
[patch.crates-io]
774-
revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
775-
op-revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
774+
revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1" }
775+
op-revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1" }
776776
# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" }
777777
# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" }
778778
# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" }

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

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,21 @@ use scroll_alloy_hardforks::ScrollHardforks;
2424
pub struct ScrollPoolBuilder<T = reth_scroll_txpool::ScrollPooledTransaction> {
2525
/// Enforced overrides that are applied to the pool config.
2626
pub pool_config_overrides: PoolBuilderConfigOverrides,
27-
27+
/// Require L1 data fee buffer in balance check.
28+
/// When enabled, validates balance >= `L2_cost` + 2*`L1_cost` but only charges `L2_cost` +
29+
/// 1*`L1_cost`.
30+
pub require_l1_data_fee_buffer: bool,
2831
/// Marker for the pooled transaction type.
2932
_pd: core::marker::PhantomData<T>,
3033
}
3134

3235
impl<T> Default for ScrollPoolBuilder<T> {
3336
fn default() -> Self {
34-
Self { pool_config_overrides: Default::default(), _pd: Default::default() }
37+
Self {
38+
pool_config_overrides: Default::default(),
39+
require_l1_data_fee_buffer: false,
40+
_pd: Default::default(),
41+
}
3542
}
3643
}
3744

@@ -44,6 +51,14 @@ impl<T> ScrollPoolBuilder<T> {
4451
self.pool_config_overrides = pool_config_overrides;
4552
self
4653
}
54+
55+
/// Sets the require L1 data fee buffer flag.
56+
/// When enabled, validates balance >= `L2_cost` + 2*`L1_cost` but only charges `L2_cost` +
57+
/// 1*`L1_cost`. This matches geth's block validation behavior.
58+
pub const fn with_require_l1_data_fee_buffer(mut self, require: bool) -> Self {
59+
self.require_l1_data_fee_buffer = require;
60+
self
61+
}
4762
}
4863

4964
impl<Node, T> PoolBuilder<Node> for ScrollPoolBuilder<T>
@@ -58,7 +73,7 @@ where
5873
type Pool = ScrollTransactionPool<Node::Provider, DiskFileBlobStore, T>;
5974

6075
async fn build_pool(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
61-
let Self { pool_config_overrides, .. } = self;
76+
let Self { pool_config_overrides, require_l1_data_fee_buffer, .. } = self;
6277
let data_dir = ctx.config().datadir();
6378
let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?;
6479

@@ -81,6 +96,7 @@ where
8196
// In --dev mode we can't require gas fees because we're unable to decode
8297
// the L1 block info
8398
.require_l1_data_gas_fee(!ctx.config().dev.dev)
99+
.require_l1_data_fee_buffer(require_l1_data_fee_buffer)
84100
});
85101

86102
let transaction_pool = reth_transaction_pool::Pool::new(
@@ -379,4 +395,111 @@ mod tests {
379395
// drop all validation tasks.
380396
drop(manager);
381397
}
398+
399+
#[test]
400+
fn test_pool_builder_with_require_l1_data_fee_buffer() {
401+
// Test that the builder method correctly sets the flag
402+
let pool_builder = ScrollPoolBuilder::<ScrollPooledTransaction>::default();
403+
assert!(!pool_builder.require_l1_data_fee_buffer);
404+
405+
let pool_builder = pool_builder.with_require_l1_data_fee_buffer(true);
406+
assert!(pool_builder.require_l1_data_fee_buffer);
407+
408+
let pool_builder = pool_builder.with_require_l1_data_fee_buffer(false);
409+
assert!(!pool_builder.require_l1_data_fee_buffer);
410+
}
411+
412+
#[tokio::test]
413+
async fn test_l1_data_fee_buffer_validation() {
414+
// Test that the L1 data fee buffer feature correctly validates transactions:
415+
// - With buffer enabled: rejects when balance < L2_cost + 2*L1_cost
416+
// - With buffer disabled: accepts when balance >= L2_cost + 1*L1_cost
417+
//
418+
// Both scenarios use identical setup to prove only the buffer flag differs.
419+
420+
// Shared test constants
421+
let signer: alloy_primitives::Address = Default::default();
422+
let balance = U256::from(500_000_000_000_000u64); // 500 Twei
423+
let gas_limit = 55_000u64;
424+
let gas_price = 7u128;
425+
let tx_input = Bytes::from(random_iter::<u8>().take(100).collect::<Vec<_>>());
426+
427+
// Helper to create a client with identical state
428+
let client =
429+
MockEthProvider::<ScrollPrimitives, _>::new().with_chain_spec(SCROLL_DEV.clone());
430+
let hash = B256::random();
431+
client.add_header(hash, Header::default());
432+
client.add_block(hash, ScrollBlock::default());
433+
// Balance covers L2_cost + 1*L1_cost but NOT L2_cost + 2*L1_cost
434+
// With u32::MAX storage values, L1 cost is ~483 Twei.
435+
// max L2_cost = 55,000 * 7 = 385,000 Wei.
436+
client.add_account(signer, ExtendedAccount::new(0, balance));
437+
client.add_account(
438+
L1_GAS_PRICE_ORACLE_ADDRESS,
439+
ExtendedAccount::new(0, U256::ZERO).extend_storage(
440+
(0u8..8).map(|k| (B256::from(U256::from(k)), U256::from(u32::MAX))),
441+
),
442+
);
443+
444+
// Helper to create a transaction with identical parameters
445+
let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked(
446+
TxLegacy { gas_limit, gas_price, input: tx_input.clone(), ..Default::default() },
447+
Signature::new(U256::ZERO, U256::ZERO, false),
448+
Default::default(),
449+
));
450+
let tx = ScrollPooledTransaction::new(Recovered::new_unchecked(tx, signer), 200);
451+
452+
let handle = tokio::runtime::Handle::current();
453+
let manager = TaskManager::new(handle);
454+
455+
// Test 1: With L1 data fee buffer ENABLED - should reject (requires 2x L1 cost)
456+
let validator = TransactionValidationTaskExecutor::eth_builder(client.clone())
457+
.no_eip4844()
458+
.build_with_tasks(manager.executor(), NoopBlobStore::default())
459+
.map(|validator| {
460+
ScrollTransactionValidator::new(validator)
461+
.require_l1_data_gas_fee(true)
462+
.require_l1_data_fee_buffer(true)
463+
});
464+
465+
let pool = ScrollTransactionPool::new(
466+
validator,
467+
CoinbaseTipOrdering::<ScrollPooledTransaction>::default(),
468+
NoopBlobStore::default(),
469+
PoolConfig::default(),
470+
);
471+
472+
let err = pool.add_transaction(TransactionOrigin::Local, tx.clone()).await.unwrap_err();
473+
assert!(matches!(
474+
err.kind,
475+
PoolErrorKind::InvalidTransaction(
476+
InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(GotExpectedBoxed(expected)))
477+
) if *expected == GotExpected{ got: balance, expected: U256::from(967347259159872u64) }
478+
));
479+
480+
// Test 2: With L1 data fee buffer DISABLED - should accept (only requires 1x L1 cost)
481+
let validator = TransactionValidationTaskExecutor::eth_builder(client)
482+
.no_eip4844()
483+
.build_with_tasks(manager.executor(), NoopBlobStore::default())
484+
.map(|validator| {
485+
ScrollTransactionValidator::new(validator)
486+
.require_l1_data_gas_fee(true)
487+
.require_l1_data_fee_buffer(false)
488+
});
489+
490+
let pool = ScrollTransactionPool::new(
491+
validator,
492+
CoinbaseTipOrdering::<ScrollPooledTransaction>::default(),
493+
NoopBlobStore::default(),
494+
PoolConfig::default(),
495+
);
496+
497+
let result = pool.add_transaction(TransactionOrigin::Local, tx).await;
498+
assert!(
499+
result.is_ok(),
500+
"Expected transaction to be accepted without buffer, got: {result:?}"
501+
);
502+
503+
drop(manager);
504+
}
382505
}

crates/scroll/openvm-compat/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ scroll-alloy-consensus = { path = "../alloy/consensus", default-features = false
2828
scroll-alloy-rpc-types = { path = "../alloy/rpc-types", default-features = false }
2929

3030
[patch.crates-io]
31-
revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" }
31+
revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1" }

crates/scroll/txpool/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ tracing.workspace = true
4545
[dev-dependencies]
4646
reth-scroll-chainspec.workspace = true
4747
reth-provider = { workspace = true, features = ["test-utils"] }
48+
scroll-alloy-evm.workspace = true

0 commit comments

Comments
 (0)