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

View File

@@ -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"

View File

@@ -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.

View File

@@ -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)]

View File

@@ -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;

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);
}
}