diff --git a/crates/protocol/derive/src/attributes/stateful.rs b/crates/protocol/derive/src/attributes/stateful.rs index 03415ca6b7..aab848d40b 100644 --- a/crates/protocol/derive/src/attributes/stateful.rs +++ b/crates/protocol/derive/src/attributes/stateful.rs @@ -13,7 +13,7 @@ use alloy_rlp::Encodable; use alloy_rpc_types_engine::PayloadAttributes; use async_trait::async_trait; use kona_genesis::RollupConfig; -use kona_hardforks::{Hardfork, Hardforks}; +use kona_hardforks::{Hardfork, Hardforks, Isthmus}; use kona_protocol::{ DEPOSIT_EVENT_ABI_HASH, L1BlockInfoTx, L2BlockInfo, closing_deposit_context_tx, decode_deposit, }; @@ -130,6 +130,9 @@ where )); } + let mut upgrade_gas = + u64::from_be_bytes(alloy_primitives::U64::from(sys_config.gas_limit).to_be_bytes()); + let mut upgrade_transactions: Vec = vec![]; if self.rollup_cfg.is_ecotone_active(next_l2_time) && !self.rollup_cfg.is_ecotone_active(l2_parent.block_info.timestamp) @@ -144,6 +147,7 @@ where if self.rollup_cfg.is_isthmus_active(next_l2_time) && !self.rollup_cfg.is_isthmus_active(l2_parent.block_info.timestamp) { + upgrade_gas += Isthmus::deposits().map(|tx| tx.gas_limit).sum::(); upgrade_transactions.append(&mut Hardforks::ISTHMUS.txs().collect()); } @@ -197,9 +201,7 @@ where }, transactions: Some(txs), no_tx_pool: Some(true), - gas_limit: Some(u64::from_be_bytes( - alloy_primitives::U64::from(sys_config.gas_limit).to_be_bytes(), - )), + gas_limit: Some(upgrade_gas), eip_1559_params: sys_config.eip_1559_params( &self.rollup_cfg, l2_parent.block_info.timestamp, @@ -250,7 +252,7 @@ mod tests { }; use alloc::vec; use alloy_consensus::Header; - use alloy_primitives::{B256, Log, LogData, U64, U256}; + use alloy_primitives::{B64, B256, Log, LogData, U64, U256}; use kona_genesis::{HardForkConfig, SystemConfig}; use kona_protocol::{BlockInfo, DepositError}; @@ -628,4 +630,55 @@ mod tests { assert_eq!(payload.transactions.as_ref().unwrap().len(), 10); assert_eq!(payload, expected); } + + #[tokio::test] + async fn test_prepare_payload_with_isthmus() { + let block_time = 2; + let timestamp = 100; + let cfg = Arc::new(RollupConfig { + block_time, + hardforks: HardForkConfig { isthmus_time: Some(102), ..Default::default() }, + ..Default::default() + }); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::default(); + fetcher.insert(l2_number, SystemConfig::default()); + let mut provider = TestChainProvider::default(); + let header = Header { timestamp, ..Default::default() }; + let prev_randao = header.mix_hash; + let hash = header.hash_slow(); + provider.insert_header(hash, header); + let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider); + let epoch = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let next_l2_time = l2_parent.block_info.timestamp + block_time; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + let gas_limit = u64::from_be_bytes( + alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), + ) + 3040000; + let expected = OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: next_l2_time, + prev_randao, + suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, + parent_beacon_block_root: Some(B256::ZERO), + withdrawals: Some(vec![]), + }, + transactions: payload.transactions.clone(), + no_tx_pool: Some(true), + gas_limit: Some(gas_limit), + eip_1559_params: Some(B64::ZERO), + }; + assert_eq!(payload.transactions.as_ref().unwrap().len(), 18); + assert_eq!(payload, expected); + } } diff --git a/crates/protocol/genesis/src/rollup.rs b/crates/protocol/genesis/src/rollup.rs index 76bc26e4a0..dbcb7ef403 100644 --- a/crates/protocol/genesis/src/rollup.rs +++ b/crates/protocol/genesis/src/rollup.rs @@ -251,6 +251,20 @@ impl RollupConfig { self.da_challenge_address.is_some_and(|addr| !addr.is_zero()) } + /// Returns true if the specified block is the first block subject to the Isthmus upgrade. + pub fn is_isthmus_activation_block(&self, l2_block_time: u64) -> bool { + self.is_isthmus_active(l2_block_time) && + l2_block_time >= self.block_time && + !self.is_isthmus_active(l2_block_time - self.block_time) + } + + /// Returns true if the specified block is the first block subject to the Interop upgrade. + pub fn is_interop_activation_block(&self, l2_block_time: u64) -> bool { + self.is_interop_active(l2_block_time) && + l2_block_time >= self.block_time && + !self.is_interop_active(l2_block_time - self.block_time) + } + /// Returns the max sequencer drift for the given timestamp. pub fn max_sequencer_drift(&self, timestamp: u64) -> u64 { if self.is_fjord_active(timestamp) { diff --git a/crates/protocol/protocol/src/batch/single.rs b/crates/protocol/protocol/src/batch/single.rs index a6d4989fa4..f8aadcf043 100644 --- a/crates/protocol/protocol/src/batch/single.rs +++ b/crates/protocol/protocol/src/batch/single.rs @@ -122,6 +122,14 @@ impl SingleBatch { return BatchValidity::Drop; } + // Future forks that contain upgrade transactions must be added here. + let contains_txs = self.transactions.iter().any(|tx| !tx.is_empty()); + let isthmus_act_block = cfg.is_isthmus_activation_block(self.timestamp); + let interop_act_block = cfg.is_interop_activation_block(self.timestamp); + if contains_txs && (isthmus_act_block || interop_act_block) { + return BatchValidity::Drop; + } + // Check if we ran out of sequencer time drift let max_drift = cfg.max_sequencer_drift(batch_origin.timestamp); let max = if let Some(max) = batch_origin.timestamp.checked_add(max_drift) { @@ -466,6 +474,82 @@ mod tests { ); } + #[test] + fn test_check_batch_activation_block_dropped_isthmus() { + // Use the example transaction + let mut transactions = example_transactions(); + + // Extend the transactions with the 7702 transaction + let eip_7702_tx = eip_7702_tx(); + let sig = PrimitiveSignature::test_signature(); + let tx_signed = eip_7702_tx.into_signed(sig); + let envelope: TxEnvelope = tx_signed.into(); + let encoded = envelope.encoded_2718(); + transactions.push(encoded.into()); + + // Construct a basic `SingleBatch` + let parent_hash = BlockHash::ZERO; + let epoch_num = 1; + let epoch_hash = BlockHash::ZERO; + let timestamp = 2; + + let single_batch = + SingleBatch { parent_hash, epoch_num, epoch_hash, timestamp, transactions }; + + // Notice: Isthmus is active. + let cfg = RollupConfig { + max_sequencer_drift: 1, + block_time: 1, + hardforks: HardForkConfig { isthmus_time: Some(1), ..Default::default() }, + ..Default::default() + }; + let l1_blocks = vec![BlockInfo::default(), BlockInfo::default()]; + let l2_safe_head = L2BlockInfo { + block_info: BlockInfo { timestamp: 1, ..Default::default() }, + ..Default::default() + }; + let inclusion_block = BlockInfo::default(); + assert_eq!( + single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block), + BatchValidity::Drop + ); + } + + #[test] + fn test_check_batch_valid_activation_block_isthmus() { + // Construct a basic `SingleBatch` + let parent_hash = BlockHash::ZERO; + let epoch_num = 1; + let epoch_hash = BlockHash::ZERO; + let timestamp = 2; + + let single_batch = SingleBatch { + parent_hash, + epoch_num, + epoch_hash, + timestamp, + transactions: Default::default(), + }; + + // Notice: Isthmus is active. + let cfg = RollupConfig { + max_sequencer_drift: 2, + block_time: 1, + hardforks: HardForkConfig { isthmus_time: Some(1), ..Default::default() }, + ..Default::default() + }; + let l1_blocks = vec![BlockInfo::default(), BlockInfo::default()]; + let l2_safe_head = L2BlockInfo { + block_info: BlockInfo { timestamp: 1, ..Default::default() }, + ..Default::default() + }; + let inclusion_block = BlockInfo::default(); + assert_eq!( + single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block), + BatchValidity::Accept + ); + } + #[test] fn test_check_batch_accept_7702_post_isthmus() { // Use the example transaction