diff --git a/crates/scroll/chainspec/src/constants.rs b/crates/scroll/chainspec/src/constants.rs
index d41916c5732..a95fba0ee46 100644
--- a/crates/scroll/chainspec/src/constants.rs
+++ b/crates/scroll/chainspec/src/constants.rs
@@ -5,6 +5,9 @@ use alloy_primitives::{address, b256, Address, B256};
/// The transaction fee recipient on the L2.
pub const SCROLL_FEE_VAULT_ADDRESS: Address = address!("5300000000000000000000000000000000000005");
+/// The maximum size in bytes of the payload for a block.
+pub const MAX_TX_PAYLOAD_BYTES_PER_BLOCK: usize = 120 * 1024;
+
/// The system contract on L2 mainnet.
pub const SCROLL_MAINNET_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS: Address =
address!("331A873a2a85219863d80d248F9e2978fE88D0Ea");
diff --git a/crates/scroll/chainspec/src/genesis.rs b/crates/scroll/chainspec/src/genesis.rs
index c82d8600893..c522461ec01 100644
--- a/crates/scroll/chainspec/src/genesis.rs
+++ b/crates/scroll/chainspec/src/genesis.rs
@@ -1,9 +1,13 @@
//! Scroll types for genesis data.
use crate::{
- constants::{SCROLL_FEE_VAULT_ADDRESS, SCROLL_MAINNET_L1_CONFIG, SCROLL_SEPOLIA_L1_CONFIG},
+ constants::{
+ MAX_TX_PAYLOAD_BYTES_PER_BLOCK, SCROLL_FEE_VAULT_ADDRESS, SCROLL_MAINNET_L1_CONFIG,
+ SCROLL_SEPOLIA_L1_CONFIG,
+ },
SCROLL_DEV_L1_CONFIG,
};
+
use alloy_primitives::Address;
use alloy_serde::OtherFields;
use serde::de::Error;
@@ -113,6 +117,8 @@ pub struct ScrollChainConfig {
/// This is an optional field that, when set, specifies where L2 transaction fees
/// will be sent or stored.
pub fee_vault_address: Option
,
+ /// The maximum tx payload size of blocks that we produce.
+ pub max_tx_payload_bytes_per_block: usize,
/// The L1 configuration.
/// This field encapsulates specific settings and parameters required for L1
pub l1_config: L1Config,
@@ -129,6 +135,7 @@ impl ScrollChainConfig {
pub const fn mainnet() -> Self {
Self {
fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS),
+ max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK,
l1_config: SCROLL_MAINNET_L1_CONFIG,
}
}
@@ -137,13 +144,18 @@ impl ScrollChainConfig {
pub const fn sepolia() -> Self {
Self {
fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS),
+ max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK,
l1_config: SCROLL_SEPOLIA_L1_CONFIG,
}
}
/// Returns the [`ScrollChainConfig`] for Scroll dev.
pub const fn dev() -> Self {
- Self { fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS), l1_config: SCROLL_DEV_L1_CONFIG }
+ Self {
+ fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS),
+ max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK,
+ l1_config: SCROLL_DEV_L1_CONFIG,
+ }
}
}
@@ -209,6 +221,7 @@ mod tests {
"feynmanTime": 100,
"scroll": {
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
+ "maxTxPayloadBytesPerBlock": 122880,
"l1Config": {
"l1ChainId": 1,
"l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
@@ -237,6 +250,7 @@ mod tests {
}),
scroll_chain_config: ScrollChainConfig {
fee_vault_address: Some(address!("5300000000000000000000000000000000000005")),
+ max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK,
l1_config: L1Config {
l1_chain_id: 1,
l1_message_queue_address: address!("0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"),
diff --git a/crates/scroll/chainspec/src/lib.rs b/crates/scroll/chainspec/src/lib.rs
index 7668044ae61..ccd15550e99 100644
--- a/crates/scroll/chainspec/src/lib.rs
+++ b/crates/scroll/chainspec/src/lib.rs
@@ -34,10 +34,10 @@ extern crate alloc;
mod constants;
pub use constants::{
- SCROLL_BASE_FEE_PARAMS_FEYNMAN, SCROLL_DEV_L1_CONFIG, SCROLL_DEV_L1_MESSAGE_QUEUE_ADDRESS,
- SCROLL_DEV_L1_MESSAGE_QUEUE_V2_ADDRESS, SCROLL_DEV_L1_PROXY_ADDRESS,
- SCROLL_DEV_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, SCROLL_DEV_MAX_L1_MESSAGES,
- SCROLL_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_FEYNMAN,
+ MAX_TX_PAYLOAD_BYTES_PER_BLOCK, SCROLL_BASE_FEE_PARAMS_FEYNMAN, SCROLL_DEV_L1_CONFIG,
+ SCROLL_DEV_L1_MESSAGE_QUEUE_ADDRESS, SCROLL_DEV_L1_MESSAGE_QUEUE_V2_ADDRESS,
+ SCROLL_DEV_L1_PROXY_ADDRESS, SCROLL_DEV_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS,
+ SCROLL_DEV_MAX_L1_MESSAGES, SCROLL_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_FEYNMAN,
SCROLL_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER_FEYNMAN, SCROLL_FEE_VAULT_ADDRESS,
SCROLL_MAINNET_GENESIS_HASH, SCROLL_MAINNET_L1_CONFIG, SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS,
SCROLL_MAINNET_L1_MESSAGE_QUEUE_V2_ADDRESS, SCROLL_MAINNET_L1_PROXY_ADDRESS,
@@ -624,26 +624,26 @@ mod tests {
#[test]
fn parse_scroll_hardforks() {
let geth_genesis = r#"
- {
- "config": {
- "bernoulliBlock": 10,
- "curieBlock": 20,
- "darwinTime": 30,
- "darwinV2Time": 31,
- "scroll": {
- "feeVaultAddress": "0x5300000000000000000000000000000000000005",
- "l1Config": {
- "l1ChainId": 1,
- "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
- "l1MessageQueueV2Address": "0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a",
- "l2SystemConfigAddress": "0x331A873a2a85219863d80d248F9e2978fE88D0Ea",
- "scrollChainAddress": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556",
- "numL1MessagesPerBlock": 10
+ {
+ "config": {
+ "bernoulliBlock": 10,
+ "curieBlock": 20,
+ "darwinTime": 30,
+ "darwinV2Time": 31,
+ "scroll": {
+ "feeVaultAddress": "0x5300000000000000000000000000000000000005",
+ "maxTxPayloadBytesPerBlock": 122880,
+ "l1Config": {
+ "l1ChainId": 1,
+ "l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
+ "l1MessageQueueV2Address": "0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a",
+ "l2SystemConfigAddress": "0x331A873a2a85219863d80d248F9e2978fE88D0Ea",
+ "scrollChainAddress": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556",
+ "numL1MessagesPerBlock": 10
+ }
+ }
}
- }
- }
- }
- "#;
+ }"#;
let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
let actual_bernoulli_block = genesis.config.extra_fields.get("bernoulliBlock");
@@ -659,6 +659,7 @@ mod tests {
scroll_object,
&serde_json::json!({
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
+ "maxTxPayloadBytesPerBlock": 122880,
"l1Config": {
"l1ChainId": 1,
"l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
@@ -712,6 +713,7 @@ mod tests {
String::from("scroll"),
serde_json::json!({
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
+ "maxTxPayloadBytesPerBlock": 122880,
"l1Config": {
"l1ChainId": 1,
"l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
diff --git a/crates/scroll/node/src/builder/pool.rs b/crates/scroll/node/src/builder/pool.rs
index 8152a5fe5e7..70faed0f901 100644
--- a/crates/scroll/node/src/builder/pool.rs
+++ b/crates/scroll/node/src/builder/pool.rs
@@ -68,6 +68,7 @@ where
.with_local_transactions_config(
pool_config_overrides.clone().apply(ctx.pool_config()).local_transactions_config,
)
+ .with_max_tx_input_bytes(ctx.chain_spec().chain_config().max_tx_payload_bytes_per_block)
.with_additional_tasks(
pool_config_overrides
.additional_validation_tasks
@@ -130,3 +131,158 @@ where
Ok(transaction_pool)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::ScrollNode;
+
+ use alloy_consensus::{transaction::Recovered, Header, Signed, TxLegacy};
+ use alloy_primitives::{private::rand::random_iter, Bytes, Signature, B256, U256};
+ use reth_chainspec::Head;
+ use reth_db::mock::DatabaseMock;
+ use reth_node_api::FullNodeTypesAdapter;
+ use reth_node_builder::common::WithConfigs;
+ use reth_node_core::node_config::NodeConfig;
+ use reth_primitives_traits::{
+ transaction::error::InvalidTransactionError, GotExpected, GotExpectedBoxed,
+ };
+ use reth_provider::{
+ noop::NoopProvider,
+ test_utils::{ExtendedAccount, MockEthProvider},
+ };
+ use reth_scroll_chainspec::{ScrollChainSpec, SCROLL_DEV, SCROLL_MAINNET};
+ use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives};
+ use reth_scroll_txpool::ScrollPooledTransaction;
+ use reth_tasks::TaskManager;
+ use reth_transaction_pool::{
+ blobstore::NoopBlobStore,
+ error::{InvalidPoolTransactionError, PoolErrorKind},
+ PoolConfig, TransactionOrigin, TransactionPool,
+ };
+ use scroll_alloy_consensus::ScrollTxEnvelope;
+ use scroll_alloy_evm::curie::L1_GAS_PRICE_ORACLE_ADDRESS;
+
+ async fn pool() -> (
+ ScrollTransactionPool, DiskFileBlobStore>,
+ TaskManager,
+ ) {
+ let handle = tokio::runtime::Handle::current();
+ let manager = TaskManager::new(handle);
+ let config = WithConfigs {
+ config: NodeConfig::new(SCROLL_MAINNET.clone()),
+ toml_config: Default::default(),
+ };
+
+ let pool_builder = ScrollPoolBuilder::::default();
+ let ctx = BuilderContext::<
+ FullNodeTypesAdapter<
+ ScrollNode,
+ DatabaseMock,
+ NoopProvider,
+ >,
+ >::new(
+ Head::default(),
+ NoopProvider::new(SCROLL_MAINNET.clone()),
+ manager.executor(),
+ config,
+ );
+ (pool_builder.build_pool(&ctx).await.unwrap(), manager)
+ }
+
+ #[tokio::test]
+ async fn test_validate_one_oversized_transaction() {
+ // create the pool.
+ let (pool, manager) = pool().await;
+ let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked(
+ TxLegacy { gas_limit: 21_000, ..Default::default() },
+ Signature::new(U256::ZERO, U256::ZERO, false),
+ Default::default(),
+ ));
+
+ // Create a pool transaction with an encoded length of 123,904 bytes.
+ let pool_tx = ScrollPooledTransaction::new(
+ Recovered::new_unchecked(tx, Default::default()),
+ 121 * 1024,
+ );
+
+ // add the transaction to the pool and expect an `OversizedData` error.
+ let err = pool.add_transaction(TransactionOrigin::Local, pool_tx).await.unwrap_err();
+ assert!(matches!(
+ err.kind,
+ PoolErrorKind::InvalidTransaction(
+ InvalidPoolTransactionError::OversizedData(x, y,)
+ ) if x == 121*1024 && y == 120*1024,
+ ));
+
+ // explicitly drop the manager here otherwise the `TransactionValidationTaskExecutor` will
+ // drop all validation tasks.
+ drop(manager);
+ }
+
+ #[tokio::test]
+ async fn test_validate_one_rollup_fee_exceeds_balance() {
+ // create the client.
+ let handle = tokio::runtime::Handle::current();
+ let manager = TaskManager::new(handle);
+ let blob_store = NoopBlobStore::default();
+ let signer = Default::default();
+ let client =
+ MockEthProvider::::new().with_chain_spec(SCROLL_DEV.clone());
+ let hash = B256::random();
+
+ // load a header, block, signer and the L1_GAS_PRICE_ORACLE_ADDRESS storage.
+ client.add_header(hash, Header::default());
+ client.add_block(hash, ScrollBlock::default());
+ client.add_account(signer, ExtendedAccount::new(0, U256::from(400_000)));
+ client.add_account(
+ L1_GAS_PRICE_ORACLE_ADDRESS,
+ ExtendedAccount::new(0, U256::from(400_000)).extend_storage(
+ (0u8..8).map(|k| (B256::from(U256::from(k)), U256::from(u64::MAX))),
+ ),
+ );
+
+ // create the validation task.
+ let validator = TransactionValidationTaskExecutor::eth_builder(client)
+ .no_eip4844()
+ .build_with_tasks(manager.executor(), blob_store)
+ .map(|validator| {
+ ScrollTransactionValidator::new(validator).require_l1_data_gas_fee(true)
+ });
+
+ // create the pool.
+ let pool = ScrollTransactionPool::new(
+ validator,
+ CoinbaseTipOrdering::::default(),
+ NoopBlobStore::default(),
+ PoolConfig::default(),
+ );
+
+ // prepare a transaction with random input.
+ let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked(
+ TxLegacy {
+ gas_limit: 55_000,
+ gas_price: 7,
+ input: Bytes::from(random_iter::().take(100).collect::>()),
+ ..Default::default()
+ },
+ Signature::new(U256::ZERO, U256::ZERO, false),
+ Default::default(),
+ ));
+ let pool_tx =
+ ScrollPooledTransaction::new(Recovered::new_unchecked(tx, signer), 120 * 1024);
+
+ // add the transaction in the pool and expect to hit `InsufficientFunds` error.
+ let err = pool.add_transaction(TransactionOrigin::Local, pool_tx).await.unwrap_err();
+ assert!(matches!(
+ err.kind,
+ PoolErrorKind::InvalidTransaction(
+ InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(GotExpectedBoxed(expected)))
+ ) if *expected == GotExpected{ got: U256::from(400000), expected: U256::from_limbs([384999, 1, 0, 0]) }
+ ));
+
+ // explicitly drop the manager here otherwise the `TransactionValidationTaskExecutor` will
+ // drop all validation tasks.
+ drop(manager);
+ }
+}
diff --git a/crates/scroll/node/src/test_utils.rs b/crates/scroll/node/src/test_utils.rs
index 1e7141895f5..be464870cbf 100644
--- a/crates/scroll/node/src/test_utils.rs
+++ b/crates/scroll/node/src/test_utils.rs
@@ -9,7 +9,7 @@ use reth_node_api::NodeTypesWithDBAdapter;
use reth_payload_builder::EthPayloadBuilderAttributes;
use reth_provider::providers::BlockchainProvider;
-use reth_scroll_chainspec::ScrollChainSpecBuilder;
+use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpecBuilder};
use reth_tasks::TaskManager;
use scroll_alloy_rpc_types_engine::BlockDataHint;
use std::sync::Arc;
@@ -31,10 +31,12 @@ pub async fn setup(
reth_e2e_test_utils::setup_engine(
num_nodes,
Arc::new(
- ScrollChainSpecBuilder::scroll_mainnet()
- .genesis(genesis)
- .euclid_v2_activated()
- .build(Default::default()),
+ ScrollChainSpecBuilder::scroll_mainnet().genesis(genesis).euclid_v2_activated().build(
+ ScrollChainConfig {
+ max_tx_payload_bytes_per_block: 120 * 1024,
+ ..Default::default()
+ },
+ ),
),
is_dev,
Default::default(),
diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs
index 68f8c38e59d..7636b27db4a 100644
--- a/crates/storage/provider/src/test_utils/mock.rs
+++ b/crates/storage/provider/src/test_utils/mock.rs
@@ -5,7 +5,7 @@ use crate::{
StateProvider, StateProviderBox, StateProviderFactory, StateReader, StateRootProvider,
TransactionVariant, TransactionsProvider,
};
-use alloy_consensus::{constants::EMPTY_ROOT_HASH, transaction::TransactionMeta, Header};
+use alloy_consensus::{constants::EMPTY_ROOT_HASH, transaction::TransactionMeta, BlockHeader};
use alloy_eips::{BlockHashOrNumber, BlockId, BlockNumberOrTag};
use alloy_primitives::{
keccak256, map::HashMap, Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue,
@@ -19,11 +19,12 @@ use reth_db_api::{
models::{AccountBeforeTx, StoredBlockBodyIndices},
};
use reth_ethereum_engine_primitives::EthEngineTypes;
-use reth_ethereum_primitives::{EthPrimitives, Receipt};
+use reth_ethereum_primitives::EthPrimitives;
use reth_execution_types::ExecutionOutcome;
use reth_node_types::NodeTypes;
use reth_primitives_traits::{
- Account, Bytecode, GotExpected, NodePrimitives, RecoveredBlock, SealedHeader, SignerRecoverable,
+ Account, Block, BlockBody, Bytecode, GotExpected, NodePrimitives, RecoveredBlock, SealedHeader,
+ SignedTransaction, SignerRecoverable,
};
use reth_prune_types::PruneModes;
use reth_stages_types::{StageCheckpoint, StageId};
@@ -48,16 +49,14 @@ use tokio::sync::broadcast;
/// A mock implementation for Provider interfaces.
#[derive(Debug)]
-pub struct MockEthProvider<
- T: NodePrimitives = reth_ethereum_primitives::EthPrimitives,
- ChainSpec = reth_chainspec::ChainSpec,
-> {
+pub struct MockEthProvider
+{
///local block store
pub blocks: Arc>>,
/// Local header store
- pub headers: Arc>>,
+ pub headers: Arc::Header>>>,
/// Local receipt store indexed by block number
- pub receipts: Arc>>>,
+ pub receipts: Arc>>>,
/// Local account store
pub accounts: Arc>>,
/// Local chain spec
@@ -106,31 +105,31 @@ impl MockEthProvider {
}
}
-impl MockEthProvider {
+impl MockEthProvider {
/// Add block to local block store
- pub fn add_block(&self, hash: B256, block: reth_ethereum_primitives::Block) {
- self.add_header(hash, block.header.clone());
+ pub fn add_block(&self, hash: B256, block: T::Block) {
+ self.add_header(hash, block.header().clone());
self.blocks.lock().insert(hash, block);
}
/// Add multiple blocks to local block store
- pub fn extend_blocks(
- &self,
- iter: impl IntoIterator,
- ) {
+ pub fn extend_blocks(&self, iter: impl IntoIterator) {
for (hash, block) in iter {
- self.add_header(hash, block.header.clone());
+ self.add_header(hash, block.header().clone());
self.add_block(hash, block)
}
}
/// Add header to local header store
- pub fn add_header(&self, hash: B256, header: Header) {
+ pub fn add_header(&self, hash: B256, header: ::Header) {
self.headers.lock().insert(hash, header);
}
/// Add multiple headers to local header store
- pub fn extend_headers(&self, iter: impl IntoIterator) {
+ pub fn extend_headers(
+ &self,
+ iter: impl IntoIterator::Header)>,
+ ) {
for (hash, header) in iter {
self.add_header(hash, header)
}
@@ -149,12 +148,12 @@ impl MockEthProvider) {
+ pub fn add_receipts(&self, block_number: BlockNumber, receipts: Vec) {
self.receipts.lock().insert(block_number, receipts);
}
/// Add multiple receipts to local receipt store
- pub fn extend_receipts(&self, iter: impl IntoIterator)>) {
+ pub fn extend_receipts(&self, iter: impl IntoIterator)>) {
for (block_number, receipts) in iter {
self.add_receipts(block_number, receipts);
}
@@ -175,10 +174,7 @@ impl MockEthProvider(
- self,
- chain_spec: C,
- ) -> MockEthProvider {
+ pub fn with_chain_spec(self, chain_spec: C) -> MockEthProvider {
MockEthProvider {
blocks: self.blocks,
headers: self.headers,
@@ -300,27 +296,27 @@ impl DBProvider
}
}
-impl HeaderProvider
- for MockEthProvider
+impl HeaderProvider
+ for MockEthProvider
{
- type Header = Header;
+ type Header = ::Header;
- fn header(&self, block_hash: &BlockHash) -> ProviderResult