mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
chore: use native conversions for payload to block (#13608)
This commit is contained in:
@ -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<PayloadStatus> {
|
||||
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,
|
||||
|
||||
@ -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<F: FnOnce(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::<TransactionSigned>(&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::<TransactionSigned>(&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::<TransactionSigned>(&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::<TransactionSigned>(payload_with_invalid_txs);
|
||||
let payload_with_invalid_txs = payload_with_invalid_txs.try_into_block::<TransactionSigned>();
|
||||
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::<SealedBlock>::try_into(valid_block), Ok(_));
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<T: Decodable2718>(
|
||||
payload: ExecutionPayloadV1,
|
||||
) -> Result<Block<T>, 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::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// 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<T: Decodable2718>(
|
||||
payload: ExecutionPayloadV2,
|
||||
) -> Result<Block<T>, 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<T: Decodable2718>(
|
||||
payload: ExecutionPayloadV3,
|
||||
) -> Result<Block<T>, 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<T: SignedTransaction>(
|
||||
value: SealedBlock<Header, BlockBody<T>>,
|
||||
@ -212,42 +106,6 @@ pub fn convert_block_to_payload_field_v2<T: Encodable2718>(
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
|
||||
pub fn try_into_block<T: Decodable2718>(
|
||||
value: ExecutionPayload,
|
||||
sidecar: &ExecutionPayloadSidecar,
|
||||
) -> Result<Block<T>, 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<SealedBlock, PayloadError> {
|
||||
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<SealedBlock, PayloadError> {
|
||||
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::<TransactionSigned>(new_payload)
|
||||
let _block = new_payload
|
||||
.try_into_block::<TransactionSigned>()
|
||||
.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::<TransactionSigned>(&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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user