feat: Eip1559 params in extradata (#11887)

Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
cody-wang-cb
2024-10-30 08:59:19 -04:00
committed by GitHub
parent 2778ba3d52
commit 93a9b8a218
16 changed files with 461 additions and 43 deletions

View File

@ -184,7 +184,7 @@ impl From<EthBuiltPayload> for ExecutionPayloadEnvelopeV4 {
}
/// Container type for all components required to build a payload.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EthPayloadBuilderAttributes {
/// Id of the payload
pub id: PayloadId,

View File

@ -17,6 +17,8 @@
extern crate alloc;
use core::convert::Infallible;
use alloc::{sync::Arc, vec::Vec};
use alloy_primitives::{Address, Bytes, TxKind, U256};
use reth_chainspec::{ChainSpec, Head};
@ -59,6 +61,7 @@ impl EthEvmConfig {
impl ConfigureEvmEnv for EthEvmConfig {
type Header = Header;
type Error = Infallible;
fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) {
transaction.fill_tx_env(tx_env, sender);
@ -131,7 +134,7 @@ impl ConfigureEvmEnv for EthEvmConfig {
&self,
parent: &Self::Header,
attributes: NextBlockEnvAttributes,
) -> (CfgEnvWithHandlerCfg, BlockEnv) {
) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), Self::Error> {
// configure evm env based on parent block
let cfg = CfgEnv::default().with_chain_id(self.chain_spec.chain().id());
@ -179,7 +182,7 @@ impl ConfigureEvmEnv for EthEvmConfig {
blob_excess_gas_and_price,
};
(CfgEnvWithHandlerCfg::new_with_spec_id(cfg, spec_id), block_env)
Ok((CfgEnvWithHandlerCfg::new_with_spec_id(cfg, spec_id), block_env))
}
}

View File

@ -73,7 +73,7 @@ where
&self,
config: &PayloadConfig<EthPayloadBuilderAttributes>,
parent: &Header,
) -> (CfgEnvWithHandlerCfg, BlockEnv) {
) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), EvmConfig::Error> {
let next_attributes = NextBlockEnvAttributes {
timestamp: config.attributes.timestamp(),
suggested_fee_recipient: config.attributes.suggested_fee_recipient(),
@ -97,7 +97,9 @@ where
&self,
args: BuildArguments<Pool, Client, EthPayloadBuilderAttributes, EthBuiltPayload>,
) -> Result<BuildOutcome<EthBuiltPayload>, PayloadBuilderError> {
let (cfg_env, block_env) = self.cfg_and_block_env(&args.config, &args.config.parent_header);
let (cfg_env, block_env) = self
.cfg_and_block_env(&args.config, &args.config.parent_header)
.map_err(PayloadBuilderError::other)?;
let pool = args.pool.clone();
default_ethereum_payload(self.evm_config.clone(), args, cfg_env, block_env, |attributes| {
@ -120,7 +122,9 @@ where
None,
);
let (cfg_env, block_env) = self.cfg_and_block_env(&args.config, &args.config.parent_header);
let (cfg_env, block_env) = self
.cfg_and_block_env(&args.config, &args.config.parent_header)
.map_err(PayloadBuilderError::other)?;
let pool = args.pool.clone();

View File

@ -57,12 +57,12 @@ std = [
"revm/std",
]
test-utils = [
"dep:parking_lot",
"reth-chainspec/test-utils",
"reth-consensus/test-utils",
"reth-primitives/test-utils",
"reth-primitives-traits/test-utils",
"reth-revm/test-utils",
"revm/test-utils",
"reth-prune-types/test-utils"
"dep:parking_lot",
"reth-chainspec/test-utils",
"reth-consensus/test-utils",
"reth-primitives/test-utils",
"reth-primitives-traits/test-utils",
"reth-revm/test-utils",
"revm/test-utils",
"reth-prune-types/test-utils"
]

View File

@ -116,6 +116,9 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static {
/// The header type used by the EVM.
type Header: BlockHeader;
/// The error type that is returned by [`Self::next_cfg_and_block_env`].
type Error: core::error::Error + Send + Sync;
/// Returns a [`TxEnv`] from a [`TransactionSigned`] and [`Address`].
fn tx_env(&self, transaction: &TransactionSigned, signer: Address) -> TxEnv {
let mut tx_env = TxEnv::default();
@ -192,7 +195,7 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static {
&self,
parent: &Self::Header,
attributes: NextBlockEnvAttributes,
) -> (CfgEnvWithHandlerCfg, BlockEnv);
) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), Self::Error>;
}
/// Represents additional attributes required to configure the next block.

View File

@ -20,10 +20,10 @@ mod op_sepolia;
use alloc::{vec, vec::Vec};
use alloy_chains::Chain;
use alloy_genesis::Genesis;
use alloy_primitives::{B256, U256};
use alloy_primitives::{Bytes, Parity, Signature, B256, U256};
pub use base::BASE_MAINNET;
pub use base_sepolia::BASE_SEPOLIA;
use derive_more::{Constructor, Deref, From, Into};
use derive_more::{Constructor, Deref, Display, From, Into};
pub use dev::OP_DEV;
#[cfg(not(feature = "std"))]
pub(crate) use once_cell::sync::Lazy as LazyLock;
@ -159,6 +159,16 @@ impl OpChainSpecBuilder {
self
}
/// Enable Holocene at genesis
pub fn holocene_activated(mut self) -> Self {
self = self.granite_activated();
self.inner = self.inner.with_fork(
reth_optimism_forks::OptimismHardfork::Holocene,
ForkCondition::Timestamp(0),
);
self
}
/// Build the resulting [`OpChainSpec`].
///
/// # Panics
@ -177,6 +187,81 @@ pub struct OpChainSpec {
pub inner: ChainSpec,
}
impl OpChainSpec {
/// Read from parent to determine the base fee for the next block
pub fn next_block_base_fee(
&self,
parent: &Header,
timestamp: u64,
) -> Result<U256, DecodeError> {
let is_holocene_activated = self.inner.is_fork_active_at_timestamp(
reth_optimism_forks::OptimismHardfork::Holocene,
timestamp,
);
// If we are in the Holocene, we need to use the base fee params
// from the parent block's extra data.
// Else, use the base fee params (default values) from chainspec
if is_holocene_activated {
let (denominator, elasticity) = decode_holocene_1559_params(parent.extra_data.clone())?;
if elasticity == 0 && denominator == 0 {
return Ok(U256::from(
parent
.next_block_base_fee(self.base_fee_params_at_timestamp(timestamp))
.unwrap_or_default(),
));
}
let base_fee_params = BaseFeeParams::new(denominator as u128, elasticity as u128);
Ok(U256::from(parent.next_block_base_fee(base_fee_params).unwrap_or_default()))
} else {
Ok(U256::from(
parent
.next_block_base_fee(self.base_fee_params_at_timestamp(timestamp))
.unwrap_or_default(),
))
}
}
}
#[derive(Clone, Debug, Display, Eq, PartialEq)]
/// Error type for decoding Holocene 1559 parameters
pub enum DecodeError {
#[display("Insufficient data to decode")]
/// Insufficient data to decode
InsufficientData,
#[display("Invalid denominator parameter")]
/// Invalid denominator parameter
InvalidDenominator,
#[display("Invalid elasticity parameter")]
/// Invalid elasticity parameter
InvalidElasticity,
}
impl core::error::Error for DecodeError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
// None of the errors have sub-errors
None
}
}
/// Extracts the Holcene 1599 parameters from the encoded form:
/// <https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/holocene/exec-engine.md#eip1559params-encoding>
pub fn decode_holocene_1559_params(extra_data: Bytes) -> Result<(u32, u32), DecodeError> {
if extra_data.len() < 9 {
return Err(DecodeError::InsufficientData);
}
let denominator: [u8; 4] =
extra_data[1..5].try_into().map_err(|_| DecodeError::InvalidDenominator)?;
let elasticity: [u8; 4] =
extra_data[5..9].try_into().map_err(|_| DecodeError::InvalidElasticity)?;
Ok((u32::from_be_bytes(denominator), u32::from_be_bytes(elasticity)))
}
/// Returns the signature for the optimism deposit transactions, which don't include a
/// signature.
pub fn optimism_deposit_tx_signature() -> Signature {
Signature::new(U256::ZERO, U256::ZERO, Parity::Parity(false))
}
impl EthChainSpec for OpChainSpec {
fn chain(&self) -> alloy_chains::Chain {
self.inner.chain()
@ -405,6 +490,8 @@ impl OptimismGenesisInfo {
#[cfg(test)]
mod tests {
use std::sync::Arc;
use alloy_genesis::{ChainConfig, Genesis};
use alloy_primitives::b256;
use reth_chainspec::{test_fork_ids, BaseFeeParams, BaseFeeParamsKind};
@ -919,4 +1006,87 @@ mod tests {
.all(|(expected, actual)| &**expected == *actual));
assert_eq!(expected_hardforks.len(), hardforks.len());
}
#[test]
fn test_get_base_fee_pre_holocene() {
let op_chain_spec = &BASE_SEPOLIA;
let parent = Header {
base_fee_per_gas: Some(1),
gas_used: 15763614,
gas_limit: 144000000,
..Default::default()
};
let base_fee = op_chain_spec.next_block_base_fee(&parent, 0);
assert_eq!(
base_fee.unwrap(),
U256::from(
parent
.next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0))
.unwrap_or_default()
)
);
}
fn holocene_chainspec() -> Arc<OpChainSpec> {
let mut hardforks = OptimismHardfork::base_sepolia();
hardforks.insert(OptimismHardfork::Holocene.boxed(), ForkCondition::Timestamp(1800000000));
Arc::new(OpChainSpec {
inner: ChainSpec {
chain: BASE_SEPOLIA.inner.chain,
genesis: BASE_SEPOLIA.inner.genesis.clone(),
genesis_hash: BASE_SEPOLIA.inner.genesis_hash.clone(),
paris_block_and_final_difficulty: Some((0, U256::from(0))),
hardforks,
base_fee_params: BASE_SEPOLIA.inner.base_fee_params.clone(),
max_gas_limit: crate::constants::BASE_SEPOLIA_MAX_GAS_LIMIT,
prune_delete_limit: 10000,
..Default::default()
},
})
}
#[test]
fn test_get_base_fee_holocene_nonce_not_set() {
let op_chain_spec = holocene_chainspec();
let parent = Header {
base_fee_per_gas: Some(1),
gas_used: 15763614,
gas_limit: 144000000,
timestamp: 1800000003,
extra_data: Bytes::from_static(&[0, 0, 0, 0, 0, 0, 0, 0, 0]),
..Default::default()
};
let base_fee = op_chain_spec.next_block_base_fee(&parent, 1800000005);
assert_eq!(
base_fee.unwrap(),
U256::from(
parent
.next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0))
.unwrap_or_default()
)
);
}
#[test]
fn test_get_base_fee_holocene_nonce_set() {
let op_chain_spec = holocene_chainspec();
let parent = Header {
base_fee_per_gas: Some(1),
gas_used: 15763614,
gas_limit: 144000000,
extra_data: Bytes::from_static(&[0, 0, 0, 0, 8, 0, 0, 0, 8]),
timestamp: 1800000003,
..Default::default()
};
let base_fee = op_chain_spec.next_block_base_fee(&parent, 1800000005);
assert_eq!(
base_fee.unwrap(),
U256::from(
parent
.next_block_base_fee(BaseFeeParams::new(0x00000008, 0x00000008))
.unwrap_or_default()
)
);
}
}

