diff --git a/Cargo.lock b/Cargo.lock index 95a570376bef..69f193ee5a1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8702,7 +8702,6 @@ dependencies = [ "reth-chainspec", "reth-primitives", "reth-primitives-traits", - "reth-rpc-types-compat", ] [[package]] @@ -9227,7 +9226,6 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", - "alloy-rlp", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "jsonrpsee-types", diff --git a/crates/payload/validator/Cargo.toml b/crates/payload/validator/Cargo.toml index fee8d01d2baa..5c34a9f456f9 100644 --- a/crates/payload/validator/Cargo.toml +++ b/crates/payload/validator/Cargo.toml @@ -16,7 +16,6 @@ workspace = true reth-chainspec.workspace = true reth-primitives.workspace = true reth-primitives-traits.workspace = true -reth-rpc-types-compat.workspace = true # alloy alloy-rpc-types = { workspace = true, features = ["engine"] } diff --git a/crates/payload/validator/src/lib.rs b/crates/payload/validator/src/lib.rs index 30f1ca02b964..c1b231a7764a 100644 --- a/crates/payload/validator/src/lib.rs +++ b/crates/payload/validator/src/lib.rs @@ -14,7 +14,6 @@ use alloy_rpc_types::engine::{ use reth_chainspec::EthereumHardforks; use reth_primitives::{BlockBody, BlockExt, Header, SealedBlock}; use reth_primitives_traits::SignedTransaction; -use reth_rpc_types_compat::engine::payload::try_into_block; use std::sync::Arc; /// Execution payload validator. @@ -121,7 +120,7 @@ impl ExecutionPayloadValidator { let expected_hash = payload.block_hash(); // First parse the block - let sealed_block = try_into_block(payload, &sidecar)?.seal_slow(); + let sealed_block = payload.try_into_block_with_sidecar(&sidecar)?.seal_slow(); // Ensure the hash included in the payload matches the block hash if expected_hash != sealed_block.hash() { diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 85b7ed162d2e..843fb07eadcd 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -26,9 +26,7 @@ use reth_payload_primitives::{ }; use reth_primitives::EthereumHardfork; use reth_rpc_api::EngineApiServer; -use reth_rpc_types_compat::engine::payload::{ - convert_payload_input_v2_to_payload, convert_to_payload_body_v1, -}; +use reth_rpc_types_compat::engine::payload::convert_to_payload_body_v1; use reth_storage_api::{BlockReader, HeaderProvider, StateProviderFactory}; use reth_tasks::TaskSpawner; use reth_transaction_pool::TransactionPool; @@ -176,7 +174,7 @@ where &self, payload: ExecutionPayloadInputV2, ) -> EngineApiResult { - let payload = convert_payload_input_v2_to_payload(payload); + let payload = payload.into_payload(); let payload_or_attrs = PayloadOrAttributes::<'_, EngineT::PayloadAttributes>::from_execution_payload( &payload, None, diff --git a/crates/rpc/rpc-engine-api/tests/it/payload.rs b/crates/rpc/rpc-engine-api/tests/it/payload.rs index 61199773e0ed..fbc528417ca5 100644 --- a/crates/rpc/rpc-engine-api/tests/it/payload.rs +++ b/crates/rpc/rpc-engine-api/tests/it/payload.rs @@ -1,7 +1,7 @@ //! Some payload tests use alloy_eips::eip4895::Withdrawals; -use alloy_primitives::{Bytes, U256}; +use alloy_primitives::Bytes; use alloy_rlp::{Decodable, Error as RlpError}; use alloy_rpc_types_engine::{ ExecutionPayload, ExecutionPayloadBodyV1, ExecutionPayloadSidecar, ExecutionPayloadV1, @@ -10,11 +10,10 @@ use alloy_rpc_types_engine::{ use assert_matches::assert_matches; use reth_primitives::{proofs, Block, SealedBlock, SealedHeader, TransactionSigned}; use reth_rpc_types_compat::engine::payload::{ - block_to_payload, block_to_payload_v1, convert_to_payload_body_v1, try_into_sealed_block, - try_payload_v1_to_block, + block_to_payload, block_to_payload_v1, convert_to_payload_body_v1, }; use reth_testing_utils::generators::{ - self, random_block, random_block_range, random_header, BlockParams, BlockRangeParams, Rng, + self, random_block, random_block_range, BlockParams, BlockRangeParams, Rng, }; fn transform_block Block>(src: SealedBlock, f: F) -> ExecutionPayload { @@ -56,7 +55,7 @@ fn payload_body_roundtrip() { } #[test] -fn payload_validation() { +fn payload_validation_conversion() { let mut rng = generators::rng(); let parent = rng.gen(); let block = random_block( @@ -77,7 +76,8 @@ fn payload_validation() { }); assert_matches!( - try_into_sealed_block(block_with_valid_extra_data, &ExecutionPayloadSidecar::none()), + block_with_valid_extra_data + .try_into_block_with_sidecar::(&ExecutionPayloadSidecar::none()), Ok(_) ); @@ -88,7 +88,7 @@ fn payload_validation() { b }); assert_matches!( - try_into_sealed_block(invalid_extra_data_block, &ExecutionPayloadSidecar::none()), + invalid_extra_data_block.try_into_block_with_sidecar::(&ExecutionPayloadSidecar::none()), Err(PayloadError::ExtraData(data)) if data == block_with_invalid_extra_data ); @@ -98,52 +98,16 @@ fn payload_validation() { b }); assert_matches!( - try_into_sealed_block(block_with_zero_base_fee, &ExecutionPayloadSidecar::none()), + block_with_zero_base_fee.try_into_block_with_sidecar::(&ExecutionPayloadSidecar::none()), Err(PayloadError::BaseFee(val)) if val.is_zero() ); // Invalid encoded transactions - let mut payload_with_invalid_txs: ExecutionPayloadV1 = block_to_payload_v1(block.clone()); + let mut payload_with_invalid_txs: ExecutionPayloadV1 = block_to_payload_v1(block); payload_with_invalid_txs.transactions.iter_mut().for_each(|tx| { *tx = Bytes::new(); }); - let payload_with_invalid_txs = - try_payload_v1_to_block::(payload_with_invalid_txs); + let payload_with_invalid_txs = payload_with_invalid_txs.try_into_block::(); assert_matches!(payload_with_invalid_txs, Err(PayloadError::Decode(RlpError::InputTooShort))); - - // Non empty ommers - let block_with_ommers = transform_block(block.clone(), |mut b| { - b.body.ommers.push(random_header(&mut rng, 100, None).unseal()); - b - }); - assert_matches!( - try_into_sealed_block(block_with_ommers.clone(), &ExecutionPayloadSidecar::none()), - Err(PayloadError::BlockHash { consensus, .. }) - if consensus == block_with_ommers.block_hash() - ); - - // None zero difficulty - let block_with_difficulty = transform_block(block.clone(), |mut b| { - b.header.difficulty = U256::from(1); - b - }); - assert_matches!( - try_into_sealed_block(block_with_difficulty.clone(), &ExecutionPayloadSidecar::none()), - Err(PayloadError::BlockHash { consensus, .. }) if consensus == block_with_difficulty.block_hash() - ); - - // None zero nonce - let block_with_nonce = transform_block(block.clone(), |mut b| { - b.header.nonce = 1u64.into(); - b - }); - assert_matches!( - try_into_sealed_block(block_with_nonce.clone(), &ExecutionPayloadSidecar::none()), - Err(PayloadError::BlockHash { consensus, .. }) if consensus == block_with_nonce.block_hash() - ); - - // Valid block - let valid_block = block; - assert_matches!(TryInto::::try_into(valid_block), Ok(_)); } diff --git a/crates/rpc/rpc-types-compat/Cargo.toml b/crates/rpc/rpc-types-compat/Cargo.toml index 8a6ee44e7cf7..f2d5ce2e2a30 100644 --- a/crates/rpc/rpc-types-compat/Cargo.toml +++ b/crates/rpc/rpc-types-compat/Cargo.toml @@ -19,7 +19,6 @@ reth-primitives-traits.workspace = true # ethereum alloy-eips.workspace = true alloy-primitives.workspace = true -alloy-rlp.workspace = true alloy-rpc-types-eth = { workspace = true, default-features = false, features = ["serde"] } alloy-rpc-types-engine.workspace = true alloy-consensus.workspace = true diff --git a/crates/rpc/rpc-types-compat/src/engine/mod.rs b/crates/rpc/rpc-types-compat/src/engine/mod.rs index aa7456250262..a97d880fe8c2 100644 --- a/crates/rpc/rpc-types-compat/src/engine/mod.rs +++ b/crates/rpc/rpc-types-compat/src/engine/mod.rs @@ -1,3 +1,3 @@ //! Standalone functions for engine specific rpc type conversions pub mod payload; -pub use payload::{block_to_payload_v1, try_into_sealed_block, try_payload_v1_to_block}; +pub use payload::block_to_payload_v1; diff --git a/crates/rpc/rpc-types-compat/src/engine/payload.rs b/crates/rpc/rpc-types-compat/src/engine/payload.rs index 0ed30de6a32c..6e2e18b19e09 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -1,123 +1,17 @@ //! Standalone Conversion Functions for Handling Different Versions of Execution Payloads in //! Ethereum's Engine -use alloy_consensus::{constants::MAXIMUM_EXTRA_DATA_SIZE, Header, EMPTY_OMMER_ROOT_HASH}; -use alloy_eips::{ - eip2718::{Decodable2718, Encodable2718}, - eip4895::Withdrawals, - eip7685::RequestsOrHash, -}; -use alloy_primitives::{B256, U256}; -use alloy_rlp::BufMut; +use alloy_consensus::Header; +use alloy_eips::{eip2718::Encodable2718, eip4895::Withdrawals, eip7685::RequestsOrHash}; +use alloy_primitives::U256; use alloy_rpc_types_engine::{ payload::{ExecutionPayloadBodyV1, ExecutionPayloadFieldV2, ExecutionPayloadInputV2}, CancunPayloadFields, ExecutionPayload, ExecutionPayloadSidecar, ExecutionPayloadV1, - ExecutionPayloadV2, ExecutionPayloadV3, PayloadError, PraguePayloadFields, -}; -use reth_primitives::{ - proofs::{self}, - Block, BlockBody, BlockExt, SealedBlock, + ExecutionPayloadV2, ExecutionPayloadV3, PraguePayloadFields, }; +use reth_primitives::{BlockBody, SealedBlock}; use reth_primitives_traits::{BlockBody as _, SignedTransaction}; -/// Converts [`ExecutionPayloadV1`] to [`Block`] -pub fn try_payload_v1_to_block( - payload: ExecutionPayloadV1, -) -> Result, PayloadError> { - if payload.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE { - return Err(PayloadError::ExtraData(payload.extra_data)) - } - - if payload.base_fee_per_gas.is_zero() { - return Err(PayloadError::BaseFee(payload.base_fee_per_gas)) - } - - let transactions = payload - .transactions - .iter() - .map(|tx| { - let mut buf = tx.as_ref(); - - let tx = T::decode_2718(&mut buf).map_err(alloy_rlp::Error::from)?; - - if !buf.is_empty() { - return Err(alloy_rlp::Error::UnexpectedLength); - } - - Ok(tx) - }) - .collect::, _>>()?; - - // Reuse the encoded bytes for root calculation - let transactions_root = - proofs::ordered_trie_root_with_encoder(&payload.transactions, |item, buf| { - buf.put_slice(item) - }); - - let header = Header { - parent_hash: payload.parent_hash, - beneficiary: payload.fee_recipient, - state_root: payload.state_root, - transactions_root, - receipts_root: payload.receipts_root, - withdrawals_root: None, - logs_bloom: payload.logs_bloom, - number: payload.block_number, - gas_limit: payload.gas_limit, - gas_used: payload.gas_used, - timestamp: payload.timestamp, - mix_hash: payload.prev_randao, - // WARNING: It’s allowed for a base fee in EIP1559 to increase unbounded. We assume that - // it will fit in an u64. This is not always necessarily true, although it is extremely - // unlikely not to be the case, a u64 maximum would have 2^64 which equates to 18 ETH per - // gas. - base_fee_per_gas: Some( - payload - .base_fee_per_gas - .try_into() - .map_err(|_| PayloadError::BaseFee(payload.base_fee_per_gas))?, - ), - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, - requests_hash: None, - extra_data: payload.extra_data, - // Defaults - ommers_hash: EMPTY_OMMER_ROOT_HASH, - difficulty: Default::default(), - nonce: Default::default(), - }; - - Ok(Block { header, body: BlockBody { transactions, ..Default::default() } }) -} - -/// Converts [`ExecutionPayloadV2`] to [`Block`] -pub fn try_payload_v2_to_block( - payload: ExecutionPayloadV2, -) -> Result, PayloadError> { - // this performs the same conversion as the underlying V1 payload, but calculates the - // withdrawals root and adds withdrawals - let mut base_sealed_block = try_payload_v1_to_block(payload.payload_inner)?; - let withdrawals_root = proofs::calculate_withdrawals_root(&payload.withdrawals); - base_sealed_block.body.withdrawals = Some(payload.withdrawals.into()); - base_sealed_block.header.withdrawals_root = Some(withdrawals_root); - Ok(base_sealed_block) -} - -/// Converts [`ExecutionPayloadV3`] to [`Block`] -pub fn try_payload_v3_to_block( - payload: ExecutionPayloadV3, -) -> Result, PayloadError> { - // this performs the same conversion as the underlying V2 payload, but inserts the blob gas - // used and excess blob gas - let mut base_block = try_payload_v2_to_block(payload.payload_inner)?; - - base_block.header.blob_gas_used = Some(payload.blob_gas_used); - base_block.header.excess_blob_gas = Some(payload.excess_blob_gas); - - Ok(base_block) -} - /// Converts [`SealedBlock`] to [`ExecutionPayload`] pub fn block_to_payload( value: SealedBlock>, @@ -212,42 +106,6 @@ pub fn convert_block_to_payload_field_v2( } } -/// Converts [`ExecutionPayloadFieldV2`] to [`ExecutionPayload`] -pub fn convert_payload_field_v2_to_payload(value: ExecutionPayloadFieldV2) -> ExecutionPayload { - match value { - ExecutionPayloadFieldV2::V1(payload) => ExecutionPayload::V1(payload), - ExecutionPayloadFieldV2::V2(payload) => ExecutionPayload::V2(payload), - } -} - -/// Converts [`ExecutionPayloadV2`] to [`ExecutionPayloadInputV2`]. -/// -/// An [`ExecutionPayloadInputV2`] should have a [`Some`] withdrawals field if shanghai is active, -/// otherwise the withdrawals field should be [`None`], so the `is_shanghai_active` argument is -/// provided which will either: -/// - include the withdrawals field as [`Some`] if true -/// - set the withdrawals field to [`None`] if false -pub fn convert_payload_v2_to_payload_input_v2( - value: ExecutionPayloadV2, - is_shanghai_active: bool, -) -> ExecutionPayloadInputV2 { - ExecutionPayloadInputV2 { - execution_payload: value.payload_inner, - withdrawals: is_shanghai_active.then_some(value.withdrawals), - } -} - -/// Converts [`ExecutionPayloadInputV2`] to [`ExecutionPayload`] -pub fn convert_payload_input_v2_to_payload(value: ExecutionPayloadInputV2) -> ExecutionPayload { - match value.withdrawals { - Some(withdrawals) => ExecutionPayload::V2(ExecutionPayloadV2 { - payload_inner: value.execution_payload, - withdrawals, - }), - None => ExecutionPayload::V1(value.execution_payload), - } -} - /// Converts [`SealedBlock`] to [`ExecutionPayloadInputV2`] pub fn convert_block_to_payload_input_v2(value: SealedBlock) -> ExecutionPayloadInputV2 { ExecutionPayloadInputV2 { @@ -256,76 +114,7 @@ pub fn convert_block_to_payload_input_v2(value: SealedBlock) -> ExecutionPayload } } -/// Tries to create a new unsealed block from the given payload and payload sidecar. -/// -/// Performs additional validation of `extra_data` and `base_fee_per_gas` fields. -/// -/// # Note -/// -/// The log bloom is assumed to be validated during serialization. -/// -/// See -pub fn try_into_block( - value: ExecutionPayload, - sidecar: &ExecutionPayloadSidecar, -) -> Result, PayloadError> { - let mut base_payload = match value { - ExecutionPayload::V1(payload) => try_payload_v1_to_block(payload)?, - ExecutionPayload::V2(payload) => try_payload_v2_to_block(payload)?, - ExecutionPayload::V3(payload) => try_payload_v3_to_block(payload)?, - }; - - base_payload.header.parent_beacon_block_root = sidecar.parent_beacon_block_root(); - base_payload.header.requests_hash = sidecar.requests_hash(); - - Ok(base_payload) -} - -/// Tries to create a sealed new block from the given payload and payload sidecar. -/// -/// Uses [`try_into_block`] to convert from the [`ExecutionPayload`] to [`Block`] and seals the -/// block with its hash. -/// -/// Uses [`validate_block_hash`] to validate the payload block hash and ultimately return the -/// [`SealedBlock`]. -/// -/// # Note -/// -/// Empty ommers, nonce, difficulty, and execution request values are validated upon computing block -/// hash and comparing the value with `payload.block_hash`. -pub fn try_into_sealed_block( - payload: ExecutionPayload, - sidecar: &ExecutionPayloadSidecar, -) -> Result { - let block_hash = payload.block_hash(); - let base_payload = try_into_block(payload, sidecar)?; - - // validate block hash and return - validate_block_hash(block_hash, base_payload) -} - -/// Takes the expected block hash and [`Block`], validating the block and converting it into a -/// [`SealedBlock`]. -/// -/// If the provided block hash does not match the block hash computed from the provided block, this -/// returns [`PayloadError::BlockHash`]. -#[inline] -pub fn validate_block_hash( - expected_block_hash: B256, - block: Block, -) -> Result { - let sealed_block = block.seal_slow(); - if expected_block_hash != sealed_block.hash() { - return Err(PayloadError::BlockHash { - execution: sealed_block.hash(), - consensus: expected_block_hash, - }) - } - - Ok(sealed_block) -} - -/// Converts [`Block`] to [`ExecutionPayloadBodyV1`] +/// Converts a [`reth_primitives_traits::Block`] to [`ExecutionPayloadBodyV1`] pub fn convert_to_payload_body_v1( value: impl reth_primitives_traits::Block, ) -> ExecutionPayloadBodyV1 { @@ -359,9 +148,7 @@ pub fn execution_payload_from_sealed_block(value: SealedBlock) -> ExecutionPaylo #[cfg(test)] mod tests { - use super::{ - block_to_payload_v3, try_into_block, try_payload_v3_to_block, validate_block_hash, - }; + use super::block_to_payload_v3; use alloy_primitives::{b256, hex, Bytes, U256}; use alloy_rpc_types_engine::{ CancunPayloadFields, ExecutionPayload, ExecutionPayloadSidecar, ExecutionPayloadV1, @@ -398,7 +185,7 @@ mod tests { excess_blob_gas: 0x580000, }; - let mut block: Block = try_payload_v3_to_block(new_payload.clone()).unwrap(); + let mut block: Block = new_payload.clone().try_into_block().unwrap(); // this newPayload came with a parent beacon block root, we need to manually insert it // before hashing @@ -441,7 +228,8 @@ mod tests { excess_blob_gas: 0x580000, }; - let _block = try_payload_v3_to_block::(new_payload) + let _block = new_payload + .try_into_block::() .expect_err("execution payload conversion requires typed txs without a rlp header"); } @@ -584,9 +372,13 @@ mod tests { let cancun_fields = CancunPayloadFields { parent_beacon_block_root, versioned_hashes }; // convert into block - let block = try_into_block(payload, &ExecutionPayloadSidecar::v3(cancun_fields)).unwrap(); + let block = payload + .try_into_block_with_sidecar::(&ExecutionPayloadSidecar::v3( + cancun_fields, + )) + .unwrap(); // Ensure the actual hash is calculated if we set the fields to what they should be - validate_block_hash(block_hash_with_blob_fee_fields, block).unwrap(); + assert_eq!(block_hash_with_blob_fee_fields, block.header.hash_slow()); } }