Skip to content

Commit 2686f6e

Browse files
authored
feat: limit pool tx size (#317)
* feat: add maxTxPayloadBytesPerBlock to ScrollChainConfig Signed-off-by: Gregory Edison <gregory.edison1993@gmail.com> * feat: limit pool max tx input bytes Signed-off-by: Gregory Edison <gregory.edison1993@gmail.com> * test: set max_tx_payload_bytes_per_block Signed-off-by: Gregory Edison <gregory.edison1993@gmail.com> * feat: make MockEthProvider more generic Signed-off-by: Gregory Edison <gregory.edison1993@gmail.com> * test: add pool limit tests Signed-off-by: Gregory Edison <gregory.edison1993@gmail.com> * fix: lints Signed-off-by: Gregory Edison <gregory.edison1993@gmail.com> --------- Signed-off-by: Gregory Edison <gregory.edison1993@gmail.com>
1 parent 6eee35c commit 2686f6e

File tree

6 files changed

+310
-120
lines changed

6 files changed

+310
-120
lines changed

crates/scroll/chainspec/src/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use alloy_primitives::{address, b256, Address, B256};
55
/// The transaction fee recipient on the L2.
66
pub const SCROLL_FEE_VAULT_ADDRESS: Address = address!("5300000000000000000000000000000000000005");
77

8+
/// The maximum size in bytes of the payload for a block.
9+
pub const MAX_TX_PAYLOAD_BYTES_PER_BLOCK: usize = 120 * 1024;
10+
811
/// The system contract on L2 mainnet.
912
pub const SCROLL_MAINNET_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS: Address =
1013
address!("331A873a2a85219863d80d248F9e2978fE88D0Ea");

crates/scroll/chainspec/src/genesis.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
//! Scroll types for genesis data.
22
33
use crate::{
4-
constants::{SCROLL_FEE_VAULT_ADDRESS, SCROLL_MAINNET_L1_CONFIG, SCROLL_SEPOLIA_L1_CONFIG},
4+
constants::{
5+
MAX_TX_PAYLOAD_BYTES_PER_BLOCK, SCROLL_FEE_VAULT_ADDRESS, SCROLL_MAINNET_L1_CONFIG,
6+
SCROLL_SEPOLIA_L1_CONFIG,
7+
},
58
SCROLL_DEV_L1_CONFIG,
69
};
10+
711
use alloy_primitives::Address;
812
use alloy_serde::OtherFields;
913
use serde::de::Error;
@@ -113,6 +117,8 @@ pub struct ScrollChainConfig {
113117
/// This is an optional field that, when set, specifies where L2 transaction fees
114118
/// will be sent or stored.
115119
pub fee_vault_address: Option<Address>,
120+
/// The maximum tx payload size of blocks that we produce.
121+
pub max_tx_payload_bytes_per_block: usize,
116122
/// The L1 configuration.
117123
/// This field encapsulates specific settings and parameters required for L1
118124
pub l1_config: L1Config,
@@ -129,6 +135,7 @@ impl ScrollChainConfig {
129135
pub const fn mainnet() -> Self {
130136
Self {
131137
fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS),
138+
max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK,
132139
l1_config: SCROLL_MAINNET_L1_CONFIG,
133140
}
134141
}
@@ -137,13 +144,18 @@ impl ScrollChainConfig {
137144
pub const fn sepolia() -> Self {
138145
Self {
139146
fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS),
147+
max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK,
140148
l1_config: SCROLL_SEPOLIA_L1_CONFIG,
141149
}
142150
}
143151

144152
/// Returns the [`ScrollChainConfig`] for Scroll dev.
145153
pub const fn dev() -> Self {
146-
Self { fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS), l1_config: SCROLL_DEV_L1_CONFIG }
154+
Self {
155+
fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS),
156+
max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK,
157+
l1_config: SCROLL_DEV_L1_CONFIG,
158+
}
147159
}
148160
}
149161