View File

@ -12,7 +12,9 @@ pub fn revm_spec_by_timestamp_after_bedrock(
chain_spec: &OpChainSpec,
timestamp: u64,
) -> revm_primitives::SpecId {
if chain_spec.fork(OptimismHardfork::Granite).active_at_timestamp(timestamp) {
if chain_spec.fork(OptimismHardfork::Holocene).active_at_timestamp(timestamp) {
revm_primitives::HOLOCENE
} else if chain_spec.fork(OptimismHardfork::Granite).active_at_timestamp(timestamp) {
revm_primitives::GRANITE
} else if chain_spec.fork(OptimismHardfork::Fjord).active_at_timestamp(timestamp) {
revm_primitives::FJORD
@ -29,7 +31,9 @@ pub fn revm_spec_by_timestamp_after_bedrock(
/// Map the latest active hardfork at the given block to a revm [`SpecId`](revm_primitives::SpecId).
pub fn revm_spec(chain_spec: &OpChainSpec, block: &Head) -> revm_primitives::SpecId {
if chain_spec.fork(OptimismHardfork::Granite).active_at_head(block) {
if chain_spec.fork(OptimismHardfork::Holocene).active_at_head(block) {
revm_primitives::HOLOCENE
} else if chain_spec.fork(OptimismHardfork::Granite).active_at_head(block) {
revm_primitives::GRANITE
} else if chain_spec.fork(OptimismHardfork::Fjord).active_at_head(block) {
revm_primitives::FJORD

View File

@ -15,7 +15,7 @@ extern crate alloc;
use alloc::{sync::Arc, vec::Vec};
use alloy_primitives::{Address, U256};
use reth_evm::{ConfigureEvm, ConfigureEvmEnv, NextBlockEnvAttributes};
use reth_optimism_chainspec::OpChainSpec;
use reth_optimism_chainspec::{DecodeError, OpChainSpec};
use reth_primitives::{
revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv},
transaction::FillTxEnv,
@ -56,6 +56,7 @@ impl OptimismEvmConfig {
impl ConfigureEvmEnv for OptimismEvmConfig {
type Header = Header;
type Error = DecodeError;
fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) {
transaction.fill_tx_env(tx_env, sender);
@ -134,7 +135,7 @@ impl ConfigureEvmEnv for OptimismEvmConfig {
&self,
parent: &Self::Header,
attributes: NextBlockEnvAttributes,
) -> (CfgEnvWithHandlerCfg, BlockEnv) {
) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), Self::Error> {
// configure evm env based on parent block
let cfg = CfgEnv::default().with_chain_id(self.chain_spec.chain().id());
@ -156,13 +157,7 @@ impl ConfigureEvmEnv for OptimismEvmConfig {
prevrandao: Some(attributes.prev_randao),
gas_limit: U256::from(parent.gas_limit),
// calculate basefee based on parent block's gas usage
basefee: U256::from(
parent
.next_block_base_fee(
self.chain_spec.base_fee_params_at_timestamp(attributes.timestamp),
)
.unwrap_or_default(),
),
basefee: self.chain_spec.next_block_base_fee(parent, attributes.timestamp)?,
// calculate excess gas based on parent block's blob gas usage
blob_excess_gas_and_price,
};
@ -175,7 +170,7 @@ impl ConfigureEvmEnv for OptimismEvmConfig {
};
}
(cfg_with_handler_cfg, block_env)
Ok((cfg_with_handler_cfg, block_env))
}
}

View File

@ -31,6 +31,8 @@ hardfork!(
Fjord,
/// Granite: <https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/superchain-upgrades.md#granite>
Granite,
/// Holocene: <https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/superchain-upgrades.md#holocene>
Holocene,
}
);
@ -156,6 +158,7 @@ impl OptimismHardfork {
Self::Ecotone => Some(1708534800),
Self::Fjord => Some(1716998400),
Self::Granite => Some(1723478400),
Self::Holocene => None,
},
)
}
@ -190,6 +193,7 @@ impl OptimismHardfork {
Self::Ecotone => Some(1710374401),
Self::Fjord => Some(1720627201),
Self::Granite => Some(1726070401),
Self::Holocene => None,
},
)
}

View File

@ -15,7 +15,9 @@ use reth_node_api::{
};
use reth_optimism_chainspec::OpChainSpec;
use reth_optimism_forks::OptimismHardfork;
use reth_optimism_payload_builder::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes};
use reth_optimism_payload_builder::{
builder::decode_eip_1559_params, OptimismBuiltPayload, OptimismPayloadBuilderAttributes,
};
/// The types used in the optimism beacon consensus engine.
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
@ -147,6 +149,139 @@ where
))
}
if self.chain_spec.is_fork_active_at_timestamp(
OptimismHardfork::Holocene,
attributes.payload_attributes.timestamp,
) {
let Some(eip_1559_params) = attributes.eip_1559_params else {
return Err(EngineObjectValidationError::InvalidParams(
"MissingEip1559ParamsInPayloadAttributes".to_string().into(),
))
};
let (elasticity, denominator) = decode_eip_1559_params(eip_1559_params);
if elasticity != 0 && denominator == 0 {
return Err(EngineObjectValidationError::InvalidParams(
"Eip1559ParamsDenominatorZero".to_string().into(),
))
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::engine;
use alloy_primitives::{b64, Address, B256, B64};
use alloy_rpc_types_engine::PayloadAttributes;
use reth_chainspec::ForkCondition;
use reth_optimism_chainspec::BASE_SEPOLIA;
use super::*;
fn get_chainspec(is_holocene: bool) -> Arc<OpChainSpec> {
let mut hardforks = OptimismHardfork::base_sepolia();
if is_holocene {
hardforks
.insert(OptimismHardfork::Holocene.boxed(), ForkCondition::Timestamp(1800000000));
}
Arc::new(OpChainSpec {
inner: ChainSpec {
chain: BASE_SEPOLIA.inner.chain,
genesis: BASE_SEPOLIA.inner.genesis.clone(),
genesis_hash: BASE_SEPOLIA.inner.genesis_hash.clone(),
paris_block_and_final_difficulty: BASE_SEPOLIA
.inner
.paris_block_and_final_difficulty,
hardforks,
base_fee_params: BASE_SEPOLIA.inner.base_fee_params.clone(),
max_gas_limit: BASE_SEPOLIA.inner.max_gas_limit,
prune_delete_limit: 10000,
..Default::default()
},
})
}
const fn get_attributes(eip_1559_params: Option<B64>, timestamp: u64) -> OpPayloadAttributes {
OpPayloadAttributes {
gas_limit: Some(1000),
eip_1559_params,
transactions: None,
no_tx_pool: None,
payload_attributes: PayloadAttributes {
timestamp,
prev_randao: B256::ZERO,
suggested_fee_recipient: Address::ZERO,
withdrawals: Some(vec![]),
parent_beacon_block_root: Some(B256::ZERO),
},
}
}
#[test]
fn test_well_formed_attributes_pre_holocene() {
let validator = OptimismEngineValidator::new(get_chainspec(false));
let attributes = get_attributes(None, 1799999999);
let result = <engine::OptimismEngineValidator as reth_node_builder::EngineValidator<
OptimismEngineTypes,
>>::ensure_well_formed_attributes(
&validator, EngineApiMessageVersion::V3, &attributes
);
assert!(result.is_ok());
}
#[test]
fn test_well_formed_attributes_holocene_no_eip1559_params() {
let validator = OptimismEngineValidator::new(get_chainspec(true));
let attributes = get_attributes(None, 1800000000);
let result = <engine::OptimismEngineValidator as reth_node_builder::EngineValidator<
OptimismEngineTypes,
>>::ensure_well_formed_attributes(
&validator, EngineApiMessageVersion::V3, &attributes
);
assert!(matches!(result, Err(EngineObjectValidationError::InvalidParams(_))));
}
#[test]
fn test_well_formed_attributes_holocene_eip1559_params_zero_denominator() {
let validator = OptimismEngineValidator::new(get_chainspec(true));
let attributes = get_attributes(Some(b64!("0000000000000008")), 1800000000);
let result = <engine::OptimismEngineValidator as reth_node_builder::EngineValidator<
OptimismEngineTypes,
>>::ensure_well_formed_attributes(
&validator, EngineApiMessageVersion::V3, &attributes
);
assert!(matches!(result, Err(EngineObjectValidationError::InvalidParams(_))));
}
#[test]
fn test_well_formed_attributes_holocene_valid() {
let validator = OptimismEngineValidator::new(get_chainspec(true));
let attributes = get_attributes(Some(b64!("0000000800000008")), 1800000000);
let result = <engine::OptimismEngineValidator as reth_node_builder::EngineValidator<
OptimismEngineTypes,
>>::ensure_well_formed_attributes(
&validator, EngineApiMessageVersion::V3, &attributes
);
assert!(result.is_ok());
}
#[test]
fn test_well_formed_attributes_holocene_valid_all_zero() {
let validator = OptimismEngineValidator::new(get_chainspec(true));
let attributes = get_attributes(Some(b64!("0000000000000000")), 1800000000);
let result = <engine::OptimismEngineValidator as reth_node_builder::EngineValidator<
OptimismEngineTypes,
>>::ensure_well_formed_attributes(
&validator, EngineApiMessageVersion::V3, &attributes
);
assert!(result.is_ok());
}
}

View File

@ -63,5 +63,6 @@ pub(crate) fn optimism_payload_attributes(timestamp: u64) -> OptimismPayloadBuil
transactions: vec![],
no_tx_pool: false,
gas_limit: Some(30_000_000),
eip_1559_params: None,
}
}

