mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-08 03:01:12 -04:00
feat: Duplicate Withdrawal and move try from impls to rpc-compat (#4186)
This commit is contained in:
@@ -23,6 +23,7 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
jsonrpsee-types = { workspace = true, optional = true }
|
||||
|
||||
|
||||
[features]
|
||||
default = ["jsonrpsee-types"]
|
||||
|
||||
@@ -30,3 +31,5 @@ default = ["jsonrpsee-types"]
|
||||
# misc
|
||||
rand.workspace = true
|
||||
similar-asserts = "1.4"
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
|
||||
mod cancun;
|
||||
mod forkchoice;
|
||||
mod payload;
|
||||
pub mod payload;
|
||||
mod transition;
|
||||
|
||||
pub use self::{cancun::*, forkchoice::*, payload::*, transition::*};
|
||||
|
||||
/// The list of all supported Engine capabilities available over the engine endpoint.
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
pub use crate::Withdrawal;
|
||||
use reth_primitives::{
|
||||
constants::{MAXIMUM_EXTRA_DATA_SIZE, MIN_PROTOCOL_BASE_FEE_U256},
|
||||
kzg::{Blob, Bytes48},
|
||||
proofs::{self, EMPTY_LIST_HASH},
|
||||
Address, BlobTransactionSidecar, Block, Bloom, Bytes, Header, SealedBlock, TransactionSigned,
|
||||
UintTryTo, Withdrawal, H256, H64, U256, U64,
|
||||
Address, BlobTransactionSidecar, Bloom, Bytes, SealedBlock, H256, H64, U256, U64,
|
||||
};
|
||||
use reth_rlp::Decodable;
|
||||
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
|
||||
|
||||
/// 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.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -92,27 +69,6 @@ pub struct ExecutionPayloadInputV2 {
|
||||
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
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
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 {
|
||||
@@ -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.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum PayloadError {
|
||||
@@ -626,20 +372,6 @@ pub struct ExecutionPayloadBodyV1 {
|
||||
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
|
||||
/// context of an `engine_forkchoiceUpdated` call.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -15,15 +15,18 @@ mod syncing;
|
||||
pub mod trace;
|
||||
mod transaction;
|
||||
pub mod txpool;
|
||||
mod withdrawal;
|
||||
mod work;
|
||||
|
||||
pub use account::*;
|
||||
pub use block::*;
|
||||
pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext};
|
||||
pub use engine::{ExecutionPayload, PayloadError};
|
||||
pub use fee::{FeeHistory, TxGasAndReward};
|
||||
pub use filter::*;
|
||||
pub use index::Index;
|
||||
pub use log::Log;
|
||||
pub use syncing::*;
|
||||
pub use transaction::*;
|
||||
pub use withdrawal::Withdrawal;
|
||||
pub use work::Work;
|
||||
|
||||
40
crates/rpc/rpc-types/src/eth/withdrawal.rs
Normal file
40
crates/rpc/rpc-types/src/eth/withdrawal.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user