@@ -209,6 +221,7 @@ mod tests {
209221
"feynmanTime": 100,
210222
"scroll": {
211223
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
224+
"maxTxPayloadBytesPerBlock": 122880,
212225
"l1Config": {
213226
"l1ChainId": 1,
214227
"l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
@@ -237,6 +250,7 @@ mod tests {
237250
}),
238251
scroll_chain_config: ScrollChainConfig {
239252
fee_vault_address: Some(address!("5300000000000000000000000000000000000005")),
253+
max_tx_payload_bytes_per_block: MAX_TX_PAYLOAD_BYTES_PER_BLOCK,
240254
l1_config: L1Config {
241255
l1_chain_id: 1,
242256
l1_message_queue_address: address!("0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"),

crates/scroll/chainspec/src/lib.rs

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ extern crate alloc;
3434

3535
mod constants;
3636
pub use constants::{
37-
SCROLL_BASE_FEE_PARAMS_FEYNMAN, SCROLL_DEV_L1_CONFIG, SCROLL_DEV_L1_MESSAGE_QUEUE_ADDRESS,
38-
SCROLL_DEV_L1_MESSAGE_QUEUE_V2_ADDRESS, SCROLL_DEV_L1_PROXY_ADDRESS,
39-
SCROLL_DEV_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS, SCROLL_DEV_MAX_L1_MESSAGES,
40-
SCROLL_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_FEYNMAN,
37+
MAX_TX_PAYLOAD_BYTES_PER_BLOCK, SCROLL_BASE_FEE_PARAMS_FEYNMAN, SCROLL_DEV_L1_CONFIG,
38+
SCROLL_DEV_L1_MESSAGE_QUEUE_ADDRESS, SCROLL_DEV_L1_MESSAGE_QUEUE_V2_ADDRESS,
39+
SCROLL_DEV_L1_PROXY_ADDRESS, SCROLL_DEV_L2_SYSTEM_CONFIG_CONTRACT_ADDRESS,
40+
SCROLL_DEV_MAX_L1_MESSAGES, SCROLL_EIP1559_BASE_FEE_MAX_CHANGE_DENOMINATOR_FEYNMAN,
4141
SCROLL_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER_FEYNMAN, SCROLL_FEE_VAULT_ADDRESS,
4242
SCROLL_MAINNET_GENESIS_HASH, SCROLL_MAINNET_L1_CONFIG, SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS,
4343
SCROLL_MAINNET_L1_MESSAGE_QUEUE_V2_ADDRESS, SCROLL_MAINNET_L1_PROXY_ADDRESS,
@@ -624,26 +624,26 @@ mod tests {
624624
#[test]
625625
fn parse_scroll_hardforks() {
626626
let geth_genesis = r#"
627-
{
628-
"config": {
629-
"bernoulliBlock": 10,
630-
"curieBlock": 20,
631-
"darwinTime": 30,
632-
"darwinV2Time": 31,
633-
"scroll": {
634-
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
635-
"l1Config": {
636-
"l1ChainId": 1,
637-
"l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
638-
"l1MessageQueueV2Address": "0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a",
639-
"l2SystemConfigAddress": "0x331A873a2a85219863d80d248F9e2978fE88D0Ea",
640-
"scrollChainAddress": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556",
641-
"numL1MessagesPerBlock": 10
627+
{
628+
"config": {
629+
"bernoulliBlock": 10,
630+
"curieBlock": 20,
631+
"darwinTime": 30,
632+
"darwinV2Time": 31,
633+
"scroll": {
634+
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
635+
"maxTxPayloadBytesPerBlock": 122880,
636+
"l1Config": {
637+
"l1ChainId": 1,
638+
"l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
639+
"l1MessageQueueV2Address": "0x56971da63A3C0205184FEF096E9ddFc7A8C2D18a",
640+
"l2SystemConfigAddress": "0x331A873a2a85219863d80d248F9e2978fE88D0Ea",
641+
"scrollChainAddress": "0xa13BAF47339d63B743e7Da8741db5456DAc1E556",
642+
"numL1MessagesPerBlock": 10
643+
}
644+
}
642645
}
643-
}
644-
}
645-
}
646-
"#;
646+
}"#;
647647
let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
648648

649649
let actual_bernoulli_block = genesis.config.extra_fields.get("bernoulliBlock");
@@ -659,6 +659,7 @@ mod tests {
659659
scroll_object,
660660
&serde_json::json!({
661661
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
662+
"maxTxPayloadBytesPerBlock": 122880,
662663
"l1Config": {
663664
"l1ChainId": 1,
664665
"l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",
@@ -712,6 +713,7 @@ mod tests {
712713
String::from("scroll"),
713714
serde_json::json!({
714715
"feeVaultAddress": "0x5300000000000000000000000000000000000005",
716+
"maxTxPayloadBytesPerBlock": 122880,
715717
"l1Config": {
716718
"l1ChainId": 1,
717719
"l1MessageQueueAddress": "0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B",

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

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ where
6868
.with_local_transactions_config(
6969
pool_config_overrides.clone().apply(ctx.pool_config()).local_transactions_config,
7070
)
71+
.with_max_tx_input_bytes(ctx.chain_spec().chain_config().max_tx_payload_bytes_per_block)
7172
.with_additional_tasks(
7273
pool_config_overrides
7374
.additional_validation_tasks
@@ -130,3 +131,158 @@ where
130131
Ok(transaction_pool)
131132
}
132133
}
134+
135+
#[cfg(test)]
136+
mod tests {
137+
use super::*;
138+
use crate::ScrollNode;
139+
140+
use alloy_consensus::{transaction::Recovered, Header, Signed, TxLegacy};
141+
use alloy_primitives::{private::rand::random_iter, Bytes, Signature, B256, U256};
142+
use reth_chainspec::Head;
143+
use reth_db::mock::DatabaseMock;
144+
use reth_node_api::FullNodeTypesAdapter;
145+
use reth_node_builder::common::WithConfigs;
146+
use reth_node_core::node_config::NodeConfig;
147+
use reth_primitives_traits::{
148+
transaction::error::InvalidTransactionError, GotExpected, GotExpectedBoxed,
149+
};
150+
use reth_provider::{
151+
noop::NoopProvider,
152+
test_utils::{ExtendedAccount, MockEthProvider},
153+
};
154+
use reth_scroll_chainspec::{ScrollChainSpec, SCROLL_DEV, SCROLL_MAINNET};
155+
use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives};
156+
use reth_scroll_txpool::ScrollPooledTransaction;
157+
use reth_tasks::TaskManager;
158+
use reth_transaction_pool::{
159+
blobstore::NoopBlobStore,
160+
error::{InvalidPoolTransactionError, PoolErrorKind},
161+
PoolConfig, TransactionOrigin, TransactionPool,
162+
};
163+
use scroll_alloy_consensus::ScrollTxEnvelope;
164+
use scroll_alloy_evm::curie::L1_GAS_PRICE_ORACLE_ADDRESS;
165+
166+
async fn pool() -> (
167+
ScrollTransactionPool<NoopProvider<ScrollChainSpec, ScrollPrimitives>, DiskFileBlobStore>,
168+
TaskManager,
169+
) {
170+
let handle = tokio::runtime::Handle::current();
171+
let manager = TaskManager::new(handle);
172+
let config = WithConfigs {
173+
config: NodeConfig::new(SCROLL_MAINNET.clone()),
174+
toml_config: Default::default(),
175+
};
176+
177+
let pool_builder = ScrollPoolBuilder::<ScrollPooledTransaction>::default();
178+
let ctx = BuilderContext::<
179+
FullNodeTypesAdapter<
180+
ScrollNode,
181+
DatabaseMock,
182+
NoopProvider<ScrollChainSpec, ScrollPrimitives>,
183+
>,
184+
>::new(
185+
Head::default(),
186+
NoopProvider::new(SCROLL_MAINNET.clone()),
187+
manager.executor(),
188+
config,
189+
);
190+
(pool_builder.build_pool(&ctx).await.unwrap(), manager)
191+
}
192+
193+
#[tokio::test]
194+
async fn test_validate_one_oversized_transaction() {
195+
// create the pool.
196+
let (pool, manager) = pool().await;
197+
let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked(
198+
TxLegacy { gas_limit: 21_000, ..Default::default() },
199+
Signature::new(U256::ZERO, U256::ZERO, false),
200+
Default::default(),
201+
));
202+
203+
// Create a pool transaction with an encoded length of 123,904 bytes.
204+
let pool_tx = ScrollPooledTransaction::new(
205+
Recovered::new_unchecked(tx, Default::default()),
206+
121 * 1024,
207+
);
208+
209+
// add the transaction to the pool and expect an `OversizedData` error.
210+
let err = pool.add_transaction(TransactionOrigin::Local, pool_tx).await.unwrap_err();
211+
assert!(matches!(
212+
err.kind,
213+
PoolErrorKind::InvalidTransaction(
214+
InvalidPoolTransactionError::OversizedData(x, y,)
215+
) if x == 121*1024 && y == 120*1024,
216+
));
217+
218+
// explicitly drop the manager here otherwise the `TransactionValidationTaskExecutor` will
219+
// drop all validation tasks.
220+
drop(manager);
221+
}
222+
223+
#[tokio::test]
224+
async fn test_validate_one_rollup_fee_exceeds_balance() {
225+
// create the client.
226+
let handle = tokio::runtime::Handle::current();
227+
let manager = TaskManager::new(handle);
228+
let blob_store = NoopBlobStore::default();
229+
let signer = Default::default();
230+
let client =
231+
MockEthProvider::<ScrollPrimitives, _>::new().with_chain_spec(SCROLL_DEV.clone());
232+
let hash = B256::random();
233+
234+
// load a header, block, signer and the L1_GAS_PRICE_ORACLE_ADDRESS storage.
235+
client.add_header(hash, Header::default());
236+
client.add_block(hash, ScrollBlock::default());
237+
client.add_account(signer, ExtendedAccount::new(0, U256::from(400_000)));
238+
client.add_account(
239+
L1_GAS_PRICE_ORACLE_ADDRESS,
240+
ExtendedAccount::new(0, U256::from(400_000)).extend_storage(
241+
(0u8..8).map(|k| (B256::from(U256::from(k)), U256::from(u64::MAX))),
242+
),
243+
);
244+
245+
// create the validation task.
246+
let validator = TransactionValidationTaskExecutor::eth_builder(client)
247+
.no_eip4844()
248+
.build_with_tasks(manager.executor(), blob_store)
249+
.map(|validator| {
250+
ScrollTransactionValidator::new(validator).require_l1_data_gas_fee(true)
251+
});
252+
253+
// create the pool.
254+
let pool = ScrollTransactionPool::new(
255+
validator,
256+
CoinbaseTipOrdering::<ScrollPooledTransaction>::default(),
257+
NoopBlobStore::default(),
258+
PoolConfig::default(),
259+
);
260+
261+
// prepare a transaction with random input.
262+
let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked(
263+
TxLegacy {
264+
gas_limit: 55_000,
265+
gas_price: 7,
266+
input: Bytes::from(random_iter::<u8>().take(100).collect::<Vec<_>>()),
267+
..Default::default()
268+
},
269+
Signature::new(U256::ZERO, U256::ZERO, false),
270+
Default::default(),
271+
));
272+
let pool_tx =
273+
ScrollPooledTransaction::new(Recovered::new_unchecked(tx, signer), 120 * 1024);
274+
275+
// add the transaction in the pool and expect to hit `InsufficientFunds` error.
276+
let err = pool.add_transaction(TransactionOrigin::Local, pool_tx).await.unwrap_err();
277+
assert!(matches!(
278+
err.kind,
279+
PoolErrorKind::InvalidTransaction(
280+
InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(GotExpectedBoxed(expected)))
281+
) if *expected == GotExpected{ got: U256::from(400000), expected: U256::from_limbs([384999, 1, 0, 0]) }
282+
));
283+
284+
// explicitly drop the manager here otherwise the `TransactionValidationTaskExecutor` will
285+
// drop all validation tasks.
286+
drop(manager);
287+
}
288+
}

crates/scroll/node/src/test_utils.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use reth_node_api::NodeTypesWithDBAdapter;
99

1010
use reth_payload_builder::EthPayloadBuilderAttributes;
1111
use reth_provider::providers::BlockchainProvider;
12-
use reth_scroll_chainspec::ScrollChainSpecBuilder;
12+
use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpecBuilder};
1313
use reth_tasks::TaskManager;
1414
use scroll_alloy_rpc_types_engine::BlockDataHint;
1515
use std::sync::Arc;
@@ -31,10 +31,12 @@ pub async fn setup(
3131
reth_e2e_test_utils::setup_engine(
3232
num_nodes,
3333
Arc::new(
34-
ScrollChainSpecBuilder::scroll_mainnet()
35-
.genesis(genesis)
36-
.euclid_v2_activated()
37-
.build(Default::default()),
34+
ScrollChainSpecBuilder::scroll_mainnet().genesis(genesis).euclid_v2_activated().build(
35+
ScrollChainConfig {
36+
max_tx_payload_bytes_per_block: 120 * 1024,
37+
..Default::default()
38+
},
39+
),
3840
),
3941
is_dev,
4042
Default::default(),

0 commit comments

Comments
 (0)