Skip to content

Commit

Permalink
Relocate taiko payload codes
Browse files Browse the repository at this point in the history
  • Loading branch information
johntaiko committed Jul 8, 2024
1 parent 775ef32 commit 112e21e
Show file tree
Hide file tree
Showing 9 changed files with 237 additions and 61 deletions.
1 change: 0 additions & 1 deletion crates/payload/validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
60 changes: 1 addition & 59 deletions crates/payload/validator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -108,27 +104,14 @@ impl ExecutionPayloadValidator {
/// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification>
pub fn ensure_well_formed_payload(
&self,
#[cfg(not(feature = "taiko"))] payload: ExecutionPayload,
#[cfg(feature = "taiko")] payload: TaikoExecutionPayload,
payload: ExecutionPayload,
cancun_fields: MaybeCancunPayloadFields,
) -> Result<SealedBlock, PayloadError> {
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 {
Expand Down Expand Up @@ -181,44 +164,3 @@ impl ExecutionPayloadValidator {
Ok(sealed_block)
}
}

#[cfg(feature = "taiko")]
fn create_taiko_block(
payload: TaikoExecutionPayload,
parent_beacon_block_root: Option<B256>,
) -> Result<Block, PayloadError> {
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,
})
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl<EvmConfig> TaikoPayloadBuilder<EvmConfig> {
}
}

/// Implementation of the [PayloadBuilder] trait for [TaikoPayloadBuilder].
/// Implementation of the [`PayloadBuilder`] trait for [`TaikoPayloadBuilder`].
impl<Pool, Client, EvmConfig> PayloadBuilder<Pool, Client> for TaikoPayloadBuilder<EvmConfig>
where
Client: StateProviderFactory,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
20 changes: 20 additions & 0 deletions crates/taiko/payload/validator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
215 changes: 215 additions & 0 deletions crates/taiko/payload/validator/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<ChainSpec>,
}

impl TaikoExecutionPayloadValidator {
/// Create a new validator.
pub const fn new(chain_spec: Arc<ChainSpec>) -> 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 <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#engine_newpayloadv3>
///
/// 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:
/// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification>
pub fn ensure_well_formed_payload(
&self,
payload: TaikoExecutionPayload,
cancun_fields: MaybeCancunPayloadFields,
) -> Result<SealedBlock, PayloadError> {
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<B256>,
) -> Result<Block, PayloadError> {
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,
})
}

0 comments on commit 112e21e

Please sign in to comment.