mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
feat(jovian): track da footprint block limit. Update basefee calculation (#19048)
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
This commit is contained in:
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -253,9 +253,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-evm"
|
||||
version = "0.22.3"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb19405755c6f94c9bb856f2b1449767074b7e2002e1ab2be0a79b9b28db322"
|
||||
checksum = "83ce19ea6140497670b1b7e721f9a9ce88022fe475a5e4e6a68a403499cca209"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -370,9 +370,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-op-evm"
|
||||
version = "0.22.3"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f059cf29d7f15b3e6581ceb6eda06a16d8ed4b55adc02b0677add3fd381db6bb"
|
||||
checksum = "7d7aeaf6051f53880a65b547c43e3b05ee42f68236b1f43f013abfe4eadc47bb"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -6127,9 +6127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "op-revm"
|
||||
version = "11.1.2"
|
||||
version = "11.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d721c4c196273dd135ea5b823cd573ea8735cd3c5f2c19fcb91ee3af655351"
|
||||
checksum = "a33ab6a7bbcfffcbf784de78f14593b6389003f5c69653fcffcc163459a37d69"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"revm",
|
||||
@@ -9435,6 +9435,7 @@ version = "1.8.2"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
"alloy-evm",
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
"alloy-rpc-types-debug",
|
||||
|
||||
@@ -478,14 +478,14 @@ revm-inspector = { version = "11.1.0", default-features = false }
|
||||
revm-context = { version = "10.1.0", default-features = false }
|
||||
revm-context-interface = { version = "11.1.0", default-features = false }
|
||||
revm-database-interface = { version = "8.0.1", default-features = false }
|
||||
op-revm = { version = "11.1.0", default-features = false }
|
||||
op-revm = { version = "11.2.0", default-features = false }
|
||||
revm-inspectors = "0.31.0"
|
||||
|
||||
# eth
|
||||
alloy-chains = { version = "0.2.5", default-features = false }
|
||||
alloy-dyn-abi = "1.4.1"
|
||||
alloy-eip2124 = { version = "0.2.0", default-features = false }
|
||||
alloy-evm = { version = "0.22.0", default-features = false }
|
||||
alloy-evm = { version = "0.22.4", default-features = false }
|
||||
alloy-primitives = { version = "1.4.1", default-features = false, features = ["map-foldhash"] }
|
||||
alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] }
|
||||
alloy-sol-macro = "1.4.1"
|
||||
@@ -523,7 +523,7 @@ alloy-transport-ipc = { version = "1.0.41", default-features = false }
|
||||
alloy-transport-ws = { version = "1.0.41", default-features = false }
|
||||
|
||||
# op
|
||||
alloy-op-evm = { version = "0.22.0", default-features = false }
|
||||
alloy-op-evm = { version = "0.22.4", default-features = false }
|
||||
alloy-op-hardforks = "0.4.0"
|
||||
op-alloy-rpc-types = { version = "0.21.0", default-features = false }
|
||||
op-alloy-rpc-types-engine = { version = "0.21.0", default-features = false }
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
//! Base fee related utilities for Optimism chains.
|
||||
|
||||
use core::cmp::max;
|
||||
|
||||
use alloy_consensus::BlockHeader;
|
||||
use alloy_eips::calc_next_block_base_fee;
|
||||
use op_alloy_consensus::{decode_holocene_extra_data, decode_jovian_extra_data, EIP1559ParamError};
|
||||
use reth_chainspec::{BaseFeeParams, EthChainSpec};
|
||||
use reth_optimism_forks::OpHardforks;
|
||||
|
||||
fn next_base_fee_params<H: BlockHeader>(
|
||||
chain_spec: impl EthChainSpec + OpHardforks,
|
||||
parent: &H,
|
||||
timestamp: u64,
|
||||
denominator: u32,
|
||||
elasticity: u32,
|
||||
) -> u64 {
|
||||
let base_fee_params = if elasticity == 0 && denominator == 0 {
|
||||
chain_spec.base_fee_params_at_timestamp(timestamp)
|
||||
} else {
|
||||
BaseFeeParams::new(denominator as u128, elasticity as u128)
|
||||
};
|
||||
|
||||
parent.next_block_base_fee(base_fee_params).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Extracts the Holocene 1599 parameters from the encoded extra data from the parent header.
|
||||
///
|
||||
/// Caution: Caller must ensure that holocene is active in the parent header.
|
||||
@@ -36,7 +23,13 @@ where
|
||||
{
|
||||
let (elasticity, denominator) = decode_holocene_extra_data(parent.extra_data())?;
|
||||
|
||||
Ok(next_base_fee_params(chain_spec, parent, timestamp, denominator, elasticity))
|
||||
let base_fee_params = if elasticity == 0 && denominator == 0 {
|
||||
chain_spec.base_fee_params_at_timestamp(timestamp)
|
||||
} else {
|
||||
BaseFeeParams::new(denominator as u128, elasticity as u128)
|
||||
};
|
||||
|
||||
Ok(parent.next_block_base_fee(base_fee_params).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Extracts the Jovian 1599 parameters from the encoded extra data from the parent header.
|
||||
@@ -57,8 +50,22 @@ where
|
||||
{
|
||||
let (elasticity, denominator, min_base_fee) = decode_jovian_extra_data(parent.extra_data())?;
|
||||
|
||||
let next_base_fee =
|
||||
next_base_fee_params(chain_spec, parent, timestamp, denominator, elasticity);
|
||||
let base_fee_params = if elasticity == 0 && denominator == 0 {
|
||||
chain_spec.base_fee_params_at_timestamp(timestamp)
|
||||
} else {
|
||||
BaseFeeParams::new(denominator as u128, elasticity as u128)
|
||||
};
|
||||
|
||||
// Starting from Jovian, we use the maximum of the gas used and the blob gas used to calculate
|
||||
// the next base fee.
|
||||
let gas_used = max(parent.gas_used(), parent.blob_gas_used().unwrap_or_default());
|
||||
|
||||
let next_base_fee = calc_next_block_base_fee(
|
||||
gas_used,
|
||||
parent.gas_limit(),
|
||||
parent.base_fee_per_gas().unwrap_or_default(),
|
||||
base_fee_params,
|
||||
);
|
||||
|
||||
if next_base_fee < min_base_fee {
|
||||
return Ok(min_base_fee);
|
||||
@@ -66,3 +73,127 @@ where
|
||||
|
||||
Ok(next_base_fee)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use op_alloy_consensus::encode_jovian_extra_data;
|
||||
use reth_chainspec::{ChainSpec, ForkCondition, Hardfork};
|
||||
use reth_optimism_forks::OpHardfork;
|
||||
|
||||
use crate::{OpChainSpec, BASE_SEPOLIA};
|
||||
|
||||
use super::*;
|
||||
|
||||
const JOVIAN_TIMESTAMP: u64 = 1900000000;
|
||||
|
||||
fn get_chainspec() -> Arc<OpChainSpec> {
|
||||
let mut base_sepolia_spec = BASE_SEPOLIA.inner.clone();
|
||||
base_sepolia_spec
|
||||
.hardforks
|
||||
.insert(OpHardfork::Jovian.boxed(), ForkCondition::Timestamp(JOVIAN_TIMESTAMP));
|
||||
Arc::new(OpChainSpec {
|
||||
inner: ChainSpec {
|
||||
chain: base_sepolia_spec.chain,
|
||||
genesis: base_sepolia_spec.genesis,
|
||||
genesis_header: base_sepolia_spec.genesis_header,
|
||||
..Default::default()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_next_base_fee_jovian_blob_gas_used_greater_than_gas_used() {
|
||||
let chain_spec = get_chainspec();
|
||||
let mut parent = chain_spec.genesis_header().clone();
|
||||
let timestamp = JOVIAN_TIMESTAMP;
|
||||
|
||||
const GAS_LIMIT: u64 = 10_000_000_000;
|
||||
const BLOB_GAS_USED: u64 = 5_000_000_000;
|
||||
const GAS_USED: u64 = 1_000_000_000;
|
||||
const MIN_BASE_FEE: u64 = 100_000_000;
|
||||
|
||||
parent.extra_data =
|
||||
encode_jovian_extra_data([0; 8].into(), BaseFeeParams::base_sepolia(), MIN_BASE_FEE)
|
||||
.unwrap();
|
||||
parent.blob_gas_used = Some(BLOB_GAS_USED);
|
||||
parent.gas_used = GAS_USED;
|
||||
parent.gas_limit = GAS_LIMIT;
|
||||
|
||||
let expected_base_fee = calc_next_block_base_fee(
|
||||
BLOB_GAS_USED,
|
||||
parent.gas_limit(),
|
||||
parent.base_fee_per_gas().unwrap_or_default(),
|
||||
BaseFeeParams::base_sepolia(),
|
||||
);
|
||||
assert_eq!(
|
||||
expected_base_fee,
|
||||
compute_jovian_base_fee(chain_spec, &parent, timestamp).unwrap()
|
||||
);
|
||||
assert_ne!(
|
||||
expected_base_fee,
|
||||
calc_next_block_base_fee(
|
||||
GAS_USED,
|
||||
parent.gas_limit(),
|
||||
parent.base_fee_per_gas().unwrap_or_default(),
|
||||
BaseFeeParams::base_sepolia(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_next_base_fee_jovian_blob_gas_used_less_than_gas_used() {
|
||||
let chain_spec = get_chainspec();
|
||||
let mut parent = chain_spec.genesis_header().clone();
|
||||
let timestamp = JOVIAN_TIMESTAMP;
|
||||
|
||||
const GAS_LIMIT: u64 = 10_000_000_000;
|
||||
const BLOB_GAS_USED: u64 = 100_000_000;
|
||||
const GAS_USED: u64 = 1_000_000_000;
|
||||
const MIN_BASE_FEE: u64 = 100_000_000;
|
||||
|
||||
parent.extra_data =
|
||||
encode_jovian_extra_data([0; 8].into(), BaseFeeParams::base_sepolia(), MIN_BASE_FEE)
|
||||
.unwrap();
|
||||
parent.blob_gas_used = Some(BLOB_GAS_USED);
|
||||
parent.gas_used = GAS_USED;
|
||||
parent.gas_limit = GAS_LIMIT;
|
||||
|
||||
let expected_base_fee = calc_next_block_base_fee(
|
||||
GAS_USED,
|
||||
parent.gas_limit(),
|
||||
parent.base_fee_per_gas().unwrap_or_default(),
|
||||
BaseFeeParams::base_sepolia(),
|
||||
);
|
||||
assert_eq!(
|
||||
expected_base_fee,
|
||||
compute_jovian_base_fee(chain_spec, &parent, timestamp).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_next_base_fee_jovian_min_base_fee() {
|
||||
let chain_spec = get_chainspec();
|
||||
let mut parent = chain_spec.genesis_header().clone();
|
||||
let timestamp = JOVIAN_TIMESTAMP;
|
||||
|
||||
const GAS_LIMIT: u64 = 10_000_000_000;
|
||||
const BLOB_GAS_USED: u64 = 100_000_000;
|
||||
const GAS_USED: u64 = 1_000_000_000;
|
||||
const MIN_BASE_FEE: u64 = 5_000_000_000;
|
||||
|
||||
parent.extra_data =
|
||||
encode_jovian_extra_data([0; 8].into(), BaseFeeParams::base_sepolia(), MIN_BASE_FEE)
|
||||
.unwrap();
|
||||
parent.blob_gas_used = Some(BLOB_GAS_USED);
|
||||
parent.gas_used = GAS_USED;
|
||||
parent.gas_limit = GAS_LIMIT;
|
||||
|
||||
let expected_base_fee = MIN_BASE_FEE;
|
||||
assert_eq!(
|
||||
expected_base_fee,
|
||||
compute_jovian_base_fee(chain_spec, &parent, timestamp).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ impl<ChainSpec: OpHardforks> OpBlockAssembler<ChainSpec> {
|
||||
evm_env,
|
||||
execution_ctx: ctx,
|
||||
transactions,
|
||||
output: BlockExecutionResult { receipts, gas_used, .. },
|
||||
output: BlockExecutionResult { receipts, gas_used, blob_gas_used, requests: _ },
|
||||
bundle_state,
|
||||
state_root,
|
||||
state_provider,
|
||||
@@ -80,7 +80,11 @@ impl<ChainSpec: OpHardforks> OpBlockAssembler<ChainSpec> {
|
||||
};
|
||||
|
||||
let (excess_blob_gas, blob_gas_used) =
|
||||
if self.chain_spec.is_ecotone_active_at_timestamp(timestamp) {
|
||||
if self.chain_spec.is_jovian_active_at_timestamp(timestamp) {
|
||||
// In jovian, we're using the blob gas used field to store the current da
|
||||
// footprint's value.
|
||||
(Some(0), Some(*blob_gas_used))
|
||||
} else if self.chain_spec.is_ecotone_active_at_timestamp(timestamp) {
|
||||
(Some(0), Some(0))
|
||||
} else {
|
||||
(None, None)
|
||||
|
||||
@@ -38,6 +38,9 @@ pub enum L1BlockInfoError {
|
||||
/// Operator fee constant conversion error
|
||||
#[error("could not convert operator fee constant")]
|
||||
OperatorFeeConstantConversion,
|
||||
/// DA foootprint gas scalar constant conversion error
|
||||
#[error("could not convert DA footprint gas scalar constant")]
|
||||
DaFootprintGasScalarConversion,
|
||||
/// Optimism hardforks not active
|
||||
#[error("Optimism hardforks are not active")]
|
||||
HardforksNotActive,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{error::L1BlockInfoError, revm_spec_by_timestamp_after_bedrock, OpBlockExecutionError};
|
||||
use alloy_consensus::Transaction;
|
||||
use alloy_primitives::{hex, U256};
|
||||
use alloy_primitives::{hex, U16, U256};
|
||||
use op_revm::L1BlockInfo;
|
||||
use reth_execution_errors::BlockExecutionError;
|
||||
use reth_optimism_forks::OpHardforks;
|
||||
@@ -14,6 +14,10 @@ const L1_BLOCK_ECOTONE_SELECTOR: [u8; 4] = hex!("440a5e20");
|
||||
/// The function selector of the "setL1BlockValuesIsthmus" function in the `L1Block` contract.
|
||||
const L1_BLOCK_ISTHMUS_SELECTOR: [u8; 4] = hex!("098999be");
|
||||
|
||||
/// The function selector of the "setL1BlockValuesJovian" function in the `L1Block` contract.
|
||||
/// This is the first 4 bytes of `keccak256("setL1BlockValuesJovian()")`.
|
||||
const L1_BLOCK_JOVIAN_SELECTOR: [u8; 4] = hex!("3db6be2b");
|
||||
|
||||
/// Extracts the [`L1BlockInfo`] from the L2 block. The L1 info transaction is always the first
|
||||
/// transaction in the L2 block.
|
||||
///
|
||||
@@ -52,11 +56,14 @@ pub fn extract_l1_info_from_tx<T: Transaction>(
|
||||
/// If the input is shorter than 4 bytes.
|
||||
pub fn parse_l1_info(input: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
|
||||
// Parse the L1 info transaction into an L1BlockInfo struct, depending on the function selector.
|
||||
// There are currently 3 variants:
|
||||
// There are currently 4 variants:
|
||||
// - Jovian
|
||||
// - Isthmus
|
||||
// - Ecotone
|
||||
// - Bedrock
|
||||
if input[0..4] == L1_BLOCK_ISTHMUS_SELECTOR {
|
||||
if input[0..4] == L1_BLOCK_JOVIAN_SELECTOR {
|
||||
parse_l1_info_tx_jovian(input[4..].as_ref())
|
||||
} else if input[0..4] == L1_BLOCK_ISTHMUS_SELECTOR {
|
||||
parse_l1_info_tx_isthmus(input[4..].as_ref())
|
||||
} else if input[0..4] == L1_BLOCK_ECOTONE_SELECTOR {
|
||||
parse_l1_info_tx_ecotone(input[4..].as_ref())
|
||||
@@ -88,14 +95,12 @@ pub fn parse_l1_info_tx_bedrock(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecu
|
||||
let l1_fee_scalar = U256::try_from_be_slice(&data[224..256])
|
||||
.ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::FeeScalarConversion))?;
|
||||
|
||||
let l1block = L1BlockInfo {
|
||||
Ok(L1BlockInfo {
|
||||
l1_base_fee,
|
||||
l1_fee_overhead: Some(l1_fee_overhead),
|
||||
l1_base_fee_scalar: l1_fee_scalar,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(l1block)
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates the L1 block values for an Ecotone upgraded chain.
|
||||
@@ -142,15 +147,13 @@ pub fn parse_l1_info_tx_ecotone(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecu
|
||||
let l1_blob_base_fee = U256::try_from_be_slice(&data[64..96])
|
||||
.ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeConversion))?;
|
||||
|
||||
let l1block = L1BlockInfo {
|
||||
Ok(L1BlockInfo {
|
||||
l1_base_fee,
|
||||
l1_base_fee_scalar,
|
||||
l1_blob_base_fee: Some(l1_blob_base_fee),
|
||||
l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(l1block)
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates the L1 block values for an Isthmus upgraded chain.
|
||||
@@ -205,7 +208,7 @@ pub fn parse_l1_info_tx_isthmus(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecu
|
||||
OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeConstantConversion)
|
||||
})?;
|
||||
|
||||
let l1block = L1BlockInfo {
|
||||
Ok(L1BlockInfo {
|
||||
l1_base_fee,
|
||||
l1_base_fee_scalar,
|
||||
l1_blob_base_fee: Some(l1_blob_base_fee),
|
||||
@@ -213,9 +216,78 @@ pub fn parse_l1_info_tx_isthmus(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecu
|
||||
operator_fee_scalar: Some(operator_fee_scalar),
|
||||
operator_fee_constant: Some(operator_fee_constant),
|
||||
..Default::default()
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
Ok(l1block)
|
||||
/// Updates the L1 block values for an Jovian upgraded chain.
|
||||
/// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size.
|
||||
/// Params are expected to be in the following order:
|
||||
/// 1. _baseFeeScalar L1 base fee scalar
|
||||
/// 2. _blobBaseFeeScalar L1 blob base fee scalar
|
||||
/// 3. _sequenceNumber Number of L2 blocks since epoch start.
|
||||
/// 4. _timestamp L1 timestamp.
|
||||
/// 5. _number L1 blocknumber.
|
||||
/// 6. _basefee L1 base fee.
|
||||
/// 7. _blobBaseFee L1 blob base fee.
|
||||
/// 8. _hash L1 blockhash.
|
||||
/// 9. _batcherHash Versioned hash to authenticate batcher by.
|
||||
/// 10. _operatorFeeScalar Operator fee scalar
|
||||
/// 11. _operatorFeeConstant Operator fee constant
|
||||
/// 12. _daFootprintGasScalar DA footprint gas scalar
|
||||
pub fn parse_l1_info_tx_jovian(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
|
||||
if data.len() != 174 {
|
||||
return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::UnexpectedCalldataLength));
|
||||
}
|
||||
|
||||
// https://github.com/ethereum-optimism/op-geth/blob/60038121c7571a59875ff9ed7679c48c9f73405d/core/types/rollup_cost.go#L317-L328
|
||||
//
|
||||
// data layout assumed for Ecotone:
|
||||
// offset type varname
|
||||
// 0 <selector>
|
||||
// 4 uint32 _basefeeScalar (start offset in this scope)
|
||||
// 8 uint32 _blobBaseFeeScalar
|
||||
// 12 uint64 _sequenceNumber,
|
||||
// 20 uint64 _timestamp,
|
||||
// 28 uint64 _l1BlockNumber
|
||||
// 36 uint256 _basefee,
|
||||
// 68 uint256 _blobBaseFee,
|
||||
// 100 bytes32 _hash,
|
||||
// 132 bytes32 _batcherHash,
|
||||
// 164 uint32 _operatorFeeScalar
|
||||
// 168 uint64 _operatorFeeConstant
|
||||
// 176 uint16 _daFootprintGasScalar
|
||||
|
||||
let l1_base_fee_scalar = U256::try_from_be_slice(&data[..4])
|
||||
.ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeScalarConversion))?;
|
||||
let l1_blob_base_fee_scalar = U256::try_from_be_slice(&data[4..8]).ok_or({
|
||||
OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeScalarConversion)
|
||||
})?;
|
||||
let l1_base_fee = U256::try_from_be_slice(&data[32..64])
|
||||
.ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeConversion))?;
|
||||
let l1_blob_base_fee = U256::try_from_be_slice(&data[64..96])
|
||||
.ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeConversion))?;
|
||||
let operator_fee_scalar = U256::try_from_be_slice(&data[160..164]).ok_or({
|
||||
OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeScalarConversion)
|
||||
})?;
|
||||
let operator_fee_constant = U256::try_from_be_slice(&data[164..172]).ok_or({
|
||||
OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeConstantConversion)
|
||||
})?;
|
||||
let da_footprint_gas_scalar: u16 = U16::try_from_be_slice(&data[172..174])
|
||||
.ok_or({
|
||||
OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::DaFootprintGasScalarConversion)
|
||||
})?
|
||||
.to();
|
||||
|
||||
Ok(L1BlockInfo {
|
||||
l1_base_fee,
|
||||
l1_base_fee_scalar,
|
||||
l1_blob_base_fee: Some(l1_blob_base_fee),
|
||||
l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
|
||||
operator_fee_scalar: Some(operator_fee_scalar),
|
||||
operator_fee_constant: Some(operator_fee_constant),
|
||||
da_footprint_gas_scalar: Some(da_footprint_gas_scalar),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// An extension trait for [`L1BlockInfo`] that allows us to calculate the L1 cost of a transaction
|
||||
@@ -282,6 +354,7 @@ mod tests {
|
||||
use super::*;
|
||||
use alloy_consensus::{Block, BlockBody};
|
||||
use alloy_eips::eip2718::Decodable2718;
|
||||
use alloy_primitives::keccak256;
|
||||
use reth_optimism_chainspec::OP_MAINNET;
|
||||
use reth_optimism_forks::OpHardforks;
|
||||
use reth_optimism_primitives::OpTransactionSigned;
|
||||
@@ -308,6 +381,12 @@ mod tests {
|
||||
assert_eq!(l1_info.l1_blob_base_fee_scalar, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_set_jovian() {
|
||||
let hash = &keccak256("setL1BlockValuesJovian()")[..4];
|
||||
assert_eq!(hash, L1_BLOCK_JOVIAN_SELECTOR)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanity_l1_block_ecotone() {
|
||||
// rig
|
||||
@@ -408,4 +487,33 @@ mod tests {
|
||||
assert_eq!(l1_block_info.operator_fee_scalar, operator_fee_scalar);
|
||||
assert_eq!(l1_block_info.operator_fee_constant, operator_fee_constant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_l1_info_jovian() {
|
||||
// L1 block info from a devnet with Isthmus activated
|
||||
const DATA: &[u8] = &hex!(
|
||||
"3db6be2b00000558000c5fc500000000000000030000000067a9f765000000000000002900000000000000000000000000000000000000000000000000000000006a6d09000000000000000000000000000000000000000000000000000000000000000172fcc8e8886636bdbe96ba0e4baab67ea7e7811633f52b52e8cf7a5123213b6f000000000000000000000000d3f2c5afb2d76f5579f326b0cd7da5f5a4126c3500004e2000000000000001f4dead"
|
||||
);
|
||||
|
||||
// expected l1 block info verified against expected l1 fee and operator fee for tx.
|
||||
let l1_base_fee = U256::from(6974729);
|
||||
let l1_base_fee_scalar = U256::from(1368);
|
||||
let l1_blob_base_fee = Some(U256::from(1));
|
||||
let l1_blob_base_fee_scalar = Some(U256::from(810949));
|
||||
let operator_fee_scalar = Some(U256::from(20000));
|
||||
let operator_fee_constant = Some(U256::from(500));
|
||||
let da_footprint_gas_scalar: Option<u16> = Some(U16::from(0xdead).to());
|
||||
|
||||
// test
|
||||
|
||||
let l1_block_info = parse_l1_info(DATA).unwrap();
|
||||
|
||||
assert_eq!(l1_block_info.l1_base_fee, l1_base_fee);
|
||||
assert_eq!(l1_block_info.l1_base_fee_scalar, l1_base_fee_scalar);
|
||||
assert_eq!(l1_block_info.l1_blob_base_fee, l1_blob_base_fee);
|
||||
assert_eq!(l1_block_info.l1_blob_base_fee_scalar, l1_blob_base_fee_scalar);
|
||||
assert_eq!(l1_block_info.operator_fee_scalar, operator_fee_scalar);
|
||||
assert_eq!(l1_block_info.operator_fee_constant, operator_fee_constant);
|
||||
assert_eq!(l1_block_info.da_footprint_gas_scalar, da_footprint_gas_scalar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ op-alloy-consensus.workspace = true
|
||||
alloy-rpc-types-engine.workspace = true
|
||||
alloy-rpc-types-debug.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
alloy-evm.workspace = true
|
||||
|
||||
# misc
|
||||
derive_more.workspace = true
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Optimism payload builder implementation.
|
||||
|
||||
use crate::{
|
||||
config::{OpBuilderConfig, OpDAConfig},
|
||||
error::OpPayloadBuilderError,
|
||||
@@ -7,6 +6,7 @@ use crate::{
|
||||
OpAttributes, OpPayloadBuilderAttributes, OpPayloadPrimitives,
|
||||
};
|
||||
use alloy_consensus::{BlockHeader, Transaction, Typed2718};
|
||||
use alloy_evm::Evm as AlloyEvm;
|
||||
use alloy_primitives::{B256, U256};
|
||||
use alloy_rpc_types_debug::ExecutionWitness;
|
||||
use alloy_rpc_types_engine::PayloadId;
|
||||
@@ -14,10 +14,12 @@ use reth_basic_payload_builder::*;
|
||||
use reth_chain_state::ExecutedBlock;
|
||||
use reth_chainspec::{ChainSpecProvider, EthChainSpec};
|
||||
use reth_evm::{
|
||||
block::BlockExecutorFor,
|
||||
execute::{
|
||||
BlockBuilder, BlockBuilderOutcome, BlockExecutionError, BlockExecutor, BlockValidationError,
|
||||
},
|
||||
ConfigureEvm, Database, Evm,
|
||||
op_revm::{constants::L1_BLOCK_CONTRACT, L1BlockInfo},
|
||||
ConfigureEvm, Database,
|
||||
};
|
||||
use reth_execution_types::ExecutionOutcome;
|
||||
use reth_optimism_forks::OpHardforks;
|
||||
@@ -340,6 +342,11 @@ impl<Txs> OpBuilder<'_, Txs> {
|
||||
|
||||
let mut db = State::builder().with_database(db).with_bundle_update().build();
|
||||
|
||||
// Load the L1 block contract into the database cache. If the L1 block contract is not
|
||||
// pre-loaded the database will panic when trying to fetch the DA footprint gas
|
||||
// scalar.
|
||||
db.load_cache_account(L1_BLOCK_CONTRACT).map_err(BlockExecutionError::other)?;
|
||||
|
||||
let mut builder = ctx.block_builder(&mut db)?;
|
||||
|
||||
// 1. apply pre-execution changes
|
||||
@@ -509,17 +516,27 @@ impl ExecutionInfo {
|
||||
tx_data_limit: Option<u64>,
|
||||
block_data_limit: Option<u64>,
|
||||
tx_gas_limit: u64,
|
||||
da_footprint_gas_scalar: Option<u16>,
|
||||
) -> bool {
|
||||
if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if block_data_limit
|
||||
.is_some_and(|da_limit| self.cumulative_da_bytes_used + tx_da_size > da_limit)
|
||||
{
|
||||
let total_da_bytes_used = self.cumulative_da_bytes_used.saturating_add(tx_da_size);
|
||||
|
||||
if block_data_limit.is_some_and(|da_limit| total_da_bytes_used > da_limit) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Post Jovian: the tx DA footprint must be less than the block gas limit
|
||||
if let Some(da_footprint_gas_scalar) = da_footprint_gas_scalar {
|
||||
let tx_da_footprint =
|
||||
total_da_bytes_used.saturating_mul(da_footprint_gas_scalar as u64);
|
||||
if tx_da_footprint > block_gas_limit {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
self.cumulative_gas_used + tx_gas_limit > block_gas_limit
|
||||
}
|
||||
}
|
||||
@@ -586,7 +603,13 @@ where
|
||||
pub fn block_builder<'a, DB: Database>(
|
||||
&'a self,
|
||||
db: &'a mut State<DB>,
|
||||
) -> Result<impl BlockBuilder<Primitives = Evm::Primitives> + 'a, PayloadBuilderError> {
|
||||
) -> Result<
|
||||
impl BlockBuilder<
|
||||
Primitives = Evm::Primitives,
|
||||
Executor: BlockExecutorFor<'a, Evm::BlockExecutorFactory, DB>,
|
||||
> + 'a,
|
||||
PayloadBuilderError,
|
||||
> {
|
||||
self.evm_config
|
||||
.builder_for_next_block(
|
||||
db,
|
||||
@@ -649,14 +672,18 @@ where
|
||||
/// Executes the given best transactions and updates the execution info.
|
||||
///
|
||||
/// Returns `Ok(Some(())` if the job was cancelled.
|
||||
pub fn execute_best_transactions(
|
||||
pub fn execute_best_transactions<Builder>(
|
||||
&self,
|
||||
info: &mut ExecutionInfo,
|
||||
builder: &mut impl BlockBuilder<Primitives = Evm::Primitives>,
|
||||
builder: &mut Builder,
|
||||
mut best_txs: impl PayloadTransactions<
|
||||
Transaction: PoolTransaction<Consensus = TxTy<Evm::Primitives>> + OpPooledTx,
|
||||
>,
|
||||
) -> Result<Option<()>, PayloadBuilderError> {
|
||||
) -> Result<Option<()>, PayloadBuilderError>
|
||||
where
|
||||
Builder: BlockBuilder<Primitives = Evm::Primitives>,
|
||||
<<Builder::Executor as BlockExecutor>::Evm as AlloyEvm>::DB: Database,
|
||||
{
|
||||
let block_gas_limit = builder.evm_mut().block().gas_limit();
|
||||
let block_da_limit = self.da_config.max_da_block_size();
|
||||
let tx_da_limit = self.da_config.max_da_tx_size();
|
||||
@@ -666,12 +693,23 @@ where
|
||||
let interop = tx.interop_deadline();
|
||||
let tx_da_size = tx.estimated_da_size();
|
||||
let tx = tx.into_consensus();
|
||||
|
||||
let da_footprint_gas_scalar = self
|
||||
.chain_spec
|
||||
.is_jovian_active_at_timestamp(self.attributes().timestamp())
|
||||
.then_some(
|
||||
L1BlockInfo::fetch_da_footprint_gas_scalar(builder.evm_mut().db_mut()).expect(
|
||||
"DA footprint should always be available from the database post jovian",
|
||||
),
|
||||
);
|
||||
|
||||
if info.is_tx_over_limits(
|
||||
tx_da_size,
|
||||
block_gas_limit,
|
||||
tx_da_limit,
|
||||
block_da_limit,
|
||||
tx.gas_limit(),
|
||||
da_footprint_gas_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
|
||||
|
||||
@@ -131,10 +131,14 @@ pub struct OpReceiptFieldsBuilder {
|
||||
pub l1_blob_base_fee: Option<u128>,
|
||||
/// The current L1 blob base fee scalar.
|
||||
pub l1_blob_base_fee_scalar: Option<u128>,
|
||||
/* ---------------------------------------- Isthmus ---------------------------------------- */
|
||||
/// The current operator fee scalar.
|
||||
pub operator_fee_scalar: Option<u128>,
|
||||
/// The current L1 blob base fee scalar.
|
||||
pub operator_fee_constant: Option<u128>,
|
||||
/* ---------------------------------------- Jovian ----------------------------------------- */
|
||||
/// The current DA footprint gas scalar.
|
||||
pub da_footprint_gas_scalar: Option<u16>,
|
||||
}
|
||||
|
||||
impl OpReceiptFieldsBuilder {
|
||||
@@ -154,6 +158,7 @@ impl OpReceiptFieldsBuilder {
|
||||
l1_blob_base_fee_scalar: None,
|
||||
operator_fee_scalar: None,
|
||||
operator_fee_constant: None,
|
||||
da_footprint_gas_scalar: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +210,8 @@ impl OpReceiptFieldsBuilder {
|
||||
l1_block_info.operator_fee_constant.map(|constant| constant.saturating_to());
|
||||
}
|
||||
|
||||
self.da_footprint_gas_scalar = l1_block_info.da_footprint_gas_scalar;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
@@ -236,6 +243,7 @@ impl OpReceiptFieldsBuilder {
|
||||
l1_blob_base_fee_scalar,
|
||||
operator_fee_scalar,
|
||||
operator_fee_constant,
|
||||
da_footprint_gas_scalar,
|
||||
} = self;
|
||||
|
||||
OpTransactionReceiptFields {
|
||||
@@ -249,7 +257,7 @@ impl OpReceiptFieldsBuilder {
|
||||
l1_blob_base_fee_scalar,
|
||||
operator_fee_scalar,
|
||||
operator_fee_constant,
|
||||
da_footprint_gas_scalar: None,
|
||||
da_footprint_gas_scalar,
|
||||
},
|
||||
deposit_nonce,
|
||||
deposit_receipt_version,
|
||||
@@ -409,7 +417,7 @@ mod test {
|
||||
l1_blob_base_fee_scalar,
|
||||
operator_fee_scalar,
|
||||
operator_fee_constant,
|
||||
..
|
||||
da_footprint_gas_scalar,
|
||||
} = receipt_meta.l1_block_info;
|
||||
|
||||
assert_eq!(
|
||||
@@ -453,6 +461,11 @@ mod test {
|
||||
TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.operator_fee_constant,
|
||||
"incorrect operator fee constant"
|
||||
);
|
||||
assert_eq!(
|
||||
da_footprint_gas_scalar,
|
||||
TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.da_footprint_gas_scalar,
|
||||
"incorrect da footprint gas scalar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -540,7 +553,7 @@ mod test {
|
||||
l1_blob_base_fee_scalar,
|
||||
operator_fee_scalar,
|
||||
operator_fee_constant,
|
||||
..
|
||||
da_footprint_gas_scalar,
|
||||
} = receipt_meta.l1_block_info;
|
||||
|
||||
assert_eq!(l1_gas_price, Some(14121491676), "incorrect l1 base fee (former gas price)");
|
||||
@@ -552,5 +565,6 @@ mod test {
|
||||
assert_eq!(l1_blob_base_fee_scalar, Some(1055762), "incorrect l1 blob base fee scalar");
|
||||
assert_eq!(operator_fee_scalar, None, "incorrect operator fee scalar");
|
||||
assert_eq!(operator_fee_constant, None, "incorrect operator fee constant");
|
||||
assert_eq!(da_footprint_gas_scalar, None, "incorrect da footprint gas scalar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ where
|
||||
self.block_info.timestamp.store(header.timestamp(), Ordering::Relaxed);
|
||||
self.block_info.number.store(header.number(), Ordering::Relaxed);
|
||||
|
||||
if let Some(Ok(cost_addition)) = tx.map(reth_optimism_evm::extract_l1_info_from_tx) {
|
||||
*self.block_info.l1_block_info.write() = cost_addition;
|
||||
if let Some(Ok(l1_block_info)) = tx.map(reth_optimism_evm::extract_l1_info_from_tx) {
|
||||
*self.block_info.l1_block_info.write() = l1_block_info;
|
||||
}
|
||||
|
||||
if self.chain_spec().is_interop_active_at_timestamp(header.timestamp()) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Compatibility functions for rpc `Transaction` type.
|
||||
|
||||
use crate::{
|
||||
fees::{CallFees, CallFeesError},
|
||||
RpcHeader, RpcReceipt, RpcTransaction, RpcTxReq, RpcTypes, SignableTxRequest,
|
||||
|
||||
Reference in New Issue
Block a user