View File

@ -1,10 +1,9 @@
//! Optimism payload builder implementation.
use std::sync::Arc;
use alloy_consensus::EMPTY_OMMER_ROOT_HASH;
use alloy_eips::merge::BEACON_NONCE;
use alloy_primitives::U256;
use alloy_primitives::{B64, U256};
use reth_basic_payload_builder::*;
use reth_chain_state::ExecutedBlock;
use reth_chainspec::ChainSpecProvider;
@ -80,7 +79,7 @@ where
&self,
config: &PayloadConfig<OptimismPayloadBuilderAttributes>,
parent: &Header,
) -> (CfgEnvWithHandlerCfg, BlockEnv) {
) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), EvmConfig::Error> {
let next_attributes = NextBlockEnvAttributes {
timestamp: config.attributes.timestamp(),
suggested_fee_recipient: config.attributes.suggested_fee_recipient(),
@ -104,7 +103,9 @@ where
&self,
args: BuildArguments<Pool, Client, OptimismPayloadBuilderAttributes, OptimismBuiltPayload>,
) -> Result<BuildOutcome<OptimismBuiltPayload>, PayloadBuilderError> {
let (cfg_env, block_env) = self.cfg_and_block_env(&args.config, &args.config.parent_header);
let (cfg_env, block_env) = self
.cfg_and_block_env(&args.config, &args.config.parent_header)
.map_err(PayloadBuilderError::other)?;
optimism_payload(&self.evm_config, args, cfg_env, block_env, self.compute_pending_block)
}
@ -133,7 +134,9 @@ where
cancel: Default::default(),
best_payload: None,
};
let (cfg_env, block_env) = self.cfg_and_block_env(&args.config, &args.config.parent_header);
let (cfg_env, block_env) = self
.cfg_and_block_env(&args.config, &args.config.parent_header)
.map_err(PayloadBuilderError::other)?;
optimism_payload(&self.evm_config, args, cfg_env, block_env, false)?
.into_payload()
.ok_or_else(|| PayloadBuilderError::MissingPayload)
@ -168,7 +171,7 @@ where
let state = StateProviderDatabase::new(state_provider);
let mut db =
State::builder().with_database_ref(cached_reads.as_db(state)).with_bundle_update().build();
let PayloadConfig { parent_header, attributes, extra_data } = config;
let PayloadConfig { parent_header, attributes, mut extra_data } = config;
debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload");
@ -470,6 +473,19 @@ where
(None, None)
};
let is_holocene = chain_spec.is_fork_active_at_timestamp(
OptimismHardfork::Holocene,
attributes.payload_attributes.timestamp,
);
if is_holocene {
extra_data = attributes
.get_holocene_extra_data(
chain_spec.base_fee_params_at_timestamp(attributes.payload_attributes.timestamp),
)
.map_err(PayloadBuilderError::other)?;
}
let header = Header {
parent_hash: parent_header.hash(),
ommers_hash: EMPTY_OMMER_ROOT_HASH,
@ -532,3 +548,12 @@ where
Ok(BuildOutcome::Better { payload, cached_reads })
}
}
/// Extracts the Holocene 1599 parameters from the encoded form:
/// <https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/holocene/exec-engine.md#eip1559params-encoding>
pub fn decode_eip_1559_params(eip_1559_params: B64) -> (u32, u32) {
let denominator: [u8; 4] = eip_1559_params.0[..4].try_into().expect("sufficient length");
let elasticity: [u8; 4] = eip_1559_params.0[4..8].try_into().expect("sufficient length");
(u32::from_be_bytes(elasticity), u32::from_be_bytes(denominator))
}

