//! Contains types required for building a payload. use alloy_rlp::Encodable; use reth_chainspec::ChainSpec; use reth_evm_ethereum::revm_spec_by_timestamp_after_merge; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_primitives::{ constants::EIP1559_INITIAL_BASE_FEE, Address, BlobTransactionSidecar, EthereumHardfork, Header, SealedBlock, Withdrawals, B256, U256, }; use reth_rpc_types::engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, ExecutionPayloadV1, PayloadAttributes, PayloadId, }; use reth_rpc_types_compat::engine::payload::{ block_to_payload_v1, block_to_payload_v3, block_to_payload_v4, convert_block_to_payload_field_v2, }; use revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}; use std::convert::Infallible; /// Contains the built payload. /// /// According to the [engine API specification](https://github.com/ethereum/execution-apis/blob/main/src/engine/README.md) the execution layer should build the initial version of the payload with an empty transaction set and then keep update it in order to maximize the revenue. /// Therefore, the empty-block here is always available and full-block will be set/updated /// afterward. #[derive(Debug, Clone)] pub struct EthBuiltPayload { /// Identifier of the payload pub(crate) id: PayloadId, /// The built block pub(crate) block: SealedBlock, /// The fees of the block pub(crate) fees: U256, /// The blobs, proofs, and commitments in the block. If the block is pre-cancun, this will be /// empty. pub(crate) sidecars: Vec, } // === impl BuiltPayload === impl EthBuiltPayload { /// Initializes the payload with the given initial block. pub const fn new(id: PayloadId, block: SealedBlock, fees: U256) -> Self { Self { id, block, fees, sidecars: Vec::new() } } /// Returns the identifier of the payload. pub const fn id(&self) -> PayloadId { self.id } /// Returns the built block(sealed) pub const fn block(&self) -> &SealedBlock { &self.block } /// Fees of the block pub const fn fees(&self) -> U256 { self.fees } /// Returns the blob sidecars. pub fn sidecars(&self) -> &[BlobTransactionSidecar] { &self.sidecars } /// Adds sidecars to the payload. pub fn extend_sidecars(&mut self, sidecars: Vec) { self.sidecars.extend(sidecars) } } impl BuiltPayload for EthBuiltPayload { fn block(&self) -> &SealedBlock { &self.block } fn fees(&self) -> U256 { self.fees } } impl<'a> BuiltPayload for &'a EthBuiltPayload { fn block(&self) -> &SealedBlock { (**self).block() } fn fees(&self) -> U256 { (**self).fees() } } // V1 engine_getPayloadV1 response impl From for ExecutionPayloadV1 { fn from(value: EthBuiltPayload) -> Self { block_to_payload_v1(value.block) } } // V2 engine_getPayloadV2 response impl From for ExecutionPayloadEnvelopeV2 { fn from(value: EthBuiltPayload) -> Self { let EthBuiltPayload { block, fees, .. } = value; Self { block_value: fees, execution_payload: convert_block_to_payload_field_v2(block) } } } impl From for ExecutionPayloadEnvelopeV3 { fn from(value: EthBuiltPayload) -> Self { let EthBuiltPayload { block, fees, sidecars, .. } = value; Self { execution_payload: block_to_payload_v3(block), block_value: fees, // From the engine API spec: // // > Client software **MAY** use any heuristics to decide whether to set // `shouldOverrideBuilder` flag or not. If client software does not implement any // heuristic this flag **SHOULD** be set to `false`. // // Spec: // should_override_builder: false, blobs_bundle: sidecars.into_iter().map(Into::into).collect::>().into(), } } } impl From for ExecutionPayloadEnvelopeV4 { fn from(value: EthBuiltPayload) -> Self { let EthBuiltPayload { block, fees, sidecars, .. } = value; Self { execution_payload: block_to_payload_v4(block), block_value: fees, // From the engine API spec: // // > Client software **MAY** use any heuristics to decide whether to set // `shouldOverrideBuilder` flag or not. If client software does not implement any // heuristic this flag **SHOULD** be set to `false`. // // Spec: // should_override_builder: false, blobs_bundle: sidecars.into_iter().map(Into::into).collect::>().into(), } } } /// Container type for all components required to build a payload. #[derive(Debug, Clone, PartialEq, Eq)] pub struct EthPayloadBuilderAttributes { /// Id of the payload pub id: PayloadId, /// Parent block to build the payload on top pub parent: B256, /// Unix timestamp for the generated payload /// /// Number of seconds since the Unix epoch. pub timestamp: u64, /// Address of the recipient for collecting transaction fee pub suggested_fee_recipient: Address, /// Randomness value for the generated payload pub prev_randao: B256, /// Withdrawals for the generated payload pub withdrawals: Withdrawals, /// Root of the parent beacon block pub parent_beacon_block_root: Option, } // === impl EthPayloadBuilderAttributes === impl EthPayloadBuilderAttributes { /// Returns the identifier of the payload. pub const fn payload_id(&self) -> PayloadId { self.id } /// Creates a new payload builder for the given parent block and the attributes. /// /// Derives the unique [`PayloadId`] for the given parent and attributes pub fn new(parent: B256, attributes: PayloadAttributes) -> Self { let id = payload_id(&parent, &attributes); Self { id, parent, timestamp: attributes.timestamp, suggested_fee_recipient: attributes.suggested_fee_recipient, prev_randao: attributes.prev_randao, withdrawals: attributes.withdrawals.unwrap_or_default().into(), parent_beacon_block_root: attributes.parent_beacon_block_root, } } } impl PayloadBuilderAttributes for EthPayloadBuilderAttributes { type RpcPayloadAttributes = PayloadAttributes; type Error = Infallible; /// Creates a new payload builder for the given parent block and the attributes. /// /// Derives the unique [`PayloadId`] for the given parent and attributes fn try_new(parent: B256, attributes: PayloadAttributes) -> Result { Ok(Self::new(parent, attributes)) } fn payload_id(&self) -> PayloadId { self.id } fn parent(&self) -> B256 { self.parent } fn timestamp(&self) -> u64 { self.timestamp } fn parent_beacon_block_root(&self) -> Option { self.parent_beacon_block_root } fn suggested_fee_recipient(&self) -> Address { self.suggested_fee_recipient } fn prev_randao(&self) -> B256 { self.prev_randao } fn withdrawals(&self) -> &Withdrawals { &self.withdrawals } fn cfg_and_block_env( &self, chain_spec: &ChainSpec, parent: &Header, ) -> (CfgEnvWithHandlerCfg, BlockEnv) { // configure evm env based on parent block let cfg = CfgEnv::default().with_chain_id(chain_spec.chain().id()); // ensure we're not missing any timestamp based hardforks let spec_id = revm_spec_by_timestamp_after_merge(chain_spec, self.timestamp()); // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is // cancun now, we need to set the excess blob gas to the default value let blob_excess_gas_and_price = parent .next_block_excess_blob_gas() .or_else(|| { if spec_id == SpecId::CANCUN { // default excess blob gas is zero Some(0) } else { None } }) .map(BlobExcessGasAndPrice::new); let mut basefee = parent.next_block_base_fee(chain_spec.base_fee_params_at_timestamp(self.timestamp())); let mut gas_limit = U256::from(parent.gas_limit); // If we are on the London fork boundary, we need to multiply the parent's gas limit by the // elasticity multiplier to get the new gas limit. if chain_spec.fork(EthereumHardfork::London).transitions_at_block(parent.number + 1) { let elasticity_multiplier = chain_spec.base_fee_params_at_timestamp(self.timestamp()).elasticity_multiplier; // multiply the gas limit by the elasticity multiplier gas_limit *= U256::from(elasticity_multiplier); // set the base fee to the initial base fee from the EIP-1559 spec basefee = Some(EIP1559_INITIAL_BASE_FEE) } let block_env = BlockEnv { number: U256::from(parent.number + 1), coinbase: self.suggested_fee_recipient(), timestamp: U256::from(self.timestamp()), difficulty: U256::ZERO, prevrandao: Some(self.prev_randao()), gas_limit, // calculate basefee based on parent block's gas usage basefee: basefee.map(U256::from).unwrap_or_default(), // calculate excess gas based on parent block's blob gas usage blob_excess_gas_and_price, }; (CfgEnvWithHandlerCfg::new_with_spec_id(cfg, spec_id), block_env) } } /// Generates the payload id for the configured payload from the [`PayloadAttributes`]. /// /// Returns an 8-byte identifier by hashing the payload components with sha256 hash. pub(crate) fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId { use sha2::Digest; let mut hasher = sha2::Sha256::new(); hasher.update(parent.as_slice()); hasher.update(&attributes.timestamp.to_be_bytes()[..]); hasher.update(attributes.prev_randao.as_slice()); hasher.update(attributes.suggested_fee_recipient.as_slice()); if let Some(withdrawals) = &attributes.withdrawals { let mut buf = Vec::new(); withdrawals.encode(&mut buf); hasher.update(buf); } if let Some(parent_beacon_block) = attributes.parent_beacon_block_root { hasher.update(parent_beacon_block); } let out = hasher.finalize(); PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length")) } #[cfg(test)] mod tests { use super::*; use reth_primitives::Genesis; #[test] fn ensure_first_london_block_base_fee_is_set() { let hive_london = r#" { "config": { "ethash": {}, "chainId": 7, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "muirGlacierBlock": 0, "berlinBlock": 0, "londonBlock": 1, "mergeNetsplitBlock": 1, "terminalTotalDifficulty": 196608, "shanghaiTime": 4662, "cancunTime": 4662 }, "nonce": "0x0", "timestamp": "0x1234", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "gasLimit": "0x2fefd8", "difficulty": "0x30000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": { "0000000000000000000000000000000000000314": { "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000001234", "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x0000000000000000000000000000000000000000000000000000000000000001" }, "balance": "0x0" }, "0000000000000000000000000000000000000315": { "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029", "balance": "0x9999999999999999999999999999999" }, "0000000000000000000000000000000000000316": { "code": "0x444355", "balance": "0x0" }, "0000000000000000000000000000000000000317": { "code": "0x600160003555", "balance": "0x0" }, "000f3df6d732807ef1319fb7b8bb8522d0beac02": { "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", "balance": "0x0", "nonce": "0x1" } }, "number": "0x0", "gasUsed": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "baseFeePerGas": null, "excessBlobGas": null, "blobGasUsed": null } "#; let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#; let attributes: PayloadAttributes = serde_json::from_str(attributes).unwrap(); // check that it deserializes properly let genesis: Genesis = serde_json::from_str(hive_london).unwrap(); let chainspec = ChainSpec::from(genesis); let payload_builder_attributes = EthPayloadBuilderAttributes::new(chainspec.genesis_hash(), attributes); // use cfg_and_block_env let cfg_and_block_env = payload_builder_attributes.cfg_and_block_env(&chainspec, &chainspec.genesis_header()); // ensure the base fee is non zero assert_eq!(cfg_and_block_env.1.basefee, U256::from(EIP1559_INITIAL_BASE_FEE)); // ensure the gas limit is double the previous block's gas limit assert_eq!( cfg_and_block_env.1.gas_limit, U256::from(chainspec.genesis_header().gas_limit * 2) ); } }