mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
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:
@ -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,
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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"
|
||||
]
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user