feat: Duplicate Withdrawal and move try from impls to rpc-compat (#4186)

This commit is contained in:
Supernovahs.eth
2023-09-19 22:27:32 +05:30
committed by GitHub
parent 57c10e5b65
commit 801294252e
20 changed files with 480 additions and 317 deletions

4
Cargo.lock generated
View File

@ -5339,6 +5339,7 @@ dependencies = [
"reth-prune", "reth-prune",
"reth-revm", "reth-revm",
"reth-rpc-types", "reth-rpc-types",
"reth-rpc-types-compat",
"reth-stages", "reth-stages",
"reth-tasks", "reth-tasks",
"reth-tracing", "reth-tracing",
@ -5795,6 +5796,7 @@ dependencies = [
"reth-revm-primitives", "reth-revm-primitives",
"reth-rlp", "reth-rlp",
"reth-rpc-types", "reth-rpc-types",
"reth-rpc-types-compat",
"reth-transaction-pool", "reth-transaction-pool",
"revm-primitives", "revm-primitives",
"sha2", "sha2",
@ -6056,6 +6058,7 @@ dependencies = [
"reth-rpc-api", "reth-rpc-api",
"reth-rpc-engine-api", "reth-rpc-engine-api",
"reth-rpc-types", "reth-rpc-types",
"reth-rpc-types-compat",
"reth-tasks", "reth-tasks",
"reth-tracing", "reth-tracing",
"reth-transaction-pool", "reth-transaction-pool",
@ -6085,6 +6088,7 @@ dependencies = [
"reth-rlp", "reth-rlp",
"reth-rpc-api", "reth-rpc-api",
"reth-rpc-types", "reth-rpc-types",
"reth-rpc-types-compat",
"reth-tasks", "reth-tasks",
"thiserror", "thiserror",
"tokio", "tokio",

View File

@ -19,7 +19,7 @@ reth-rpc-types.workspace = true
reth-tasks.workspace = true reth-tasks.workspace = true
reth-payload-builder.workspace = true reth-payload-builder.workspace = true
reth-prune = { path = "../../prune" } reth-prune = { path = "../../prune" }
reth-rpc-types-compat.workspace = true
# async # async
tokio = { workspace = true, features = ["sync"] } tokio = { workspace = true, features = ["sync"] }
tokio-stream.workspace = true tokio-stream.workspace = true

View File

@ -33,6 +33,7 @@ use reth_rpc_types::engine::{
CancunPayloadFields, ExecutionPayload, PayloadAttributes, PayloadError, PayloadStatus, CancunPayloadFields, ExecutionPayload, PayloadAttributes, PayloadError, PayloadStatus,
PayloadStatusEnum, PayloadValidationError, PayloadStatusEnum, PayloadValidationError,
}; };
use reth_rpc_types_compat::engine::payload::try_into_sealed_block;
use reth_stages::{ControlFlow, Pipeline, PipelineError}; use reth_stages::{ControlFlow, Pipeline, PipelineError};
use reth_tasks::TaskSpawner; use reth_tasks::TaskSpawner;
use std::{ use std::{
@ -1137,7 +1138,8 @@ where
cancun_fields: Option<CancunPayloadFields>, cancun_fields: Option<CancunPayloadFields>,
) -> Result<SealedBlock, PayloadStatus> { ) -> Result<SealedBlock, PayloadStatus> {
let parent_hash = payload.parent_hash(); let parent_hash = payload.parent_hash();
let block = match payload.try_into_sealed_block( let block = match try_into_sealed_block(
payload,
cancun_fields.as_ref().map(|fields| fields.parent_beacon_block_root), cancun_fields.as_ref().map(|fields| fields.parent_beacon_block_root),
) { ) {
Ok(block) => block, Ok(block) => block,
@ -1833,9 +1835,8 @@ mod tests {
use assert_matches::assert_matches; use assert_matches::assert_matches;
use reth_primitives::{stage::StageCheckpoint, ChainSpec, ChainSpecBuilder, H256, MAINNET}; use reth_primitives::{stage::StageCheckpoint, ChainSpec, ChainSpecBuilder, H256, MAINNET};
use reth_provider::{BlockWriter, ProviderFactory}; use reth_provider::{BlockWriter, ProviderFactory};
use reth_rpc_types::engine::{ use reth_rpc_types::engine::{ForkchoiceState, ForkchoiceUpdated, PayloadStatus};
ExecutionPayloadV1, ForkchoiceState, ForkchoiceUpdated, PayloadStatus, use reth_rpc_types_compat::engine::payload::try_block_to_payload_v1;
};
use reth_stages::{ExecOutput, PipelineError, StageError}; use reth_stages::{ExecOutput, PipelineError, StageError};
use std::{collections::VecDeque, sync::Arc, time::Duration}; use std::{collections::VecDeque, sync::Arc, time::Duration};
use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::oneshot::error::TryRecvError;
@ -1895,7 +1896,8 @@ mod tests {
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty)); assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
// consensus engine is still idle because no FCUs were received // consensus engine is still idle because no FCUs were received
let _ = env.send_new_payload(ExecutionPayloadV1::from(SealedBlock::default()), None).await; let _ = env.send_new_payload(try_block_to_payload_v1(SealedBlock::default()), None).await;
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty)); assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
// consensus engine is still idle because pruning is running // consensus engine is still idle because pruning is running
@ -2017,7 +2019,6 @@ mod tests {
use reth_db::{tables, transaction::DbTxMut}; use reth_db::{tables, transaction::DbTxMut};
use reth_interfaces::test_utils::{generators, generators::random_block}; use reth_interfaces::test_utils::{generators, generators::random_block};
use reth_rpc_types::engine::ForkchoiceUpdateError; use reth_rpc_types::engine::ForkchoiceUpdateError;
#[tokio::test] #[tokio::test]
async fn empty_head() { async fn empty_head() {
let chain_spec = Arc::new( let chain_spec = Arc::new(
@ -2311,20 +2312,22 @@ mod tests {
// Send new payload // Send new payload
let res = env let res = env
.send_new_payload( .send_new_payload(
ExecutionPayloadV1::from(random_block(&mut rng, 0, None, None, Some(0))), try_block_to_payload_v1(random_block(&mut rng, 0, None, None, Some(0))),
None, None,
) )
.await; .await;
// Invalid, because this is a genesis block // Invalid, because this is a genesis block
assert_matches!(res, Ok(result) => assert_matches!(result.status, PayloadStatusEnum::Invalid { .. })); assert_matches!(res, Ok(result) => assert_matches!(result.status, PayloadStatusEnum::Invalid { .. }));
// Send new payload // Send new payload
let res = env let res = env
.send_new_payload( .send_new_payload(
ExecutionPayloadV1::from(random_block(&mut rng, 1, None, None, Some(0))), try_block_to_payload_v1(random_block(&mut rng, 1, None, None, Some(0))),
None, None,
) )
.await; .await;
let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Syncing); let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Syncing);
assert_matches!(res, Ok(result) => assert_eq!(result, expected_result)); assert_matches!(res, Ok(result) => assert_eq!(result, expected_result));
@ -2374,9 +2377,10 @@ mod tests {
// Send new payload // Send new payload
let result = env let result = env
.send_new_payload_retry_on_syncing(ExecutionPayloadV1::from(block2.clone()), None) .send_new_payload_retry_on_syncing(try_block_to_payload_v1(block2.clone()), None)
.await .await
.unwrap(); .unwrap();
let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Valid) let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Valid)
.with_latest_valid_hash(block2.hash); .with_latest_valid_hash(block2.hash);
assert_eq!(result, expected_result); assert_eq!(result, expected_result);
@ -2474,7 +2478,7 @@ mod tests {
// Send new payload // Send new payload
let block = random_block(&mut rng, 2, Some(H256::random()), None, Some(0)); let block = random_block(&mut rng, 2, Some(H256::random()), None, Some(0));
let res = env.send_new_payload(ExecutionPayloadV1::from(block), None).await; let res = env.send_new_payload(try_block_to_payload_v1(block), None).await;
let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Syncing); let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Syncing);
assert_matches!(res, Ok(result) => assert_eq!(result, expected_result)); assert_matches!(res, Ok(result) => assert_eq!(result, expected_result));
@ -2537,7 +2541,7 @@ mod tests {
// Send new payload // Send new payload
let result = env let result = env
.send_new_payload_retry_on_syncing(ExecutionPayloadV1::from(block2.clone()), None) .send_new_payload_retry_on_syncing(try_block_to_payload_v1(block2.clone()), None)
.await .await
.unwrap(); .unwrap();

View File

@ -16,6 +16,7 @@ reth-rlp.workspace = true
reth-transaction-pool.workspace = true reth-transaction-pool.workspace = true
reth-interfaces.workspace = true reth-interfaces.workspace = true
reth-revm-primitives = { path = "../../revm/revm-primitives" } reth-revm-primitives = { path = "../../revm/revm-primitives" }
reth-rpc-types-compat.workspace = true
## ethereum ## ethereum
revm-primitives.workspace = true revm-primitives.workspace = true

View File

@ -9,8 +9,11 @@ use reth_rpc_types::engine::{
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadV1, PayloadAttributes, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadV1, PayloadAttributes,
PayloadId, PayloadId,
}; };
use reth_rpc_types_compat::engine::payload::{
convert_block_to_payload_field_v2, convert_standalonewithdraw_to_withdrawal,
try_block_to_payload_v1, try_block_to_payload_v3,
};
use revm_primitives::{BlockEnv, CfgEnv}; use revm_primitives::{BlockEnv, CfgEnv};
/// Contains the built payload. /// 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. /// 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.
@ -76,7 +79,7 @@ impl BuiltPayload {
// V1 engine_getPayloadV1 response // V1 engine_getPayloadV1 response
impl From<BuiltPayload> for ExecutionPayloadV1 { impl From<BuiltPayload> for ExecutionPayloadV1 {
fn from(value: BuiltPayload) -> Self { fn from(value: BuiltPayload) -> Self {
value.block.into() try_block_to_payload_v1(value.block)
} }
} }
@ -85,7 +88,10 @@ impl From<BuiltPayload> for ExecutionPayloadEnvelopeV2 {
fn from(value: BuiltPayload) -> Self { fn from(value: BuiltPayload) -> Self {
let BuiltPayload { block, fees, .. } = value; let BuiltPayload { block, fees, .. } = value;
ExecutionPayloadEnvelopeV2 { block_value: fees, execution_payload: block.into() } ExecutionPayloadEnvelopeV2 {
block_value: fees,
execution_payload: convert_block_to_payload_field_v2(block),
}
} }
} }
@ -94,7 +100,7 @@ impl From<BuiltPayload> for ExecutionPayloadEnvelopeV3 {
let BuiltPayload { block, fees, sidecars, .. } = value; let BuiltPayload { block, fees, sidecars, .. } = value;
ExecutionPayloadEnvelopeV3 { ExecutionPayloadEnvelopeV3 {
execution_payload: block.into(), execution_payload: try_block_to_payload_v3(block),
block_value: fees, block_value: fees,
// From the engine API spec: // From the engine API spec:
// //
@ -137,13 +143,23 @@ impl PayloadBuilderAttributes {
/// Derives the unique [PayloadId] for the given parent and attributes /// Derives the unique [PayloadId] for the given parent and attributes
pub fn new(parent: H256, attributes: PayloadAttributes) -> Self { pub fn new(parent: H256, attributes: PayloadAttributes) -> Self {
let id = payload_id(&parent, &attributes); let id = payload_id(&parent, &attributes);
let withdraw = attributes.withdrawals.map(
|withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal>| {
withdrawals
.into_iter()
.map(convert_standalonewithdraw_to_withdrawal) // Removed the parentheses here
.collect::<Vec<_>>()
},
);
Self { Self {
id, id,
parent, parent,
timestamp: attributes.timestamp.as_u64(), timestamp: attributes.timestamp.as_u64(),
suggested_fee_recipient: attributes.suggested_fee_recipient, suggested_fee_recipient: attributes.suggested_fee_recipient,
prev_randao: attributes.prev_randao, prev_randao: attributes.prev_randao,
withdrawals: attributes.withdrawals.unwrap_or_default(), withdrawals: withdraw.unwrap_or_default(),
parent_beacon_block_root: attributes.parent_beacon_block_root, parent_beacon_block_root: attributes.parent_beacon_block_root,
} }
} }

View File

@ -21,6 +21,7 @@ reth-rpc-engine-api = { path = "../rpc-engine-api" }
reth-rpc-types.workspace = true reth-rpc-types.workspace = true
reth-tasks.workspace = true reth-tasks.workspace = true
reth-transaction-pool.workspace = true reth-transaction-pool.workspace = true
reth-rpc-types-compat.workspace = true
# rpc/net # rpc/net
jsonrpsee = { workspace = true, features = ["server"] } jsonrpsee = { workspace = true, features = ["server"] }

View File

@ -6,15 +6,17 @@ use reth_primitives::Block;
use reth_rpc::JwtSecret; use reth_rpc::JwtSecret;
use reth_rpc_api::clients::EngineApiClient; use reth_rpc_api::clients::EngineApiClient;
use reth_rpc_types::engine::{ForkchoiceState, PayloadId, TransitionConfiguration}; use reth_rpc_types::engine::{ForkchoiceState, PayloadId, TransitionConfiguration};
use reth_rpc_types_compat::engine::payload::{
convert_block_to_payload_input_v2, try_block_to_payload_v1,
};
#[allow(unused_must_use)] #[allow(unused_must_use)]
async fn test_basic_engine_calls<C>(client: &C) async fn test_basic_engine_calls<C>(client: &C)
where where
C: ClientT + SubscriptionClientT + Sync, C: ClientT + SubscriptionClientT + Sync,
{ {
let block = Block::default().seal_slow(); let block = Block::default().seal_slow();
EngineApiClient::new_payload_v1(client, block.clone().into()).await; EngineApiClient::new_payload_v1(client, try_block_to_payload_v1(block.clone())).await;
EngineApiClient::new_payload_v2(client, block.into()).await; EngineApiClient::new_payload_v2(client, convert_block_to_payload_input_v2(block)).await;
EngineApiClient::fork_choice_updated_v1(client, ForkchoiceState::default(), None).await; EngineApiClient::fork_choice_updated_v1(client, ForkchoiceState::default(), None).await;
EngineApiClient::get_payload_v1(client, PayloadId::new([0, 0, 0, 0, 0, 0, 0, 0])).await; EngineApiClient::get_payload_v1(client, PayloadId::new([0, 0, 0, 0, 0, 0, 0, 0])).await;
EngineApiClient::get_payload_v2(client, PayloadId::new([0, 0, 0, 0, 0, 0, 0, 0])).await; EngineApiClient::get_payload_v2(client, PayloadId::new([0, 0, 0, 0, 0, 0, 0, 0])).await;

View File

@ -18,7 +18,7 @@ reth-rpc-api = { path = "../rpc-api" }
reth-beacon-consensus = { path = "../../consensus/beacon" } reth-beacon-consensus = { path = "../../consensus/beacon" }
reth-payload-builder.workspace = true reth-payload-builder.workspace = true
reth-tasks.workspace = true reth-tasks.workspace = true
reth-rpc-types-compat.workspace = true
# async # async
tokio = { workspace = true, features = ["sync"] } tokio = { workspace = true, features = ["sync"] }

View File

@ -15,6 +15,9 @@ use reth_rpc_types::engine::{
ForkchoiceUpdated, PayloadAttributes, PayloadId, PayloadStatus, TransitionConfiguration, ForkchoiceUpdated, PayloadAttributes, PayloadId, PayloadStatus, TransitionConfiguration,
CAPABILITIES, CAPABILITIES,
}; };
use reth_rpc_types_compat::engine::payload::{
convert_payload_input_v2_to_payload, convert_to_payload_body_v1,
};
use reth_tasks::TaskSpawner; use reth_tasks::TaskSpawner;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::oneshot; use tokio::sync::oneshot;
@ -84,7 +87,7 @@ where
&self, &self,
payload: ExecutionPayloadInputV2, payload: ExecutionPayloadInputV2,
) -> EngineApiResult<PayloadStatus> { ) -> EngineApiResult<PayloadStatus> {
let payload = ExecutionPayload::from(payload); let payload = convert_payload_input_v2_to_payload(payload);
let payload_or_attrs = PayloadOrAttributes::from_execution_payload(&payload, None); let payload_or_attrs = PayloadOrAttributes::from_execution_payload(&payload, None);
self.validate_version_specific_fields(EngineApiMessageVersion::V2, &payload_or_attrs)?; self.validate_version_specific_fields(EngineApiMessageVersion::V2, &payload_or_attrs)?;
Ok(self.inner.beacon_consensus.new_payload(payload, None).await?) Ok(self.inner.beacon_consensus.new_payload(payload, None).await?)
@ -280,7 +283,7 @@ where
let block_result = inner.provider.block(BlockHashOrNumber::Number(num)); let block_result = inner.provider.block(BlockHashOrNumber::Number(num));
match block_result { match block_result {
Ok(block) => { Ok(block) => {
result.push(block.map(Into::into)); result.push(block.map(convert_to_payload_body_v1));
} }
Err(err) => { Err(err) => {
tx.send(Err(EngineApiError::Internal(Box::new(err)))).ok(); tx.send(Err(EngineApiError::Internal(Box::new(err)))).ok();
@ -311,7 +314,7 @@ where
.provider .provider
.block(BlockHashOrNumber::Hash(hash)) .block(BlockHashOrNumber::Hash(hash))
.map_err(|err| EngineApiError::Internal(Box::new(err)))?; .map_err(|err| EngineApiError::Internal(Box::new(err)))?;
result.push(block.map(Into::into)); result.push(block.map(convert_to_payload_body_v1));
} }
Ok(result) Ok(result)
@ -836,8 +839,11 @@ mod tests {
random_block_range(&mut rng, start..=start + count - 1, H256::default(), 0..2); random_block_range(&mut rng, start..=start + count - 1, H256::default(), 0..2);
handle.provider.extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.unseal()))); handle.provider.extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.unseal())));
let expected = let expected = blocks
blocks.iter().cloned().map(|b| Some(b.unseal().into())).collect::<Vec<_>>(); .iter()
.cloned()
.map(|b| Some(convert_to_payload_body_v1(b.unseal())))
.collect::<Vec<_>>();
let res = api.get_payload_bodies_by_range(start, count).await.unwrap(); let res = api.get_payload_bodies_by_range(start, count).await.unwrap();
assert_eq!(res, expected); assert_eq!(res, expected);
@ -875,7 +881,7 @@ mod tests {
if first_missing_range.contains(&b.number) { if first_missing_range.contains(&b.number) {
None None
} else { } else {
Some(b.unseal().into()) Some(convert_to_payload_body_v1(b.unseal()))
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -894,7 +900,7 @@ mod tests {
{ {
None None
} else { } else {
Some(b.unseal().into()) Some(convert_to_payload_body_v1(b.unseal()))
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -1,6 +1,6 @@
use reth_primitives::{Withdrawal, H256}; use reth_primitives::H256;
use reth_rpc_types::engine::{ExecutionPayload, PayloadAttributes};
use reth_rpc_types::engine::{ExecutionPayload, PayloadAttributes};
/// Either an [ExecutionPayload] or a [PayloadAttributes]. /// Either an [ExecutionPayload] or a [PayloadAttributes].
pub(crate) enum PayloadOrAttributes<'a> { pub(crate) enum PayloadOrAttributes<'a> {
/// An [ExecutionPayload] and optional parent beacon block root. /// An [ExecutionPayload] and optional parent beacon block root.
@ -25,7 +25,7 @@ impl<'a> PayloadOrAttributes<'a> {
} }
/// Return the withdrawals for the payload or attributes. /// Return the withdrawals for the payload or attributes.
pub(crate) fn withdrawals(&self) -> Option<&Vec<Withdrawal>> { pub(crate) fn withdrawals(&self) -> Option<&Vec<reth_rpc_types::engine::payload::Withdrawal>> {
match self { match self {
Self::ExecutionPayload { payload, .. } => payload.withdrawals(), Self::ExecutionPayload { payload, .. } => payload.withdrawals(),
Self::PayloadAttributes(attributes) => attributes.withdrawals.as_ref(), Self::PayloadAttributes(attributes) => attributes.withdrawals.as_ref(),

View File

@ -13,6 +13,10 @@ use reth_rlp::{Decodable, DecodeError};
use reth_rpc_types::engine::{ use reth_rpc_types::engine::{
ExecutionPayload, ExecutionPayloadBodyV1, ExecutionPayloadV1, PayloadError, ExecutionPayload, ExecutionPayloadBodyV1, ExecutionPayloadV1, PayloadError,
}; };
use reth_rpc_types_compat::engine::payload::{
convert_standalonewithdraw_to_withdrawal, convert_to_payload_body_v1, try_block_to_payload,
try_block_to_payload_v1, try_into_sealed_block, try_payload_v1_to_block,
};
fn transform_block<F: FnOnce(Block) -> Block>(src: SealedBlock, f: F) -> ExecutionPayload { fn transform_block<F: FnOnce(Block) -> Block>(src: SealedBlock, f: F) -> ExecutionPayload {
let unsealed = src.unseal(); let unsealed = src.unseal();
@ -20,13 +24,12 @@ fn transform_block<F: FnOnce(Block) -> Block>(src: SealedBlock, f: F) -> Executi
// Recalculate roots // Recalculate roots
transformed.header.transactions_root = proofs::calculate_transaction_root(&transformed.body); transformed.header.transactions_root = proofs::calculate_transaction_root(&transformed.body);
transformed.header.ommers_hash = proofs::calculate_ommers_root(&transformed.ommers); transformed.header.ommers_hash = proofs::calculate_ommers_root(&transformed.ommers);
SealedBlock { try_block_to_payload(SealedBlock {
header: transformed.header.seal_slow(), header: transformed.header.seal_slow(),
body: transformed.body, body: transformed.body,
ommers: transformed.ommers, ommers: transformed.ommers,
withdrawals: transformed.withdrawals, withdrawals: transformed.withdrawals,
} })
.into()
} }
#[test] #[test]
@ -34,7 +37,7 @@ fn payload_body_roundtrip() {
let mut rng = generators::rng(); let mut rng = generators::rng();
for block in random_block_range(&mut rng, 0..=99, H256::default(), 0..2) { for block in random_block_range(&mut rng, 0..=99, H256::default(), 0..2) {
let unsealed = block.clone().unseal(); let unsealed = block.clone().unseal();
let payload_body: ExecutionPayloadBodyV1 = unsealed.into(); let payload_body: ExecutionPayloadBodyV1 = convert_to_payload_body_v1(unsealed);
assert_eq!( assert_eq!(
Ok(block.body), Ok(block.body),
@ -44,8 +47,13 @@ fn payload_body_roundtrip() {
.map(|x| TransactionSigned::decode(&mut &x[..])) .map(|x| TransactionSigned::decode(&mut &x[..]))
.collect::<Result<Vec<_>, _>>(), .collect::<Result<Vec<_>, _>>(),
); );
let withdraw = payload_body.withdrawals.map(|withdrawals| {
assert_eq!(block.withdrawals, payload_body.withdrawals); withdrawals
.into_iter()
.map(convert_standalonewithdraw_to_withdrawal)
.collect::<Vec<_>>()
});
assert_eq!(block.withdrawals, withdraw);
} }
} }
@ -59,7 +67,8 @@ fn payload_validation() {
b.header.extra_data = BytesMut::zeroed(32).freeze().into(); b.header.extra_data = BytesMut::zeroed(32).freeze().into();
b b
}); });
assert_matches!(block_with_valid_extra_data.try_into_sealed_block(None), Ok(_));
assert_matches!(try_into_sealed_block(block_with_valid_extra_data, None), Ok(_));
// Invalid extra data // Invalid extra data
let block_with_invalid_extra_data: Bytes = BytesMut::zeroed(33).freeze(); let block_with_invalid_extra_data: Bytes = BytesMut::zeroed(33).freeze();
@ -68,7 +77,8 @@ fn payload_validation() {
b b
}); });
assert_matches!( assert_matches!(
invalid_extra_data_block.try_into_sealed_block(None),
try_into_sealed_block(invalid_extra_data_block,None),
Err(PayloadError::ExtraData(data)) if data == block_with_invalid_extra_data Err(PayloadError::ExtraData(data)) if data == block_with_invalid_extra_data
); );
@ -78,16 +88,18 @@ fn payload_validation() {
b b
}); });
assert_matches!( assert_matches!(
block_with_zero_base_fee.try_into_sealed_block(None),
try_into_sealed_block(block_with_zero_base_fee,None),
Err(PayloadError::BaseFee(val)) if val == U256::ZERO Err(PayloadError::BaseFee(val)) if val == U256::ZERO
); );
// Invalid encoded transactions // Invalid encoded transactions
let mut payload_with_invalid_txs: ExecutionPayloadV1 = block.clone().into(); let mut payload_with_invalid_txs: ExecutionPayloadV1 = try_block_to_payload_v1(block.clone());
payload_with_invalid_txs.transactions.iter_mut().for_each(|tx| { payload_with_invalid_txs.transactions.iter_mut().for_each(|tx| {
*tx = Bytes::new().into(); *tx = Bytes::new().into();
}); });
let payload_with_invalid_txs = Block::try_from(payload_with_invalid_txs); let payload_with_invalid_txs = try_payload_v1_to_block(payload_with_invalid_txs);
assert_matches!( assert_matches!(
payload_with_invalid_txs, payload_with_invalid_txs,
Err(PayloadError::Decode(DecodeError::InputTooShort)) Err(PayloadError::Decode(DecodeError::InputTooShort))
@ -99,7 +111,8 @@ fn payload_validation() {
b b
}); });
assert_matches!( assert_matches!(
block_with_ommers.clone().try_into_sealed_block(None), try_into_sealed_block(block_with_ommers.clone(),None),
Err(PayloadError::BlockHash { consensus, .. }) Err(PayloadError::BlockHash { consensus, .. })
if consensus == block_with_ommers.block_hash() if consensus == block_with_ommers.block_hash()
); );
@ -110,8 +123,9 @@ fn payload_validation() {
b b
}); });
assert_matches!( assert_matches!(
block_with_difficulty.clone().try_into_sealed_block(None), try_into_sealed_block(block_with_difficulty.clone(),None),
Err(PayloadError::BlockHash { consensus, .. }) if consensus == block_with_difficulty.block_hash() Err(PayloadError::BlockHash { consensus, .. }) if consensus == block_with_difficulty.block_hash()
); );
// None zero nonce // None zero nonce
@ -120,8 +134,9 @@ fn payload_validation() {
b b
}); });
assert_matches!( assert_matches!(
block_with_nonce.clone().try_into_sealed_block(None), try_into_sealed_block(block_with_nonce.clone(),None),
Err(PayloadError::BlockHash { consensus, .. }) if consensus == block_with_nonce.block_hash() Err(PayloadError::BlockHash { consensus, .. }) if consensus == block_with_nonce.block_hash()
); );
// Valid block // Valid block

View File

@ -14,3 +14,4 @@ Compatibility layer for reth-primitives and ethereum RPC types
reth-primitives.workspace = true reth-primitives.workspace = true
reth-rpc-types.workspace = true reth-rpc-types.workspace = true
reth-rlp.workspace = true reth-rlp.workspace = true

View File

@ -0,0 +1,6 @@
//! Standalone functions for engine specific rpc type conversions
pub mod payload;
pub use payload::{
convert_standalonewithdraw_to_withdrawal, convert_withdrawal_to_standalonewithdraw,
try_block_to_payload_v1, try_into_sealed_block, try_payload_v1_to_block,
};

View File

@ -0,0 +1,328 @@
//! Standalone Conversion Functions for Handling Different Versions of Execution Payloads in
//! Ethereum's Engine
use reth_primitives::{
constants::{MAXIMUM_EXTRA_DATA_SIZE, MIN_PROTOCOL_BASE_FEE_U256},
proofs::{self, EMPTY_LIST_HASH},
Block, Header, SealedBlock, TransactionSigned, UintTryTo, Withdrawal, H256, U256,
};
use reth_rlp::Decodable;
use reth_rpc_types::engine::{
payload::{ExecutionPayloadBodyV1, ExecutionPayloadFieldV2, ExecutionPayloadInputV2},
ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadError,
};
/// Converts [ExecutionPayloadV1] to [Block]
pub fn try_payload_v1_to_block(payload: ExecutionPayloadV1) -> Result<Block, PayloadError> {
if payload.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
return Err(PayloadError::ExtraData(payload.extra_data))
}
if payload.base_fee_per_gas < MIN_PROTOCOL_BASE_FEE_U256 {
return Err(PayloadError::BaseFee(payload.base_fee_per_gas))
}
let transactions = payload
.transactions
.iter()
.map(|tx| TransactionSigned::decode(&mut tx.as_ref()))
.collect::<Result<Vec<_>, _>>()?;
let transactions_root = proofs::calculate_transaction_root(&transactions);
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.as_u64(),
gas_limit: payload.gas_limit.as_u64(),
gas_used: payload.gas_used.as_u64(),
timestamp: payload.timestamp.as_u64(),
mix_hash: payload.prev_randao,
base_fee_per_gas: Some(
payload
.base_fee_per_gas
.uint_try_to()
.map_err(|_| PayloadError::BaseFee(payload.base_fee_per_gas))?,
),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
extra_data: payload.extra_data,
// Defaults
ommers_hash: EMPTY_LIST_HASH,
difficulty: Default::default(),
nonce: Default::default(),
};
Ok(Block { header, body: transactions, withdrawals: None, ommers: Default::default() })
}
/// Converts [ExecutionPayloadV2] to [Block]
pub fn try_payload_v2_to_block(payload: ExecutionPayloadV2) -> Result<Block, 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: Vec<_> = payload
.withdrawals
.iter()
.map(|w| convert_standalonewithdraw_to_withdrawal(w.clone()))
.collect();
let withdrawals_root = proofs::calculate_withdrawals_root(&withdrawals);
base_sealed_block.withdrawals = Some(withdrawals);
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<Block, 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.as_u64());
base_block.header.excess_blob_gas = Some(payload.excess_blob_gas.as_u64());
Ok(base_block)
}
/// Converts [SealedBlock] to [ExecutionPayload]
pub fn try_block_to_payload(value: SealedBlock) -> ExecutionPayload {
if value.header.parent_beacon_block_root.is_some() {
// block with parent beacon block root: V3
ExecutionPayload::V3(try_block_to_payload_v3(value))
} else if value.withdrawals.is_some() {
// block with withdrawals: V2
ExecutionPayload::V2(try_block_to_payload_v2(value))
} else {
// otherwise V1
ExecutionPayload::V1(try_block_to_payload_v1(value))
}
}
/// Converts [SealedBlock] to [ExecutionPayloadV1]
pub fn try_block_to_payload_v1(value: SealedBlock) -> ExecutionPayloadV1 {
let transactions = value
.body
.iter()
.map(|tx| {
let mut encoded = Vec::new();
tx.encode_enveloped(&mut encoded);
encoded.into()
})
.collect();
ExecutionPayloadV1 {
parent_hash: value.parent_hash,
fee_recipient: value.beneficiary,
state_root: value.state_root,
receipts_root: value.receipts_root,
logs_bloom: value.logs_bloom,
prev_randao: value.mix_hash,
block_number: value.number.into(),
gas_limit: value.gas_limit.into(),
gas_used: value.gas_used.into(),
timestamp: value.timestamp.into(),
extra_data: value.extra_data.clone(),
base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()),
block_hash: value.hash(),
transactions,
}
}
/// Converts [SealedBlock] to [ExecutionPayloadV2]
pub fn try_block_to_payload_v2(value: SealedBlock) -> ExecutionPayloadV2 {
let transactions = value
.body
.iter()
.map(|tx| {
let mut encoded = Vec::new();
tx.encode_enveloped(&mut encoded);
encoded.into()
})
.collect();
let standalone_withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal> = value
.withdrawals
.clone()
.unwrap_or_default()
.into_iter()
.map(convert_withdrawal_to_standalonewithdraw)
.collect();
ExecutionPayloadV2 {
payload_inner: ExecutionPayloadV1 {
parent_hash: value.parent_hash,
fee_recipient: value.beneficiary,
state_root: value.state_root,
receipts_root: value.receipts_root,
logs_bloom: value.logs_bloom,
prev_randao: value.mix_hash,
block_number: value.number.into(),
gas_limit: value.gas_limit.into(),
gas_used: value.gas_used.into(),
timestamp: value.timestamp.into(),
extra_data: value.extra_data.clone(),
base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()),
block_hash: value.hash(),
transactions,
},
withdrawals: standalone_withdrawals,
}
}
/// Converts [SealedBlock] to [ExecutionPayloadV3]
pub fn try_block_to_payload_v3(value: SealedBlock) -> ExecutionPayloadV3 {
let transactions = value
.body
.iter()
.map(|tx| {
let mut encoded = Vec::new();
tx.encode_enveloped(&mut encoded);
encoded.into()
})
.collect();
let withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal> = value
.withdrawals
.clone()
.unwrap_or_default()
.into_iter()
.map(convert_withdrawal_to_standalonewithdraw)
.collect();
ExecutionPayloadV3 {
payload_inner: ExecutionPayloadV2 {
payload_inner: ExecutionPayloadV1 {
parent_hash: value.parent_hash,
fee_recipient: value.beneficiary,
state_root: value.state_root,
receipts_root: value.receipts_root,
logs_bloom: value.logs_bloom,
prev_randao: value.mix_hash,
block_number: value.number.into(),
gas_limit: value.gas_limit.into(),
gas_used: value.gas_used.into(),
timestamp: value.timestamp.into(),
extra_data: value.extra_data.clone(),
base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()),
block_hash: value.hash(),
transactions,
},
withdrawals,
},
blob_gas_used: value.blob_gas_used.unwrap_or_default().into(),
excess_blob_gas: value.excess_blob_gas.unwrap_or_default().into(),
}
}
/// Converts [SealedBlock] to [ExecutionPayloadFieldV2]
pub fn convert_block_to_payload_field_v2(value: SealedBlock) -> ExecutionPayloadFieldV2 {
// if there are withdrawals, return V2
if value.withdrawals.is_some() {
ExecutionPayloadFieldV2::V2(try_block_to_payload_v2(value))
} else {
ExecutionPayloadFieldV2::V1(try_block_to_payload_v1(value))
}
}
/// 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 [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 {
let withdraw = value.withdrawals.clone().map(|withdrawals| {
withdrawals.into_iter().map(convert_withdrawal_to_standalonewithdraw).collect::<Vec<_>>()
});
ExecutionPayloadInputV2 {
withdrawals: withdraw,
execution_payload: try_block_to_payload_v1(value),
}
}
/// Tries to create a new block from the given payload and optional parent beacon block root.
/// Perform additional validation of `extra_data` and `base_fee_per_gas` fields.
///
/// NOTE: The log bloom is assumed to be validated during serialization.
/// NOTE: Empty ommers, nonce and difficulty values are validated upon computing block hash and
/// comparing the value with `payload.block_hash`.
///
/// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
pub fn try_into_sealed_block(
value: ExecutionPayload,
parent_beacon_block_root: Option<H256>,
) -> Result<SealedBlock, PayloadError> {
let block_hash = value.block_hash();
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 = parent_beacon_block_root;
let payload = base_payload.seal_slow();
if block_hash != payload.hash() {
return Err(PayloadError::BlockHash { execution: payload.hash(), consensus: block_hash })
}
Ok(payload)
}
/// Converts [Withdrawal] to [reth_rpc_types::engine::payload::Withdrawal]
pub fn convert_withdrawal_to_standalonewithdraw(
withdrawal: Withdrawal,
) -> reth_rpc_types::engine::payload::Withdrawal {
reth_rpc_types::engine::payload::Withdrawal {
index: withdrawal.index,
validator_index: withdrawal.validator_index,
address: withdrawal.address,
amount: withdrawal.amount,
}
}
/// Converts [reth_rpc_types::engine::payload::Withdrawal] to [Withdrawal]
pub fn convert_standalonewithdraw_to_withdrawal(
standalone: reth_rpc_types::engine::payload::Withdrawal,
) -> Withdrawal {
Withdrawal {
index: standalone.index,
validator_index: standalone.validator_index,
address: standalone.address,
amount: standalone.amount,
}
}
/// Converts [Block] to [ExecutionPayloadBodyV1]
pub fn convert_to_payload_body_v1(value: Block) -> ExecutionPayloadBodyV1 {
let transactions = value.body.into_iter().map(|tx| {
let mut out = Vec::new();
tx.encode_enveloped(&mut out);
out.into()
});
let withdraw: Option<Vec<reth_rpc_types::engine::payload::Withdrawal>> =
value.withdrawals.map(|withdrawals| {
withdrawals
.into_iter()
.map(convert_withdrawal_to_standalonewithdraw)
.collect::<Vec<_>>()
});
ExecutionPayloadBodyV1 { transactions: transactions.collect(), withdrawals: withdraw }
}

