From 112e21ed6415121c7cab611ecf61242013120dcb Mon Sep 17 00:00:00 2001 From: john xu Date: Mon, 8 Jul 2024 12:18:26 +0800 Subject: [PATCH] Relocate taiko payload codes --- crates/payload/validator/Cargo.toml | 1 - crates/payload/validator/src/lib.rs | 60 +---- crates/taiko/payload/{ => builder}/Cargo.toml | 0 .../payload/{ => builder}/src/builder.rs | 2 +- .../taiko/payload/{ => builder}/src/consts.rs | 0 .../taiko/payload/{ => builder}/src/error.rs | 0 crates/taiko/payload/{ => builder}/src/lib.rs | 0 crates/taiko/payload/validator/Cargo.toml | 20 ++ crates/taiko/payload/validator/src/lib.rs | 215 ++++++++++++++++++ 9 files changed, 237 insertions(+), 61 deletions(-) rename crates/taiko/payload/{ => builder}/Cargo.toml (100%) rename crates/taiko/payload/{ => builder}/src/builder.rs (99%) rename crates/taiko/payload/{ => builder}/src/consts.rs (100%) rename crates/taiko/payload/{ => builder}/src/error.rs (100%) rename crates/taiko/payload/{ => builder}/src/lib.rs (100%) create mode 100644 crates/taiko/payload/validator/Cargo.toml create mode 100644 crates/taiko/payload/validator/src/lib.rs diff --git a/crates/payload/validator/Cargo.toml b/crates/payload/validator/Cargo.toml index ce86734daf08..66efe865ceba 100644 --- a/crates/payload/validator/Cargo.toml +++ b/crates/payload/validator/Cargo.toml @@ -17,4 +17,3 @@ reth-chainspec.workspace = true reth-primitives.workspace = true reth-rpc-types.workspace = true reth-rpc-types-compat.workspace = true -reth-payload-builder.workspace = true diff --git a/crates/payload/validator/src/lib.rs b/crates/payload/validator/src/lib.rs index 4ff5e60deef4..5b56df025c11 100644 --- a/crates/payload/validator/src/lib.rs +++ b/crates/payload/validator/src/lib.rs @@ -9,11 +9,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use reth_chainspec::ChainSpec; -#[cfg(feature = "taiko")] -use reth_payload_builder::TaikoExecutionPayload; use reth_primitives::SealedBlock; -#[cfg(feature = "taiko")] -use reth_primitives::{Block, Header, B256, EMPTY_OMMER_ROOT_HASH}; use reth_rpc_types::{engine::MaybeCancunPayloadFields, ExecutionPayload, PayloadError}; use reth_rpc_types_compat::engine::payload::try_into_block; use std::sync::Arc; @@ -108,27 +104,14 @@ impl ExecutionPayloadValidator { /// pub fn ensure_well_formed_payload( &self, - #[cfg(not(feature = "taiko"))] payload: ExecutionPayload, - #[cfg(feature = "taiko")] payload: TaikoExecutionPayload, + payload: ExecutionPayload, cancun_fields: MaybeCancunPayloadFields, ) -> Result { let expected_hash = payload.block_hash(); // First parse the block - #[cfg(not(feature = "taiko"))] let sealed_block = try_into_block(payload, cancun_fields.parent_beacon_block_root())?.seal_slow(); - #[cfg(feature = "taiko")] - let sealed_block = if payload.payload_inner.as_v1().transactions.is_empty() && - (payload.payload_inner.withdrawals().is_none() || - payload.payload_inner.withdrawals().is_some_and(|w| w.is_empty())) - { - create_taiko_block(payload, cancun_fields.parent_beacon_block_root())?.seal_slow() - } else { - try_into_block(payload.payload_inner, cancun_fields.parent_beacon_block_root())? - .seal_slow() - }; - // Ensure the hash included in the payload matches the block hash if expected_hash != sealed_block.hash() { return Err(PayloadError::BlockHash { @@ -181,44 +164,3 @@ impl ExecutionPayloadValidator { Ok(sealed_block) } } - -#[cfg(feature = "taiko")] -fn create_taiko_block( - payload: TaikoExecutionPayload, - parent_beacon_block_root: Option, -) -> Result { - Ok(Block { - header: Header { - parent_hash: payload.payload_inner.parent_hash(), - beneficiary: payload.payload_inner.as_v1().fee_recipient, - state_root: payload.payload_inner.as_v1().state_root, - transactions_root: payload.tx_hash, - receipts_root: payload.payload_inner.as_v1().receipts_root, - withdrawals_root: Some(payload.withdrawals_hash), - logs_bloom: payload.payload_inner.as_v1().logs_bloom, - number: payload.payload_inner.block_number(), - gas_limit: payload.payload_inner.as_v1().gas_limit, - gas_used: payload.payload_inner.as_v1().gas_used, - timestamp: payload.payload_inner.timestamp(), - mix_hash: payload.payload_inner.prev_randao(), - base_fee_per_gas: Some( - payload.payload_inner.as_v1().base_fee_per_gas.try_into().map_err(|_| { - PayloadError::BaseFee(payload.payload_inner.as_v1().base_fee_per_gas) - })?, - ), - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root, - extra_data: payload.payload_inner.as_v1().extra_data.clone(), - // Defaults - ommers_hash: EMPTY_OMMER_ROOT_HASH, - difficulty: Default::default(), - nonce: Default::default(), - requests_root: None, - }, - body: vec![], - withdrawals: None, - ommers: Default::default(), - requests: None, - }) -} diff --git a/crates/taiko/payload/Cargo.toml b/crates/taiko/payload/builder/Cargo.toml similarity index 100% rename from crates/taiko/payload/Cargo.toml rename to crates/taiko/payload/builder/Cargo.toml diff --git a/crates/taiko/payload/src/builder.rs b/crates/taiko/payload/builder/src/builder.rs similarity index 99% rename from crates/taiko/payload/src/builder.rs rename to crates/taiko/payload/builder/src/builder.rs index 739c5183181b..7d7b4ff9ff9f 100644 --- a/crates/taiko/payload/src/builder.rs +++ b/crates/taiko/payload/builder/src/builder.rs @@ -43,7 +43,7 @@ impl TaikoPayloadBuilder { } } -/// Implementation of the [PayloadBuilder] trait for [TaikoPayloadBuilder]. +/// Implementation of the [`PayloadBuilder`] trait for [`TaikoPayloadBuilder`]. impl PayloadBuilder for TaikoPayloadBuilder where Client: StateProviderFactory, diff --git a/crates/taiko/payload/src/consts.rs b/crates/taiko/payload/builder/src/consts.rs similarity index 100% rename from crates/taiko/payload/src/consts.rs rename to crates/taiko/payload/builder/src/consts.rs diff --git a/crates/taiko/payload/src/error.rs b/crates/taiko/payload/builder/src/error.rs similarity index 100% rename from crates/taiko/payload/src/error.rs rename to crates/taiko/payload/builder/src/error.rs diff --git a/crates/taiko/payload/src/lib.rs b/crates/taiko/payload/builder/src/lib.rs similarity index 100% rename from crates/taiko/payload/src/lib.rs rename to crates/taiko/payload/builder/src/lib.rs diff --git a/crates/taiko/payload/validator/Cargo.toml b/crates/taiko/payload/validator/Cargo.toml new file mode 100644 index 000000000000..ffe69f8a96e8 --- /dev/null +++ b/crates/taiko/payload/validator/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "taiko-reth-payload-validator" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Payload validation support" + +[lints] +workspace = true + +[dependencies] +# reth +reth-chainspec.workspace = true +reth-primitives = { workspace = true, features = ["taiko"] } +reth-rpc-types.workspace = true +reth-rpc-types-compat.workspace = true +taiko-reth-engine-primitives.workspace = true diff --git a/crates/taiko/payload/validator/src/lib.rs b/crates/taiko/payload/validator/src/lib.rs new file mode 100644 index 000000000000..f67405234336 --- /dev/null +++ b/crates/taiko/payload/validator/src/lib.rs @@ -0,0 +1,215 @@ +//! Payload Validation support. + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use reth_chainspec::ChainSpec; +use reth_primitives::{Block, Header, SealedBlock, B256, EMPTY_OMMER_ROOT_HASH}; +use reth_rpc_types::{engine::MaybeCancunPayloadFields, PayloadError}; +use reth_rpc_types_compat::engine::payload::try_into_block; +use std::sync::Arc; +use taiko_reth_engine_primitives::TaikoExecutionPayload; + +/// Execution payload validator.; +#[derive(Clone, Debug)] +pub struct TaikoExecutionPayloadValidator { + /// Chain spec to validate against. + chain_spec: Arc, +} + +impl TaikoExecutionPayloadValidator { + /// Create a new validator. + pub const fn new(chain_spec: Arc) -> Self { + Self { chain_spec } + } + + /// Returns the chain spec used by the validator. + #[inline] + pub fn chain_spec(&self) -> &ChainSpec { + &self.chain_spec + } + + /// Returns true if the Cancun hardfork is active at the given timestamp. + #[inline] + fn is_cancun_active_at_timestamp(&self, timestamp: u64) -> bool { + self.chain_spec().is_cancun_active_at_timestamp(timestamp) + } + + /// Returns true if the Shanghai hardfork is active at the given timestamp. + #[inline] + fn is_shanghai_active_at_timestamp(&self, timestamp: u64) -> bool { + self.chain_spec().is_shanghai_active_at_timestamp(timestamp) + } + + /// Cancun specific checks for EIP-4844 blob transactions. + /// + /// Ensures that the number of blob versioned hashes matches the number hashes included in the + /// _separate_ `block_versioned_hashes` of the cancun payload fields. + fn ensure_matching_blob_versioned_hashes( + &self, + sealed_block: &SealedBlock, + cancun_fields: &MaybeCancunPayloadFields, + ) -> Result<(), PayloadError> { + let num_blob_versioned_hashes = sealed_block.blob_versioned_hashes_iter().count(); + // Additional Cancun checks for blob transactions + if let Some(versioned_hashes) = cancun_fields.versioned_hashes() { + if num_blob_versioned_hashes != versioned_hashes.len() { + // Number of blob versioned hashes does not match + return Err(PayloadError::InvalidVersionedHashes); + } + // we can use `zip` safely here because we already compared their length + for (payload_versioned_hash, block_versioned_hash) in + versioned_hashes.iter().zip(sealed_block.blob_versioned_hashes_iter()) + { + if payload_versioned_hash != block_versioned_hash { + return Err(PayloadError::InvalidVersionedHashes); + } + } + } else { + // No Cancun fields, if block includes any blobs, this is an error + if num_blob_versioned_hashes > 0 { + return Err(PayloadError::InvalidVersionedHashes); + } + } + + Ok(()) + } + + /// Ensures that the given payload does not violate any consensus rules that concern the block's + /// layout, like: + /// - missing or invalid base fee + /// - invalid extra data + /// - invalid transactions + /// - incorrect hash + /// - the versioned hashes passed with the payload do not exactly match transaction versioned + /// hashes + /// - the block does not contain blob transactions if it is pre-cancun + /// + /// The checks are done in the order that conforms with the engine-API specification. + /// + /// This is intended to be invoked after receiving the payload from the CLI. + /// The additional [`MaybeCancunPayloadFields`] are not part of the payload, but are additional fields in the `engine_newPayloadV3` RPC call, See also + /// + /// If the cancun fields are provided this also validates that the versioned hashes in the block + /// match the versioned hashes passed in the + /// [`CancunPayloadFields`](reth_rpc_types::engine::CancunPayloadFields), if the cancun payload + /// fields are provided. If the payload fields are not provided, but versioned hashes exist + /// in the block, this is considered an error: [`PayloadError::InvalidVersionedHashes`]. + /// + /// This validates versioned hashes according to the Engine API Cancun spec: + /// + pub fn ensure_well_formed_payload( + &self, + payload: TaikoExecutionPayload, + cancun_fields: MaybeCancunPayloadFields, + ) -> Result { + let expected_hash = payload.block_hash(); + + // First parse the block + let sealed_block = if payload.payload_inner.as_v1().transactions.is_empty() && + (payload.payload_inner.withdrawals().is_none() || + payload.payload_inner.withdrawals().is_some_and(|w| w.is_empty())) + { + create_taiko_block(payload, cancun_fields.parent_beacon_block_root())?.seal_slow() + } else { + try_into_block(payload.payload_inner, cancun_fields.parent_beacon_block_root())? + .seal_slow() + }; + + // Ensure the hash included in the payload matches the block hash + if expected_hash != sealed_block.hash() { + return Err(PayloadError::BlockHash { + execution: sealed_block.hash(), + consensus: expected_hash, + }); + } + + if self.is_cancun_active_at_timestamp(sealed_block.timestamp) { + if sealed_block.header.blob_gas_used.is_none() { + // cancun active but blob gas used not present + return Err(PayloadError::PostCancunBlockWithoutBlobGasUsed) + } + if sealed_block.header.excess_blob_gas.is_none() { + // cancun active but excess blob gas not present + return Err(PayloadError::PostCancunBlockWithoutExcessBlobGas) + } + if cancun_fields.as_ref().is_none() { + // cancun active but cancun fields not present + return Err(PayloadError::PostCancunWithoutCancunFields) + } + } else { + if sealed_block.has_blob_transactions() { + // cancun not active but blob transactions present + return Err(PayloadError::PreCancunBlockWithBlobTransactions) + } + if sealed_block.header.blob_gas_used.is_some() { + // cancun not active but blob gas used present + return Err(PayloadError::PreCancunBlockWithBlobGasUsed) + } + if sealed_block.header.excess_blob_gas.is_some() { + // cancun not active but excess blob gas present + return Err(PayloadError::PreCancunBlockWithExcessBlobGas) + } + if cancun_fields.as_ref().is_some() { + // cancun not active but cancun fields present + return Err(PayloadError::PreCancunWithCancunFields) + } + } + + let shanghai_active = self.is_shanghai_active_at_timestamp(sealed_block.timestamp); + if !shanghai_active && sealed_block.withdrawals.is_some() { + // shanghai not active but withdrawals present + return Err(PayloadError::PreShanghaiBlockWithWitdrawals) + } + + // EIP-4844 checks + self.ensure_matching_blob_versioned_hashes(&sealed_block, &cancun_fields)?; + + Ok(sealed_block) + } +} + +fn create_taiko_block( + payload: TaikoExecutionPayload, + parent_beacon_block_root: Option, +) -> Result { + Ok(Block { + header: Header { + parent_hash: payload.payload_inner.parent_hash(), + beneficiary: payload.payload_inner.as_v1().fee_recipient, + state_root: payload.payload_inner.as_v1().state_root, + transactions_root: payload.tx_hash, + receipts_root: payload.payload_inner.as_v1().receipts_root, + withdrawals_root: Some(payload.withdrawals_hash), + logs_bloom: payload.payload_inner.as_v1().logs_bloom, + number: payload.payload_inner.block_number(), + gas_limit: payload.payload_inner.as_v1().gas_limit, + gas_used: payload.payload_inner.as_v1().gas_used, + timestamp: payload.payload_inner.timestamp(), + mix_hash: payload.payload_inner.prev_randao(), + base_fee_per_gas: Some( + payload.payload_inner.as_v1().base_fee_per_gas.try_into().map_err(|_| { + PayloadError::BaseFee(payload.payload_inner.as_v1().base_fee_per_gas) + })?, + ), + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root, + extra_data: payload.payload_inner.as_v1().extra_data.clone(), + // Defaults + ommers_hash: EMPTY_OMMER_ROOT_HASH, + difficulty: Default::default(), + nonce: Default::default(), + requests_root: None, + }, + body: vec![], + withdrawals: None, + ommers: Default::default(), + requests: None, + }) +}