View File

@ -21,3 +21,17 @@ pub enum OptimismPayloadBuilderError {
#[error("blob transaction included in sequencer block")]
BlobTransactionRejected,
}
/// Error type for EIP-1559 parameters
#[derive(Debug, thiserror::Error)]
pub enum EIP1559ParamError {
/// No EIP-1559 parameters provided
#[error("No EIP-1559 parameters provided")]
NoEIP1559Params,
/// Denominator overflow
#[error("Denominator overflow")]
DenominatorOverflow,
/// Elasticity overflow
#[error("Elasticity overflow")]
ElasticityOverflow,
}

View File

@ -2,8 +2,9 @@
//! Optimism builder support
use alloy_eips::{eip2718::Decodable2718, eip7685::Requests};
use alloy_primitives::{keccak256, Address, B256, U256};
use crate::{builder::decode_eip_1559_params, error::EIP1559ParamError};
use alloy_eips::{eip1559::BaseFeeParams, eip2718::Decodable2718, eip7685::Requests};
use alloy_primitives::{keccak256, Address, Bytes, B256, B64, U256};
use alloy_rlp::Encodable;
use alloy_rpc_types_engine::{ExecutionPayloadEnvelopeV2, ExecutionPayloadV1, PayloadId};
/// Re-export for use in downstream arguments.
@ -23,7 +24,7 @@ use reth_rpc_types_compat::engine::payload::{
use std::sync::Arc;
/// Optimism Payload Builder Attributes
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct OptimismPayloadBuilderAttributes {
/// Inner ethereum payload builder attributes
pub payload_attributes: EthPayloadBuilderAttributes,
@ -34,6 +35,42 @@ pub struct OptimismPayloadBuilderAttributes {
pub transactions: Vec<WithEncoded<TransactionSigned>>,
/// The gas limit for the generated payload
pub gas_limit: Option<u64>,
/// EIP-1559 parameters for the generated payload
pub eip_1559_params: Option<B64>,
}
impl OptimismPayloadBuilderAttributes {
/// Extracts the `eip1559` parameters for the payload.
pub fn get_holocene_extra_data(
&self,
default_base_fee_params: BaseFeeParams,
) -> Result<Bytes, EIP1559ParamError> {
let eip_1559_params = self.eip_1559_params.ok_or(EIP1559ParamError::NoEIP1559Params)?;
let mut extra_data = [0u8; 9];
// If eip 1559 params aren't set, use the canyon base fee param constants
// otherwise use them
if eip_1559_params.is_zero() {
// Try casting max_change_denominator to u32
let max_change_denominator: u32 = (default_base_fee_params.max_change_denominator)
.try_into()
.map_err(|_| EIP1559ParamError::DenominatorOverflow)?;
// Try casting elasticity_multiplier to u32
let elasticity_multiplier: u32 = (default_base_fee_params.elasticity_multiplier)
.try_into()
.map_err(|_| EIP1559ParamError::ElasticityOverflow)?;
// Copy the values safely
extra_data[1..5].copy_from_slice(&max_change_denominator.to_be_bytes());
extra_data[5..9].copy_from_slice(&elasticity_multiplier.to_be_bytes());
} else {
let (elasticity, denominator) = decode_eip_1559_params(eip_1559_params);
extra_data[1..5].copy_from_slice(&denominator.to_be_bytes());
extra_data[5..9].copy_from_slice(&elasticity.to_be_bytes());
}
Ok(Bytes::copy_from_slice(&extra_data))
}
}
impl PayloadBuilderAttributes for OptimismPayloadBuilderAttributes {
@ -82,6 +119,7 @@ impl PayloadBuilderAttributes for OptimismPayloadBuilderAttributes {
no_tx_pool: attributes.no_tx_pool.unwrap_or_default(),
transactions,
gas_limit: attributes.gas_limit,
eip_1559_params: attributes.eip_1559_params,
})
}
@ -370,4 +408,24 @@ mod tests {
)
);
}
#[test]
fn test_get_extra_data_post_holocene() {
let attributes = OptimismPayloadBuilderAttributes {
eip_1559_params: Some(B64::from_str("0x0000000800000008").unwrap()),
..Default::default()
};
let extra_data = attributes.get_holocene_extra_data(BaseFeeParams::new(80, 60));
assert_eq!(extra_data.unwrap(), Bytes::copy_from_slice(&[0, 0, 0, 0, 8, 0, 0, 0, 8]));
}
#[test]
fn test_get_extra_data_post_holocene_default() {
let attributes = OptimismPayloadBuilderAttributes {
eip_1559_params: Some(B64::ZERO),
..Default::default()
};
let extra_data = attributes.get_holocene_extra_data(BaseFeeParams::new(80, 60));
assert_eq!(extra_data.unwrap(), Bytes::copy_from_slice(&[0, 0, 0, 0, 80, 0, 0, 0, 60]));
}
}