View File

@ -19,3 +19,5 @@ pub mod block;
pub use block::*; pub use block::*;
pub mod transaction; pub mod transaction;
pub use transaction::*; pub use transaction::*;
pub mod engine;
pub use engine::*;

View File

@ -23,6 +23,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true serde_json.workspace = true
jsonrpsee-types = { workspace = true, optional = true } jsonrpsee-types = { workspace = true, optional = true }
[features] [features]
default = ["jsonrpsee-types"] default = ["jsonrpsee-types"]
@ -30,3 +31,5 @@ default = ["jsonrpsee-types"]
# misc # misc
rand.workspace = true rand.workspace = true
similar-asserts = "1.4" similar-asserts = "1.4"

View File

@ -4,9 +4,8 @@
mod cancun; mod cancun;
mod forkchoice; mod forkchoice;
mod payload; pub mod payload;
mod transition; mod transition;
pub use self::{cancun::*, forkchoice::*, payload::*, transition::*}; pub use self::{cancun::*, forkchoice::*, payload::*, transition::*};
/// The list of all supported Engine capabilities available over the engine endpoint. /// The list of all supported Engine capabilities available over the engine endpoint.

View File

@ -1,11 +1,8 @@
pub use crate::Withdrawal;
use reth_primitives::{ use reth_primitives::{
constants::{MAXIMUM_EXTRA_DATA_SIZE, MIN_PROTOCOL_BASE_FEE_U256},
kzg::{Blob, Bytes48}, kzg::{Blob, Bytes48},
proofs::{self, EMPTY_LIST_HASH}, Address, BlobTransactionSidecar, Bloom, Bytes, SealedBlock, H256, H64, U256, U64,
Address, BlobTransactionSidecar, Block, Bloom, Bytes, Header, SealedBlock, TransactionSigned,
UintTryTo, Withdrawal, H256, H64, U256, U64,
}; };
use reth_rlp::Decodable;
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
/// The execution payload body response that allows for `null` values. /// The execution payload body response that allows for `null` values.
@ -60,26 +57,6 @@ impl ExecutionPayloadFieldV2 {
} }
} }
impl From<SealedBlock> for ExecutionPayloadFieldV2 {
fn from(value: SealedBlock) -> Self {
// if there are withdrawals, return V2
if value.withdrawals.is_some() {
ExecutionPayloadFieldV2::V2(value.into())
} else {
ExecutionPayloadFieldV2::V1(value.into())
}
}
}
impl From<ExecutionPayloadFieldV2> for ExecutionPayload {
fn from(value: ExecutionPayloadFieldV2) -> Self {
match value {
ExecutionPayloadFieldV2::V1(payload) => ExecutionPayload::V1(payload),
ExecutionPayloadFieldV2::V2(payload) => ExecutionPayload::V2(payload),
}
}
}
/// This is the input to `engine_newPayloadV2`, which may or may not have a withdrawals field. /// This is the input to `engine_newPayloadV2`, which may or may not have a withdrawals field.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -92,27 +69,6 @@ pub struct ExecutionPayloadInputV2 {
pub withdrawals: Option<Vec<Withdrawal>>, pub withdrawals: Option<Vec<Withdrawal>>,
} }
impl From<ExecutionPayloadInputV2> for ExecutionPayload {
fn from(value: ExecutionPayloadInputV2) -> Self {
match value.withdrawals {
Some(withdrawals) => ExecutionPayload::V2(ExecutionPayloadV2 {
payload_inner: value.execution_payload,
withdrawals,
}),
None => ExecutionPayload::V1(value.execution_payload),
}
}
}
impl From<SealedBlock> for ExecutionPayloadInputV2 {
fn from(value: SealedBlock) -> Self {
ExecutionPayloadInputV2 {
withdrawals: value.withdrawals.clone(),
execution_payload: value.into(),
}
}
}
/// This structure maps for the return value of `engine_getPayload` of the beacon chain spec, for /// This structure maps for the return value of `engine_getPayload` of the beacon chain spec, for
/// V2. /// V2.
/// ///
@ -211,66 +167,6 @@ impl From<SealedBlock> for ExecutionPayloadV1 {
} }
} }
/// Try to construct a block from given payload. Perform addition validation of `extra_data` and
/// `base_fee_per_gas` fields.
///
/// NOTE: The log bloom is assumed to be validated during serialization.
/// NOTE: Empty ommers, nonce and difficulty values are validated upon computing block hash and
/// comparing the value with `payload.block_hash`.
///
/// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
impl TryFrom<ExecutionPayloadV1> for Block {
type Error = PayloadError;
fn try_from(payload: ExecutionPayloadV1) -> Result<Self, Self::Error> {
if payload.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
return Err(PayloadError::ExtraData(payload.extra_data))
}
if payload.base_fee_per_gas < MIN_PROTOCOL_BASE_FEE_U256 {
return Err(PayloadError::BaseFee(payload.base_fee_per_gas))
}
let transactions = payload
.transactions
.iter()
.map(|tx| TransactionSigned::decode(&mut tx.as_ref()))
.collect::<Result<Vec<_>, _>>()?;
let transactions_root = proofs::calculate_transaction_root(&transactions);
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.as_u64(),
gas_limit: payload.gas_limit.as_u64(),
gas_used: payload.gas_used.as_u64(),
timestamp: payload.timestamp.as_u64(),
mix_hash: payload.prev_randao,
base_fee_per_gas: Some(
payload
.base_fee_per_gas
.uint_try_to()
.map_err(|_| PayloadError::BaseFee(payload.base_fee_per_gas))?,
),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
extra_data: payload.extra_data,
// Defaults
ommers_hash: EMPTY_LIST_HASH,
difficulty: Default::default(),
nonce: Default::default(),
};
Ok(Block { header, body: transactions, withdrawals: None, ommers: Default::default() })
}
}
/// This structure maps on the ExecutionPayloadV2 structure of the beacon chain spec. /// This structure maps on the ExecutionPayloadV2 structure of the beacon chain spec.
/// ///
/// See also: <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2> /// See also: <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>
@ -293,55 +189,6 @@ impl ExecutionPayloadV2 {
} }
} }
impl From<SealedBlock> for ExecutionPayloadV2 {
fn from(value: SealedBlock) -> Self {
let transactions = value
.body
.iter()
.map(|tx| {
let mut encoded = Vec::new();
tx.encode_enveloped(&mut encoded);
encoded.into()
})
.collect();
ExecutionPayloadV2 {
payload_inner: ExecutionPayloadV1 {
parent_hash: value.parent_hash,
fee_recipient: value.beneficiary,
state_root: value.state_root,
receipts_root: value.receipts_root,
logs_bloom: value.logs_bloom,
prev_randao: value.mix_hash,
block_number: value.number.into(),
gas_limit: value.gas_limit.into(),
gas_used: value.gas_used.into(),
timestamp: value.timestamp.into(),
extra_data: value.extra_data.clone(),
base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()),
block_hash: value.hash(),
transactions,
},
withdrawals: value.withdrawals.unwrap_or_default(),
}
}
}
impl TryFrom<ExecutionPayloadV2> for Block {
type Error = PayloadError;
fn try_from(payload: ExecutionPayloadV2) -> Result<Self, Self::Error> {
// this performs the same conversion as the underlying V1 payload, but calculates the
// withdrawals root and adds withdrawals
let mut base_sealed_block = Block::try_from(payload.payload_inner)?;
let withdrawals_root = proofs::calculate_withdrawals_root(&payload.withdrawals);
base_sealed_block.withdrawals = Some(payload.withdrawals);
base_sealed_block.header.withdrawals_root = Some(withdrawals_root);
Ok(base_sealed_block)
}
}
/// This structure maps on the ExecutionPayloadV3 structure of the beacon chain spec. /// This structure maps on the ExecutionPayloadV3 structure of the beacon chain spec.
/// ///
/// See also: <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2> /// See also: <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>
@ -372,62 +219,6 @@ impl ExecutionPayloadV3 {
} }
} }
impl From<SealedBlock> for ExecutionPayloadV3 {
fn from(mut value: SealedBlock) -> Self {
let transactions = value
.body
.iter()
.map(|tx| {
let mut encoded = Vec::new();
tx.encode_enveloped(&mut encoded);
encoded.into()
})
.collect();
let withdrawals = value.withdrawals.take().unwrap_or_default();
ExecutionPayloadV3 {
payload_inner: ExecutionPayloadV2 {
payload_inner: ExecutionPayloadV1 {
parent_hash: value.parent_hash,
fee_recipient: value.beneficiary,
state_root: value.state_root,
receipts_root: value.receipts_root,
logs_bloom: value.logs_bloom,
prev_randao: value.mix_hash,
block_number: value.number.into(),
gas_limit: value.gas_limit.into(),
gas_used: value.gas_used.into(),
timestamp: value.timestamp.into(),
extra_data: value.extra_data.clone(),
base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()),
block_hash: value.hash(),
transactions,
},
withdrawals,
},
blob_gas_used: value.blob_gas_used.unwrap_or_default().into(),
excess_blob_gas: value.excess_blob_gas.unwrap_or_default().into(),
}
}
}
impl TryFrom<ExecutionPayloadV3> for Block {
type Error = PayloadError;
fn try_from(payload: ExecutionPayloadV3) -> Result<Self, Self::Error> {
// this performs the same conversion as the underlying V2 payload, but inserts the blob gas
// used and excess blob gas
let mut base_block = Block::try_from(payload.payload_inner)?;
base_block.header.blob_gas_used = Some(payload.blob_gas_used.as_u64());
base_block.header.excess_blob_gas = Some(payload.excess_blob_gas.as_u64());
Ok(base_block)
}
}
/// This includes all bundled blob related data of an executed payload. /// This includes all bundled blob related data of an executed payload.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlobsBundleV1 { pub struct BlobsBundleV1 {
@ -510,36 +301,6 @@ impl ExecutionPayload {
} }
} }
} }
/// Tries to create a new block from the given payload and optional parent beacon block root.
/// Perform additional validation of `extra_data` and `base_fee_per_gas` fields.
///
/// NOTE: The log bloom is assumed to be validated during serialization.
/// NOTE: Empty ommers, nonce and difficulty values are validated upon computing block hash and
/// comparing the value with `payload.block_hash`.
///
/// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
pub fn try_into_sealed_block(
self,
parent_beacon_block_root: Option<H256>,
) -> Result<SealedBlock, PayloadError> {
let block_hash = self.block_hash();
let mut base_payload = match self {
ExecutionPayload::V1(payload) => Block::try_from(payload)?,
ExecutionPayload::V2(payload) => Block::try_from(payload)?,
ExecutionPayload::V3(payload) => Block::try_from(payload)?,
};
base_payload.header.parent_beacon_block_root = parent_beacon_block_root;
let payload = base_payload.seal_slow();
if block_hash != payload.hash() {
return Err(PayloadError::BlockHash { execution: payload.hash(), consensus: block_hash })
}
Ok(payload)
}
} }
impl From<ExecutionPayloadV1> for ExecutionPayload { impl From<ExecutionPayloadV1> for ExecutionPayload {
@ -560,21 +321,6 @@ impl From<ExecutionPayloadV3> for ExecutionPayload {
} }
} }
impl From<SealedBlock> for ExecutionPayload {
fn from(block: SealedBlock) -> Self {
if block.header.parent_beacon_block_root.is_some() {
// block with parent beacon block root: V3
Self::V3(block.into())
} else if block.withdrawals.is_some() {
// block with withdrawals: V2
Self::V2(block.into())
} else {
// otherwise V1
Self::V1(block.into())
}
}
}
/// Error that can occur when handling payloads. /// Error that can occur when handling payloads.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum PayloadError { pub enum PayloadError {
@ -626,20 +372,6 @@ pub struct ExecutionPayloadBodyV1 {
pub withdrawals: Option<Vec<Withdrawal>>, pub withdrawals: Option<Vec<Withdrawal>>,
} }
impl From<Block> for ExecutionPayloadBodyV1 {
fn from(value: Block) -> Self {
let transactions = value.body.into_iter().map(|tx| {
let mut out = Vec::new();
tx.encode_enveloped(&mut out);
out.into()
});
ExecutionPayloadBodyV1 {
transactions: transactions.collect(),
withdrawals: value.withdrawals,
}
}
}
/// This structure contains the attributes required to initiate a payload build process in the /// This structure contains the attributes required to initiate a payload build process in the
/// context of an `engine_forkchoiceUpdated` call. /// context of an `engine_forkchoiceUpdated` call.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -15,15 +15,18 @@ mod syncing;
pub mod trace; pub mod trace;
mod transaction; mod transaction;
pub mod txpool; pub mod txpool;
mod withdrawal;
mod work; mod work;
pub use account::*; pub use account::*;
pub use block::*; pub use block::*;
pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext}; pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext};
pub use engine::{ExecutionPayload, PayloadError};
pub use fee::{FeeHistory, TxGasAndReward}; pub use fee::{FeeHistory, TxGasAndReward};
pub use filter::*; pub use filter::*;
pub use index::Index; pub use index::Index;
pub use log::Log; pub use log::Log;
pub use syncing::*; pub use syncing::*;
pub use transaction::*; pub use transaction::*;
pub use withdrawal::Withdrawal;
pub use work::Work; pub use work::Work;

