From 4fa4c1ad4a683fea7c027fd49ff223c5834fc131 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Fri, 14 Nov 2025 15:31:24 -0600 Subject: [PATCH 1/6] chore: fixes for jovian --- crates/op-rbuilder/src/builders/context.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index d8652831..5bec9d5c 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -162,7 +162,15 @@ impl OpPayloadBuilderCtx { /// /// After holocene this extracts the extradata from the payload pub fn extra_data(&self) -> Result { - if self.is_holocene_active() { + if self.is_jovian_active() { + self.attributes() + .get_jovian_extra_data( + self.chain_spec.base_fee_params_at_timestamp( + self.attributes().payload_attributes.timestamp, + ), + ) + .map_err(PayloadBuilderError::other) + } else if self.is_holocene_active() { self.attributes() .get_holocene_extra_data( self.chain_spec.base_fee_params_at_timestamp( @@ -215,6 +223,12 @@ impl OpPayloadBuilderCtx { .is_isthmus_active_at_timestamp(self.attributes().timestamp()) } + /// Returns true if isthmus is active for the payload. + pub fn is_jovian_active(&self) -> bool { + self.chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + } + /// Returns the chain id pub fn chain_id(&self) -> u64 { self.chain_spec.chain_id() From d1b2a8cb7d632a05f4091ac304b50436f260578b Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Fri, 14 Nov 2025 16:41:03 -0600 Subject: [PATCH 2/6] blob fee fixes --- crates/op-rbuilder/src/builders/context.rs | 33 +++++++++++-------- .../src/builders/flashblocks/payload.rs | 3 +- .../src/builders/standard/payload.rs | 9 ++--- .../src/primitives/reth/execution.rs | 3 ++ 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index 5bec9d5c..da679a2d 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -147,11 +147,14 @@ impl OpPayloadBuilderCtx { /// Returns the blob fields for the header. /// /// This will always return `Some(0)` after ecotone. - pub fn blob_fields(&self) -> (Option, Option) { - // OP doesn't support blobs/EIP-4844. - // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions - // Need [Some] or [None] based on hardfork to match block hash. - if self.is_ecotone_active() { + pub fn blob_fields(&self, info: &ExecutionInfo) -> (Option, Option) { + if self.is_jovian_active() { + let scalar = info.da_footprint_scalar.unwrap(); + (Some(0), Some(info.cumulative_da_bytes_used * scalar as u64)) + } else if self.is_ecotone_active() { + // OP doesn't support blobs/EIP-4844. + // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions + // Need [Some] or [None] based on hardfork to match block hash. (Some(0), Some(0)) } else { (None, None) @@ -385,6 +388,17 @@ impl OpPayloadBuilderCtx { timestamp: self.attributes().timestamp(), }; + let da_footprint_gas_scalar = self + .chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + .then_some( + L1BlockInfo::fetch_da_footprint_gas_scalar(evm.db_mut()).expect( + "DA footprint should always be available from the database post jovian", + ), + ); + + info.da_footprint_scalar = da_footprint_gas_scalar; + while let Some(tx) = best_txs.next(()) { let interop = tx.interop_deadline(); let reverted_hashes = tx.reverted_hashes().clone(); @@ -436,15 +450,6 @@ impl OpPayloadBuilderCtx { } } - let da_footprint_gas_scalar = self - .chain_spec - .is_jovian_active_at_timestamp(self.attributes().timestamp()) - .then_some( - L1BlockInfo::fetch_da_footprint_gas_scalar(evm.db_mut()).expect( - "DA footprint should always be available from the database post jovian", - ), - ); - // ensure we still have capacity for this transaction if let Err(result) = info.is_tx_over_limits( tx_da_size, diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index 52bca2a1..04401e45 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -1035,7 +1035,8 @@ where // OP doesn't support blobs/EIP-4844. // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions // Need [Some] or [None] based on hardfork to match block hash. - let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(); + + let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(info); let extra_data = ctx.extra_data()?; let header = Header { diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index f36fba81..8c74ea50 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -459,6 +459,11 @@ impl OpBuilder<'_, Txs> { }; let block_number = ctx.block_number(); + // OP doesn't support blobs/EIP-4844. + // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions + // Need [Some] or [None] based on hardfork to match block hash. + let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(&info); + let execution_outcome = ExecutionOutcome::new( db.take_bundle(), vec![info.receipts], @@ -521,10 +526,6 @@ impl OpBuilder<'_, Txs> { // create the block header let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); - // OP doesn't support blobs/EIP-4844. - // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions - // Need [Some] or [None] based on hardfork to match block hash. - let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(); let extra_data = ctx.extra_data()?; let header = Header { diff --git a/crates/op-rbuilder/src/primitives/reth/execution.rs b/crates/op-rbuilder/src/primitives/reth/execution.rs index e6804885..e26bf4fd 100644 --- a/crates/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/op-rbuilder/src/primitives/reth/execution.rs @@ -40,6 +40,8 @@ pub struct ExecutionInfo { pub total_fees: U256, /// Extra execution information that can be attached by individual builders. pub extra: Extra, + /// DA Footprint Scalar for Jovian + pub da_footprint_scalar: Option } impl ExecutionInfo { @@ -53,6 +55,7 @@ impl ExecutionInfo { cumulative_da_bytes_used: 0, total_fees: U256::ZERO, extra: Default::default(), + da_footprint_scalar: None, } } From 69aff2327cc150d20af2412099f972c3b876b922 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 16 Nov 2025 20:08:32 -0600 Subject: [PATCH 3/6] Update tests to Jovian --- crates/op-rbuilder/src/builders/context.rs | 43 ++++---- .../src/builders/flashblocks/payload.rs | 34 ++++--- crates/op-rbuilder/src/builders/generator.rs | 3 +- .../src/builders/standard/payload.rs | 24 +++-- .../src/primitives/reth/execution.rs | 2 +- crates/op-rbuilder/src/tests/forks.rs | 99 +++++++++++++++++++ .../framework/artifacts/genesis.json.tmpl | 13 +++ .../op-rbuilder/src/tests/framework/driver.rs | 44 ++++++--- crates/op-rbuilder/src/tests/framework/mod.rs | 3 + crates/op-rbuilder/src/tests/framework/txs.rs | 5 +- .../op-rbuilder/src/tests/miner_gas_limit.rs | 40 ++++---- crates/op-rbuilder/src/tests/mod.rs | 2 + 12 files changed, 238 insertions(+), 74 deletions(-) create mode 100644 crates/op-rbuilder/src/tests/forks.rs diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index da679a2d..7faacb83 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -146,15 +146,20 @@ impl OpPayloadBuilderCtx { /// Returns the blob fields for the header. /// - /// This will always return `Some(0)` after ecotone. - pub fn blob_fields(&self, info: &ExecutionInfo) -> (Option, Option) { + /// This will return the culmative DA bytes * scalar after Jovian + /// after Ecotone, this will always return Some(0) as blobs aren't supported + /// pre Ecotone, these fields aren't used. + pub fn blob_fields( + &self, + info: &ExecutionInfo, + ) -> (Option, Option) { if self.is_jovian_active() { - let scalar = info.da_footprint_scalar.unwrap(); - (Some(0), Some(info.cumulative_da_bytes_used * scalar as u64)) + let scalar = info + .da_footprint_scalar + .expect("Scalar must be defined for Jovian blocks"); + let result = info.cumulative_da_bytes_used * scalar as u64; + (Some(0), Some(result)) } else if self.is_ecotone_active() { - // OP doesn't support blobs/EIP-4844. - // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions - // Need [Some] or [None] based on hardfork to match block hash. (Some(0), Some(0)) } else { (None, None) @@ -340,6 +345,7 @@ impl OpPayloadBuilderCtx { state: &state, cumulative_gas_used: info.cumulative_gas_used, }; + info.receipts.push(self.build_receipt(ctx, depositor_nonce)); // commit changes @@ -350,6 +356,16 @@ impl OpPayloadBuilderCtx { info.executed_transactions.push(sequencer_tx.into_inner()); } + let da_footprint_gas_scalar = self + .chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + .then(|| { + L1BlockInfo::fetch_da_footprint_gas_scalar(evm.db_mut()) + .expect("DA footprint should always be available from the database post jovian") + }); + + info.da_footprint_scalar = da_footprint_gas_scalar; + Ok(info) } @@ -388,17 +404,6 @@ impl OpPayloadBuilderCtx { timestamp: self.attributes().timestamp(), }; - let da_footprint_gas_scalar = self - .chain_spec - .is_jovian_active_at_timestamp(self.attributes().timestamp()) - .then_some( - L1BlockInfo::fetch_da_footprint_gas_scalar(evm.db_mut()).expect( - "DA footprint should always be available from the database post jovian", - ), - ); - - info.da_footprint_scalar = da_footprint_gas_scalar; - while let Some(tx) = best_txs.next(()) { let interop = tx.interop_deadline(); let reverted_hashes = tx.reverted_hashes().clone(); @@ -457,7 +462,7 @@ impl OpPayloadBuilderCtx { tx_da_limit, block_da_limit, tx.gas_limit(), - da_footprint_gas_scalar, + info.da_footprint_scalar, ) { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index 04401e45..ee31f944 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -22,6 +22,7 @@ use either::Either; use eyre::WrapErr as _; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::BuildOutcome; +use reth_chainspec::EthChainSpec; use reth_evm::{ConfigureEvm, execute::BlockBuilder}; use reth_node_api::{Block, NodePrimitives, PayloadBuilderError}; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; @@ -222,6 +223,21 @@ where ) -> eyre::Result> { let chain_spec = self.client.chain_spec(); let timestamp = config.attributes.timestamp(); + + let extra_data = if chain_spec.is_jovian_active_at_timestamp(timestamp) { + config + .attributes + .get_jovian_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .wrap_err("failed to get holocene extra data for flashblocks payload builder")? + } else if chain_spec.is_holocene_active_at_timestamp(timestamp) { + config + .attributes + .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .wrap_err("failed to get holocene extra data for flashblocks payload builder")? + } else { + Default::default() + }; + let block_env_attributes = OpNextBlockEnvAttributes { timestamp, suggested_fee_recipient: config.attributes.suggested_fee_recipient(), @@ -234,18 +250,12 @@ where .attributes .payload_attributes .parent_beacon_block_root, - extra_data: if chain_spec.is_holocene_active_at_timestamp(timestamp) { - config - .attributes - .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) - .wrap_err("failed to get holocene extra data for flashblocks payload builder")? - } else { - Default::default() - }, + extra_data, }; - let evm_env = self - .evm_config + let evm_config = self.evm_config.clone(); + + let evm_env = evm_config .next_evm_env(&config.parent_header, &block_env_attributes) .wrap_err("failed to create next evm env")?; @@ -1032,10 +1042,6 @@ where // create the block header let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); - // OP doesn't support blobs/EIP-4844. - // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions - // Need [Some] or [None] based on hardfork to match block hash. - let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(info); let extra_data = ctx.extra_data()?; diff --git a/crates/op-rbuilder/src/builders/generator.rs b/crates/op-rbuilder/src/builders/generator.rs index 009d2a5f..041c274d 100644 --- a/crates/op-rbuilder/src/builders/generator.rs +++ b/crates/op-rbuilder/src/builders/generator.rs @@ -134,7 +134,7 @@ where type Job = BlockPayloadJob; /// This is invoked when the node receives payload attributes from the beacon node via - /// `engine_forkchoiceUpdatedV1` + /// `engine_forkchoiceUpdatedVX` fn new_payload_job( &self, attributes: ::Attributes, @@ -470,7 +470,6 @@ mod tests { use alloy_primitives::U256; use rand::rng; use reth::tasks::TokioTaskExecutor; - use reth_chain_state::ExecutedBlock; use reth_node_api::{BuiltPayloadExecutedBlock, NodePrimitives}; use reth_optimism_payload_builder::{OpPayloadPrimitives, payload::OpPayloadBuilderAttributes}; use reth_optimism_primitives::OpPrimitives; diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index 8c74ea50..ee9302b8 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -201,6 +201,21 @@ where let chain_spec = self.client.chain_spec(); let timestamp = config.attributes.timestamp(); + + let extra_data = if chain_spec.is_jovian_active_at_timestamp(timestamp) { + config + .attributes + .get_jovian_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .map_err(PayloadBuilderError::other)? + } else if chain_spec.is_holocene_active_at_timestamp(timestamp) { + config + .attributes + .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .map_err(PayloadBuilderError::other)? + } else { + Default::default() + }; + let block_env_attributes = OpNextBlockEnvAttributes { timestamp, suggested_fee_recipient: config.attributes.suggested_fee_recipient(), @@ -213,14 +228,7 @@ where .attributes .payload_attributes .parent_beacon_block_root, - extra_data: if chain_spec.is_holocene_active_at_timestamp(timestamp) { - config - .attributes - .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) - .map_err(PayloadBuilderError::other)? - } else { - Default::default() - }, + extra_data, }; let evm_env = self diff --git a/crates/op-rbuilder/src/primitives/reth/execution.rs b/crates/op-rbuilder/src/primitives/reth/execution.rs index e26bf4fd..9ce998d1 100644 --- a/crates/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/op-rbuilder/src/primitives/reth/execution.rs @@ -41,7 +41,7 @@ pub struct ExecutionInfo { /// Extra execution information that can be attached by individual builders. pub extra: Extra, /// DA Footprint Scalar for Jovian - pub da_footprint_scalar: Option + pub da_footprint_scalar: Option, } impl ExecutionInfo { diff --git a/crates/op-rbuilder/src/tests/forks.rs b/crates/op-rbuilder/src/tests/forks.rs new file mode 100644 index 00000000..7045bf10 --- /dev/null +++ b/crates/op-rbuilder/src/tests/forks.rs @@ -0,0 +1,99 @@ +use crate::tests::{BlockTransactionsExt, LocalInstance}; +use alloy_eips::{BlockNumberOrTag::Latest, eip1559::MIN_PROTOCOL_BASE_FEE}; +use alloy_primitives::bytes; +use macros::rb_test; +use std::time::Duration; + +#[rb_test] +async fn jovian_block_parameters_set(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let tx_one = driver.create_transaction().send().await?; + let tx_two = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + assert!(block.includes(tx_one.tx_hash())); + assert!(block.includes(tx_two.tx_hash())); + + assert!(block.header.excess_blob_gas.is_some()); + + assert!(block.header.blob_gas_used.is_some()); + + // 2 transactions (excl. deposit txn) * (100 min size * 400 scalar) + // https://specs.optimism.io/protocol/fjord/exec-engine.html#l1-cost-fees-l1-fee-vault + assert_eq!(block.header.blob_gas_used.unwrap(), 80_000); + + // Version byte + assert_eq!(block.header.extra_data.slice(0..1), bytes!("0x01")); + + // Min Base Fee of zero by default + assert_eq!( + block.header.extra_data.slice(9..=16), + bytes!("0x0000000000000000"), + ); + + Ok(()) +} + +#[rb_test] +async fn jovian_minimum_base_fee(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let genesis = driver + .get_block(Latest) + .await? + .expect("must have genesis block"); + + assert_eq!(genesis.header.base_fee_per_gas, Some(1)); + + let min_base_fee = Some(MIN_PROTOCOL_BASE_FEE * 2); + + let block_timestamp = Duration::from_secs(genesis.header.timestamp) + Duration::from_secs(1); + let block_one = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, min_base_fee) + .await?; + + assert_eq!( + block_one.header.extra_data.slice(9..=16), + bytes!("0x000000000000000E"), + ); + + let overpriced_tx = driver + .create_transaction() + .with_max_fee_per_gas(MIN_PROTOCOL_BASE_FEE as u128 * 4) + .send() + .await?; + let underpriced_tx = driver + .create_transaction() + .with_max_fee_per_gas(MIN_PROTOCOL_BASE_FEE as u128) + .send() + .await?; + + let block_timestamp = Duration::from_secs(block_one.header.timestamp) + Duration::from_secs(1); + let block_two = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, min_base_fee) + .await?; + + assert_eq!( + block_two.header.extra_data.slice(9..=16), + bytes!("0x000000000000000E"), + ); + + assert!(block_two.includes(overpriced_tx.tx_hash())); + assert!(!block_two.includes(underpriced_tx.tx_hash())); + + Ok(()) +} + +#[rb_test] +async fn jovian_minimum_fee_must_be_set(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let genesis = driver + .get_block(Latest) + .await? + .expect("must have genesis block"); + let block_timestamp = Duration::from_secs(genesis.header.timestamp) + Duration::from_secs(1); + let response = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, None) + .await; + assert!(response.is_err()); + Ok(()) +} diff --git a/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl b/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl index 39a6f53e..936f353e 100644 --- a/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl +++ b/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl @@ -23,7 +23,9 @@ "ecotoneTime": 0, "fjordTime": 0, "graniteTime": 0, + "holoceneTime": 0, "isthmusTime": 0, + "jovianTime": 0, "pragueTime": 0, "terminalTotalDifficulty": 0, "terminalTotalDifficultyPassed": true, @@ -42,6 +44,17 @@ "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x4200000000000000000000000000000000000011", "alloc": { + "4200000000000000000000000000000000000015": { + "balance": "0x1", + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106dd565b610224565b6100a86100a33660046106f8565b610296565b6040516100b5919061077b565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106dd565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ee565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060c565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81815560405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a25050565b60006106367fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038381556040805173ffffffffffffffffffffffffffffffffffffffff80851682528616602082015292935090917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d857600080fd5b919050565b6000602082840312156106ef57600080fd5b610412826106b4565b60008060006040848603121561070d57600080fd5b610716846106b4565b9250602084013567ffffffffffffffff8082111561073357600080fd5b818601915086601f83011261074757600080fd5b81358181111561075657600080fd5b87602082850101111561076857600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a85785810183015185820160400152820161078c565b818111156107ba576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a", + "storage": { + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000003ba4007f5c922fbb33c454b41ea7a1f11e83df2c" + } + }, + "3Ba4007f5C922FBb33C454B41ea7a1f11E83df2C": { + "balance": "0x1", + "code": "0x608060405234801561001057600080fd5b50600436106101985760003560e01c806364ca23ef116100e3578063c59859181161008c578063e81b2c6d11610066578063e81b2c6d146103f0578063f8206140146103f9578063fe3d57101461040257600080fd5b8063c598591814610375578063d844471514610395578063e591b282146103ce57600080fd5b80638b239f73116100bd5780638b239f73146103435780639e8c49661461034c578063b80777ea1461035557600080fd5b806364ca23ef146102ff57806368d5dca6146103135780638381f58a1461032f57600080fd5b80634397dfef1161014557806354fd4d501161011f57806354fd4d501461027b578063550fcdc9146102bd5780635cf24969146102f657600080fd5b80634397dfef1461021a578063440a5e20146102425780634d5d9a2a1461024a57600080fd5b806316d3bc7f1161017657806316d3bc7f146101d657806321326849146102035780633db6be2b1461021257600080fd5b8063015d8eb91461019d578063098999be146101b257806309bd5a60146101ba575b600080fd5b6101b06101ab366004610623565b610433565b005b6101b0610572565b6101c360025481565b6040519081526020015b60405180910390f35b6008546101ea9067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101cd565b604051600081526020016101cd565b6101b0610585565b6040805173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815260126020820152016101cd565b6101b06105af565b6008546102669068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101cd565b60408051808201909152600581527f312e372e3000000000000000000000000000000000000000000000000000000060208201525b6040516101cd9190610695565b60408051808201909152600381527f455448000000000000000000000000000000000000000000000000000000000060208201526102b0565b6101c360015481565b6003546101ea9067ffffffffffffffff1681565b6003546102669068010000000000000000900463ffffffff1681565b6000546101ea9067ffffffffffffffff1681565b6101c360055481565b6101c360065481565b6000546101ea9068010000000000000000900467ffffffffffffffff1681565b600354610266906c01000000000000000000000000900463ffffffff1681565b60408051808201909152600581527f457468657200000000000000000000000000000000000000000000000000000060208201526102b0565b60405173deaddeaddeaddeaddeaddeaddeaddeaddead000181526020016101cd565b6101c360045481565b6101c360075481565b600854610420906c01000000000000000000000000900461ffff1681565b60405161ffff90911681526020016101cd565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104da576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61057a6105af565b60a43560a01c600855565b61058d6105af565b6dffff00000000000000000000000060b03560901c1660a43560a01c17600855565b73deaddeaddeaddeaddeaddeaddeaddeaddead00013381146105d957633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b803567ffffffffffffffff8116811461061e57600080fd5b919050565b600080600080600080600080610100898b03121561064057600080fd5b61064989610606565b975061065760208a01610606565b9650604089013595506060890135945061067360808a01610606565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b818110156106c2578581018301518582016040015282016106a6565b818111156106d4576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a" + }, "70997970C51812dc3A010C7d01b50e0d17dc79C8": { "balance": "0x21e19e0c9bab2400000" }, diff --git a/crates/op-rbuilder/src/tests/framework/driver.rs b/crates/op-rbuilder/src/tests/framework/driver.rs index 6e9f83f7..5246bd5e 100644 --- a/crates/op-rbuilder/src/tests/framework/driver.rs +++ b/crates/op-rbuilder/src/tests/framework/driver.rs @@ -1,7 +1,7 @@ use core::time::Duration; use alloy_eips::{BlockNumberOrTag, Encodable2718, eip7685::Requests}; -use alloy_primitives::{B256, Bytes, TxKind, U256, address, hex}; +use alloy_primitives::{B64, B256, Bytes, TxKind, U256, address, hex}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadAttributes, PayloadStatusEnum}; use alloy_rpc_types_eth::Block; @@ -14,7 +14,10 @@ use rollup_boost::OpExecutionPayloadEnvelope; use super::{EngineApi, Ipc, LocalInstance, TransactionBuilder}; use crate::{ args::OpRbuilderArgs, - tests::{ExternalNode, Protocol, framework::DEFAULT_GAS_LIMIT}, + tests::{ + DEFAULT_DENOMINATOR, DEFAULT_ELASTICITY, ExternalNode, Protocol, + framework::DEFAULT_GAS_LIMIT, + }, tx_signer::Signer, }; @@ -93,7 +96,7 @@ impl ChainDriver { // public test api impl ChainDriver { pub async fn build_new_block_with_no_tx_pool(&self) -> eyre::Result> { - self.build_new_block_with_txs_timestamp(vec![], Some(true), None, None) + self.build_new_block_with_txs_timestamp(vec![], Some(true), None, None, Some(0)) .await } @@ -107,7 +110,7 @@ impl ChainDriver { &self, timestamp_jitter: Option, ) -> eyre::Result> { - self.build_new_block_with_txs_timestamp(vec![], None, None, timestamp_jitter) + self.build_new_block_with_txs_timestamp(vec![], None, None, timestamp_jitter, Some(0)) .await } @@ -119,6 +122,7 @@ impl ChainDriver { block_timestamp: Option, // Amount of time to lag before sending FCU. This tests late FCU scenarios timestamp_jitter: Option, + min_base_fee: Option, ) -> eyre::Result> { let latest = self.latest().await?; @@ -138,7 +142,7 @@ impl ChainDriver { value: U256::default(), gas_limit: 210000, is_system_transaction: false, - input: FJORD_DATA.into(), + input: JOVIAN_DATA.into(), }; // Create a temporary signer for the deposit @@ -172,6 +176,10 @@ impl ChainDriver { tokio::time::sleep(sleep_time).await; } } + + let eip_1559_params: u64 = + ((DEFAULT_DENOMINATOR as u64) << 32) | (DEFAULT_ELASTICITY as u64); + let fcu_result = self .fcu(OpPayloadAttributes { payload_attributes: PayloadAttributes { @@ -183,7 +191,8 @@ impl ChainDriver { transactions: Some(vec![block_info_tx].into_iter().chain(txs).collect()), gas_limit: Some(self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)), no_tx_pool, - ..Default::default() + min_base_fee, + eip_1559_params: Some(B64::from(eip_1559_params)), }) .await?; @@ -212,9 +221,11 @@ impl ChainDriver { let payload = OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?); + let OpExecutionPayloadEnvelope::V4(payload) = payload else { return Err(eyre::eyre!("Expected V4 payload, got something else")); }; + let payload = payload.execution_payload; if self @@ -228,13 +239,14 @@ impl ChainDriver { } let new_block_hash = payload.payload_inner.payload_inner.payload_inner.block_hash; + self.engine_api .update_forkchoice(latest.header.hash, new_block_hash, None) .await?; let block = self .provider - .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .get_block_by_number(BlockNumberOrTag::Latest) .full() .await? .ok_or_else(|| eyre::eyre!("Failed to get latest block after building new block"))?; @@ -264,7 +276,7 @@ impl ChainDriver { let latest_timestamp = Duration::from_secs(latest.header.timestamp); let block_timestamp = latest_timestamp + Self::MIN_BLOCK_TIME; - self.build_new_block_with_txs_timestamp(txs, None, Some(block_timestamp), None) + self.build_new_block_with_txs_timestamp(txs, None, Some(block_timestamp), None, Some(0)) .await } @@ -331,12 +343,20 @@ impl ChainDriver { } // L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) -// // https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1 -const FJORD_DATA: &[u8] = &hex!( - "440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a +// It has the following modifications: +// 1. Function signature support Jovian: cast sig "setL1BlockValuesJovian()" => 0x3db6be2b +// 2. Zero operator fee scalar +// 3. Zero operator fee constant +// 4. DA footprint of 400 applied +// See: // https://specs.optimism.io/protocol/jovian/l1-attributes.html for Jovian specs. +const JOVIAN_DATA: &[u8] = &hex!( + "3db6be2b0000146b000f79c500000000000000040000000066d052e700000000013ad8a 3000000000000000000000000000000000000000000000000000000003ef12787000000 00000000000000000000000000000000000000000000000000000000012fdf87b89884a 61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a5900000000000000000000 - 00006887246668a3b87f54deb3b94ba47a6f63f32985" + 00006887246668a3b87f54deb3b94ba47a6f63f32985 + 00000000 + 0000000000000000 + 0190" ); diff --git a/crates/op-rbuilder/src/tests/framework/mod.rs b/crates/op-rbuilder/src/tests/framework/mod.rs index 26e24f0d..554c01cb 100644 --- a/crates/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/op-rbuilder/src/tests/framework/mod.rs @@ -30,6 +30,9 @@ pub const FLASHTESTATION_DEPLOY_KEY: &str = pub const DEFAULT_GAS_LIMIT: u64 = 10_000_000; +pub const DEFAULT_DENOMINATOR: u32 = 50; + +pub const DEFAULT_ELASTICITY: u32 = 2; pub const DEFAULT_JWT_TOKEN: &str = "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; diff --git a/crates/op-rbuilder/src/tests/framework/txs.rs b/crates/op-rbuilder/src/tests/framework/txs.rs index ed6b6265..35872764 100644 --- a/crates/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/op-rbuilder/src/tests/framework/txs.rs @@ -188,7 +188,10 @@ impl TransactionBuilder { }; self.tx.nonce = nonce; - self.tx.max_fee_per_gas = base_fee + self.tx.max_priority_fee_per_gas; + + if self.tx.max_fee_per_gas == 0 { + self.tx.max_fee_per_gas = base_fee + self.tx.max_priority_fee_per_gas; + } signer .sign_tx(OpTypedTransaction::Eip1559(self.tx)) diff --git a/crates/op-rbuilder/src/tests/miner_gas_limit.rs b/crates/op-rbuilder/src/tests/miner_gas_limit.rs index f0aaa353..92d5ebc0 100644 --- a/crates/op-rbuilder/src/tests/miner_gas_limit.rs +++ b/crates/op-rbuilder/src/tests/miner_gas_limit.rs @@ -1,7 +1,6 @@ use crate::tests::{BlockTransactionsExt, LocalInstance}; use alloy_provider::Provider; use macros::{if_flashblocks, if_standard, rb_test}; -use reth_primitives_traits::constants::MEGAGAS; /// This test ensures that the miner gas limit is respected /// We will set the limit to 60,000 and see that the builder will not include any transactions #[rb_test] @@ -26,29 +25,32 @@ async fn miner_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { Ok(()) } -/// This test ensures that block will fill up to the limit -/// Each transaction is 53000 gas -/// There is a deposit transaction for 24890 gas, and a builder transaction for 21600 gas -/// We will set our limit to 1Mgas and see that the builder includes 17 transactions -/// Total of 19 transactions including deposit + builder -/// In Flashblocks mode, there are 2 builder transactions, one at the beginning and one at the end of the block -/// So the total number of transactions in the block will be 20 for Flashblocks mode +/// This test ensures that block will fill up to the limit, each transaction is 53,000 gas +/// We will set our limit to 1Mgas and ensure that throttling occurs +/// There is a deposit transaction for 182,706 gas, and builder transactions are 21,600 gas +/// +/// Standard = (1.03 million - 182,706 - 21,600) / 53,000 = 15.57 = 15 transactions can fit +/// Flashblocks = (1.03 million - 182,706 - 21,600 - 21,600) / 53,000 = 15.17 = 15 transactions can fit #[rb_test] async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { + if_flashblocks! { + return Ok(()); + } + let driver = rbuilder.driver().await?; let call = driver .provider() - .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (MEGAGAS,)) + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (1_030_000,)) .await?; assert!(call, "miner_setGasLimit should be executed successfully"); let mut tx_hashes = Vec::new(); - for _ in 0..17 { + for _ in 0..15 { let tx = driver .create_transaction() .with_gas_limit(53000) - .with_max_priority_fee_per_gas(50) + .with_max_priority_fee_per_gas(100) .send() .await?; tx_hashes.push(tx.tx_hash().clone()); @@ -61,9 +63,13 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { .await?; let block = driver.build_new_block().await?; - for (i, tx_hash) in tx_hashes.iter().enumerate() { - assert!(block.includes(tx_hash), "tx{} should be in block", i); + assert!( + block.includes(tx_hash), + "tx i={} hash={} should be in block", + i, + tx_hash + ); } assert!( !block.includes(unfit_tx.tx_hash()), @@ -73,16 +79,16 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { if_standard! { assert_eq!( block.transactions.len(), - 19, - "deposit + builder + 17 valid txs should be in the block" + 17, + "deposit + builder + 15 valid txs should be in the block" ); } if_flashblocks! { assert_eq!( block.transactions.len(), - 20, - "deposit + 2 builder + 17 valid txs should be in the block" + 18, + "deposit + 2 builder + 15 valid txs should be in the block" ); } diff --git a/crates/op-rbuilder/src/tests/mod.rs b/crates/op-rbuilder/src/tests/mod.rs index afeff1cc..fd202a89 100644 --- a/crates/op-rbuilder/src/tests/mod.rs +++ b/crates/op-rbuilder/src/tests/mod.rs @@ -29,6 +29,8 @@ mod smoke; #[cfg(test)] mod txpool; +#[cfg(test)] +mod forks; // If the order of deployment from the signer changes the address will change #[cfg(test)] const FLASHBLOCKS_NUMBER_ADDRESS: alloy_primitives::Address = From 5ec6e2b7bd84207ab55b2e465eebc40735e5899a Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Mon, 17 Nov 2025 11:32:33 -0600 Subject: [PATCH 4/6] run miner limit tests on both standard/flashblocks --- .../op-rbuilder/src/tests/miner_gas_limit.rs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/op-rbuilder/src/tests/miner_gas_limit.rs b/crates/op-rbuilder/src/tests/miner_gas_limit.rs index 92d5ebc0..619509db 100644 --- a/crates/op-rbuilder/src/tests/miner_gas_limit.rs +++ b/crates/op-rbuilder/src/tests/miner_gas_limit.rs @@ -1,6 +1,7 @@ use crate::tests::{BlockTransactionsExt, LocalInstance}; use alloy_provider::Provider; use macros::{if_flashblocks, if_standard, rb_test}; + /// This test ensures that the miner gas limit is respected /// We will set the limit to 60,000 and see that the builder will not include any transactions #[rb_test] @@ -29,24 +30,20 @@ async fn miner_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { /// We will set our limit to 1Mgas and ensure that throttling occurs /// There is a deposit transaction for 182,706 gas, and builder transactions are 21,600 gas /// -/// Standard = (1.03 million - 182,706 - 21,600) / 53,000 = 15.57 = 15 transactions can fit -/// Flashblocks = (1.03 million - 182,706 - 21,600 - 21,600) / 53,000 = 15.17 = 15 transactions can fit +/// Standard = (785,000 - 182,706 - 21,600) / 53,000 = 10.95 = 10 transactions can fit +/// Flashblocks = (785,000 - 182,706 - 21,600 - 21,600) / 53,000 = 10.54 = 10 transactions can fit #[rb_test] async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { - if_flashblocks! { - return Ok(()); - } - let driver = rbuilder.driver().await?; let call = driver .provider() - .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (1_030_000,)) + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (785_000,)) .await?; assert!(call, "miner_setGasLimit should be executed successfully"); let mut tx_hashes = Vec::new(); - for _ in 0..15 { + for _ in 0..10 { let tx = driver .create_transaction() .with_gas_limit(53000) @@ -63,6 +60,7 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { .await?; let block = driver.build_new_block().await?; + for (i, tx_hash) in tx_hashes.iter().enumerate() { assert!( block.includes(tx_hash), @@ -79,7 +77,7 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { if_standard! { assert_eq!( block.transactions.len(), - 17, + 12, "deposit + builder + 15 valid txs should be in the block" ); } @@ -87,8 +85,8 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { if_flashblocks! { assert_eq!( block.transactions.len(), - 18, - "deposit + 2 builder + 15 valid txs should be in the block" + 13, + "deposit + builder + 15 valid txs should be in the block" ); } From effdf8e10e7d54f2d7a11ef09fe5b43a76665047 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Mon, 17 Nov 2025 18:02:21 -0600 Subject: [PATCH 5/6] Ensure builder transactions count towards DA usage --- crates/op-rbuilder/src/builders/context.rs | 1 + .../src/builders/flashblocks/payload.rs | 2 ++ .../op-rbuilder/src/builders/standard/payload.rs | 2 ++ crates/op-rbuilder/src/tests/data_availability.rs | 3 ++- crates/op-rbuilder/src/tests/forks.rs | 14 ++++++++++---- .../tests/framework/artifacts/genesis.json.tmpl | 3 +-- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index 7faacb83..8ce3aef3 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -388,6 +388,7 @@ impl OpPayloadBuilderCtx { let mut num_bundles_reverted = 0; let mut reverted_gas_used = 0; let base_fee = self.base_fee(); + let tx_da_limit = self.da_config.max_da_tx_size(); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs index ee31f944..02753bf5 100644 --- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs @@ -362,6 +362,7 @@ where // We subtract gas limit and da limit for builder transaction from the whole limit let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + info.cumulative_da_bytes_used += builder_tx_da_size; let (payload, fb_payload) = build_block( &mut state, @@ -638,6 +639,7 @@ where let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + info.cumulative_da_bytes_used += builder_tx_da_size; target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size diff --git a/crates/op-rbuilder/src/builders/standard/payload.rs b/crates/op-rbuilder/src/builders/standard/payload.rs index ee9302b8..4907c5c2 100644 --- a/crates/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/op-rbuilder/src/builders/standard/payload.rs @@ -366,6 +366,7 @@ impl OpBuilder<'_, Txs> { }; let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); + let block_gas_limit = ctx.block_gas_limit().saturating_sub(builder_tx_gas); if block_gas_limit == 0 { error!( @@ -374,6 +375,7 @@ impl OpBuilder<'_, Txs> { } // Save some space in the block_da_limit for builder tx let builder_tx_da_size = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + info.cumulative_da_bytes_used += builder_tx_da_size; let block_da_limit = ctx .da_config .max_da_block_size() diff --git a/crates/op-rbuilder/src/tests/data_availability.rs b/crates/op-rbuilder/src/tests/data_availability.rs index ffee176f..0e856c9f 100644 --- a/crates/op-rbuilder/src/tests/data_availability.rs +++ b/crates/op-rbuilder/src/tests/data_availability.rs @@ -64,9 +64,10 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; // Set block big enough so it could fit 3 transactions without tx size limit + // Deposit transactions also count towards DA and there is one deposit txn in this block too let call = driver .provider() - .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 3)) + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 4)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); diff --git a/crates/op-rbuilder/src/tests/forks.rs b/crates/op-rbuilder/src/tests/forks.rs index 7045bf10..a829d154 100644 --- a/crates/op-rbuilder/src/tests/forks.rs +++ b/crates/op-rbuilder/src/tests/forks.rs @@ -1,7 +1,7 @@ use crate::tests::{BlockTransactionsExt, LocalInstance}; use alloy_eips::{BlockNumberOrTag::Latest, eip1559::MIN_PROTOCOL_BASE_FEE}; use alloy_primitives::bytes; -use macros::rb_test; +use macros::{if_flashblocks, if_standard, rb_test}; use std::time::Duration; #[rb_test] @@ -18,9 +18,15 @@ async fn jovian_block_parameters_set(rbuilder: LocalInstance) -> eyre::Result<() assert!(block.header.blob_gas_used.is_some()); - // 2 transactions (excl. deposit txn) * (100 min size * 400 scalar) - // https://specs.optimism.io/protocol/fjord/exec-engine.html#l1-cost-fees-l1-fee-vault - assert_eq!(block.header.blob_gas_used.unwrap(), 80_000); + // Two user transactions + two builder transactions, all minimum size + if_flashblocks! { + assert_eq!(block.header.blob_gas_used.unwrap(), 160_000); + } + + // Two user transactions + one builder transactions, all minimum size + if_standard! { + assert_eq!(block.header.blob_gas_used.unwrap(), 120_000); + } // Version byte assert_eq!(block.header.extra_data.slice(0..1), bytes!("0x01")); diff --git a/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl b/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl index 936f353e..824b494e 100644 --- a/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl +++ b/crates/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl @@ -38,7 +38,7 @@ }, "nonce": "0x0", "timestamp": "", - "extraData": "0x", + "extraData": "0x0100000032000000020000000000000000", "gasLimit": "0x1c9c380", "difficulty": "0x0", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -901,4 +901,3 @@ "excessBlobGas": "0x0", "blobGasUsed": "0x0" } - From eb0e675e02489dc24d9ec586a4f7a846ee31387a Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Tue, 18 Nov 2025 10:15:57 -0600 Subject: [PATCH 6/6] Increment DA usage for non-deposit sequencer transactions (e.g. via CL sync) --- crates/op-rbuilder/src/builders/context.rs | 8 +++- crates/op-rbuilder/src/tests/forks.rs | 47 +++++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/crates/op-rbuilder/src/builders/context.rs b/crates/op-rbuilder/src/builders/context.rs index 8ce3aef3..e1eb4f39 100644 --- a/crates/op-rbuilder/src/builders/context.rs +++ b/crates/op-rbuilder/src/builders/context.rs @@ -1,5 +1,5 @@ use alloy_consensus::{Eip658Value, Transaction, conditional::BlockConditionalAttributes}; -use alloy_eips::Typed2718; +use alloy_eips::{Encodable2718, Typed2718}; use alloy_evm::Database; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{BlockHash, Bytes, U256}; @@ -338,6 +338,12 @@ impl OpPayloadBuilderCtx { let gas_used = result.gas_used(); info.cumulative_gas_used += gas_used; + if !sequencer_tx.is_deposit() { + info.cumulative_da_bytes_used += op_alloy_flz::tx_estimated_size_fjord_bytes( + sequencer_tx.encoded_2718().as_slice(), + ); + } + let ctx = ReceiptBuilderCtx { tx: sequencer_tx.inner(), evm: &evm, diff --git a/crates/op-rbuilder/src/tests/forks.rs b/crates/op-rbuilder/src/tests/forks.rs index a829d154..d136c375 100644 --- a/crates/op-rbuilder/src/tests/forks.rs +++ b/crates/op-rbuilder/src/tests/forks.rs @@ -1,5 +1,5 @@ use crate::tests::{BlockTransactionsExt, LocalInstance}; -use alloy_eips::{BlockNumberOrTag::Latest, eip1559::MIN_PROTOCOL_BASE_FEE}; +use alloy_eips::{BlockNumberOrTag::Latest, Encodable2718, eip1559::MIN_PROTOCOL_BASE_FEE}; use alloy_primitives::bytes; use macros::{if_flashblocks, if_standard, rb_test}; use std::time::Duration; @@ -40,6 +40,51 @@ async fn jovian_block_parameters_set(rbuilder: LocalInstance) -> eyre::Result<() Ok(()) } +#[rb_test] +async fn jovian_no_tx_pool_sync(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let block = driver + .build_new_block_with_txs_timestamp(vec![], Some(true), None, None, Some(0)) + .await?; + + // Deposit transaction + user transaction + if_flashblocks! { + assert_eq!(block.transactions.len(), 1); + assert_eq!(block.header.blob_gas_used, Some(0)); + } + + // Standard includes a builder transaction when no-tx-pool is set + if_standard! { + assert_eq!(block.transactions.len(), 2); + assert_eq!(block.header.blob_gas_used, Some(40_000)); + } + + let tx = driver.create_transaction().build().await; + let block = driver + .build_new_block_with_txs_timestamp( + vec![tx.encoded_2718().into()], + Some(true), + None, + None, + Some(0), + ) + .await?; + + // Deposit transaction + user transaction + if_flashblocks! { + assert_eq!(block.transactions.len(), 2); + assert_eq!(block.header.blob_gas_used, Some(40_000)); + } + + // Standard includes a builder transaction when no-tx-pool is set + if_standard! { + assert_eq!(block.transactions.len(), 3); + assert_eq!(block.header.blob_gas_used, Some(80_000)); + } + + Ok(()) +} + #[rb_test] async fn jovian_minimum_base_fee(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?;