View File

@ -38,7 +38,7 @@ use reth_primitives::{
Header, TransactionSigned,
};
use reth_tracing::{RethTracer, Tracer};
use std::sync::Arc;
use std::{convert::Infallible, sync::Arc};
/// Custom EVM configuration
#[derive(Debug, Clone)]
@ -87,6 +87,7 @@ impl MyEvmConfig {
impl ConfigureEvmEnv for MyEvmConfig {
type Header = Header;
type Error = Infallible;
fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) {
self.inner.fill_tx_env(tx_env, transaction, sender);
@ -115,7 +116,7 @@ impl ConfigureEvmEnv for MyEvmConfig {
&self,
parent: &Self::Header,
attributes: NextBlockEnvAttributes,
) -> (CfgEnvWithHandlerCfg, BlockEnv) {
) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), Self::Error> {
self.inner.next_cfg_and_block_env(parent, attributes)
}
}

View File

@ -30,7 +30,7 @@ use reth_primitives::{
};
use reth_tracing::{RethTracer, Tracer};
use schnellru::{ByLength, LruMap};
use std::{collections::HashMap, sync::Arc};
use std::{collections::HashMap, convert::Infallible, sync::Arc};
/// Type alias for the LRU cache used within the [`PrecompileCache`].
type PrecompileLRUCache = LruMap<(Bytes, u64), PrecompileResult>;
@ -147,6 +147,7 @@ impl StatefulPrecompileMut for WrappedPrecompile {
impl ConfigureEvmEnv for MyEvmConfig {
type Header = Header;
type Error = Infallible;
fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) {
self.inner.fill_tx_env(tx_env, transaction, sender)
@ -175,7 +176,7 @@ impl ConfigureEvmEnv for MyEvmConfig {
&self,
parent: &Self::Header,
attributes: NextBlockEnvAttributes,
) -> (CfgEnvWithHandlerCfg, BlockEnv) {
) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), Self::Error> {
self.inner.next_cfg_and_block_env(parent, attributes)
}
}