View File

@ -0,0 +1,40 @@
use reth_primitives::{constants::GWEI_TO_WEI, serde_helper::u64_hex, Address, U256};
use reth_rlp::RlpEncodable;
use serde::{Deserialize, Serialize};
/// Withdrawal represents a validator withdrawal from the consensus layer.
#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodable, Serialize, Deserialize)]
pub struct Withdrawal {
/// Monotonically increasing identifier issued by consensus layer.
#[serde(with = "u64_hex")]
pub index: u64,
/// Index of validator associated with withdrawal.
#[serde(with = "u64_hex", rename = "validatorIndex")]
pub validator_index: u64,
/// Target address for withdrawn ether.
pub address: Address,
/// Value of the withdrawal in gwei.
#[serde(with = "u64_hex")]
pub amount: u64,
}
impl Withdrawal {
/// Return the withdrawal amount in wei.
pub fn amount_wei(&self) -> U256 {
U256::from(self.amount) * U256::from(GWEI_TO_WEI)
}
}
#[cfg(test)]
mod tests {
use super::*;
// <https://github.com/paradigmxyz/reth/issues/1614>
#[test]
fn test_withdrawal_serde_roundtrip() {
let input = r#"[{"index":"0x0","validatorIndex":"0x0","address":"0x0000000000000000000000000000000000001000","amount":"0x1"},{"index":"0x1","validatorIndex":"0x1","address":"0x0000000000000000000000000000000000001001","amount":"0x1"},{"index":"0x2","validatorIndex":"0x2","address":"0x0000000000000000000000000000000000001002","amount":"0x1"},{"index":"0x3","validatorIndex":"0x3","address":"0x0000000000000000000000000000000000001003","amount":"0x1"},{"index":"0x4","validatorIndex":"0x4","address":"0x0000000000000000000000000000000000001004","amount":"0x1"},{"index":"0x5","validatorIndex":"0x5","address":"0x0000000000000000000000000000000000001005","amount":"0x1"},{"index":"0x6","validatorIndex":"0x6","address":"0x0000000000000000000000000000000000001006","amount":"0x1"},{"index":"0x7","validatorIndex":"0x7","address":"0x0000000000000000000000000000000000001007","amount":"0x1"},{"index":"0x8","validatorIndex":"0x8","address":"0x0000000000000000000000000000000000001008","amount":"0x1"},{"index":"0x9","validatorIndex":"0x9","address":"0x0000000000000000000000000000000000001009","amount":"0x1"},{"index":"0xa","validatorIndex":"0xa","address":"0x000000000000000000000000000000000000100a","amount":"0x1"},{"index":"0xb","validatorIndex":"0xb","address":"0x000000000000000000000000000000000000100b","amount":"0x1"},{"index":"0xc","validatorIndex":"0xc","address":"0x000000000000000000000000000000000000100c","amount":"0x1"},{"index":"0xd","validatorIndex":"0xd","address":"0x000000000000000000000000000000000000100d","amount":"0x1"},{"index":"0xe","validatorIndex":"0xe","address":"0x000000000000000000000000000000000000100e","amount":"0x1"},{"index":"0xf","validatorIndex":"0xf","address":"0x000000000000000000000000000000000000100f","amount":"0x1"}]"#;
let withdrawals: Vec<Withdrawal> = serde_json::from_str(input).unwrap();
let s = serde_json::to_string(&withdrawals).unwrap();
assert_eq!(input, s);
}
}