diff --git a/Cargo.lock b/Cargo.lock index 0f3f6ea..372db1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,6 +249,8 @@ dependencies = [ "arbitrary", "c-kzg", "derive_more 0.99.18", + "ethereum_ssz", + "ethereum_ssz_derive", "k256", "once_cell", "rand 0.8.5", @@ -367,6 +369,7 @@ dependencies = [ "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-transport", "alloy-transport-http", @@ -458,6 +461,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c31a3750b8f5a350d17354e46a52b0f2f19ec5f2006d816935af599dedc521" dependencies = [ + "alloy-rpc-types-beacon", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-rpc-types-trace", @@ -497,6 +501,8 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", + "ethereum_ssz", + "ethereum_ssz_derive", "serde", "serde_with", "thiserror", @@ -514,6 +520,8 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", + "ethereum_ssz", + "ethereum_ssz_derive", "jsonrpsee-types", "jsonwebtoken", "rand 0.8.5", @@ -1691,7 +1699,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] @@ -2071,14 +2079,38 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] @@ -2091,17 +2123,28 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn 2.0.76", ] +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core", + "darling_core 0.20.10", "quote", "syn 2.0.76", ] @@ -2583,6 +2626,18 @@ dependencies = [ "smallvec", ] +[[package]] +name = "ethereum_ssz_derive" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eccd5378ec34a07edd3d9b48088cbc63309d0367d14ba10b0cdb1d1791080ea" +dependencies = [ + "darling 0.13.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "event-listener" version = "5.3.1" @@ -8671,6 +8726,7 @@ dependencies = [ "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", + "ethereum_ssz", "fastrlp", "num-bigint", "num-traits", @@ -9126,7 +9182,7 @@ version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ - "darling", + "darling 0.20.10", "proc-macro2", "quote", "syn 2.0.76", @@ -9445,6 +9501,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index c9ea196..c491e1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,6 +134,8 @@ alloy = { version = "0.2", features = [ "provider-http", "signers", "consensus", + "rpc-types-engine", + "ssz" ] } alloy-primitives = { version = "0.8", features = ["serde"] } alloy-rlp = "0.3.4" diff --git a/crates/net/src/types/envelope.rs b/crates/net/src/types/envelope.rs index c1293a6..30b3704 100644 --- a/crates/net/src/types/envelope.rs +++ b/crates/net/src/types/envelope.rs @@ -1,13 +1,14 @@ //! Execution Payload Envelope Type -use alloy::primitives::{Signature, B256}; +use alloy::{ + primitives::{Signature, B256}, + rpc::types::engine::{ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3}, +}; use eyre::Result; -use kona_primitives::L2ExecutionPayload; +use kona_primitives::{alloy_primitives::private::ssz::Decode, L2ExecutionPayload}; use ssz_rs::prelude::*; -use super::payload::{ - ExecutionPayloadV1SSZ, ExecutionPayloadV2SSZ, ExecutionPayloadV3SSZ, PayloadHash, -}; +use super::payload::PayloadHash; /// An envelope around the execution payload for L2. #[derive(Debug, Clone)] @@ -32,8 +33,9 @@ impl ExecutionPayloadEnvelope { let signature = Signature::try_from(sig_data)?; - let payload = ExecutionPayloadV1SSZ::deserialize(block_data)?; - let payload = L2ExecutionPayload::from(payload); + let payload = + ExecutionPayloadV1::from_ssz_bytes(block_data).map_err(|e| eyre::eyre!("{:?}", e))?; + let payload = Self::convert_payload_v1(payload); let hash = PayloadHash::from(block_data); @@ -49,8 +51,9 @@ impl ExecutionPayloadEnvelope { let signature = Signature::try_from(sig_data)?; - let payload = ExecutionPayloadV2SSZ::deserialize(block_data)?; - let payload = L2ExecutionPayload::from(payload); + let payload = + ExecutionPayloadV2::from_ssz_bytes(block_data).map_err(|e| eyre::eyre!("{:?}", e))?; + let payload = Self::convert_payload_v2(payload); let hash = PayloadHash::from(block_data); @@ -69,11 +72,116 @@ impl ExecutionPayloadEnvelope { let parent_beacon_block_root = Some(B256::from_slice(parent_beacon_block_root)); - let payload = ExecutionPayloadV3SSZ::deserialize(block_data)?; - let payload = L2ExecutionPayload::from(payload); + let payload = + ExecutionPayloadV3::from_ssz_bytes(block_data).map_err(|e| eyre::eyre!("{:?}", e))?; + let payload = Self::convert_payload_v3(payload); let hash = PayloadHash::from(block_data); Ok(ExecutionPayloadEnvelope { parent_beacon_block_root, signature, payload, hash }) } + + fn convert_payload_v1(payload: ExecutionPayloadV1) -> L2ExecutionPayload { + L2ExecutionPayload { + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom, + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit.into(), + gas_used: payload.gas_used.into(), + timestamp: payload.timestamp, + extra_data: payload.extra_data, + base_fee_per_gas: Self::convert_uint128(payload.base_fee_per_gas), + block_hash: payload.block_hash, + transactions: payload.transactions, + deserialized_transactions: Vec::new(), + withdrawals: None, + blob_gas_used: None, + excess_blob_gas: None, + } + } + + fn convert_payload_v2(payload: ExecutionPayloadV2) -> L2ExecutionPayload { + L2ExecutionPayload { + parent_hash: payload.payload_inner.parent_hash, + fee_recipient: payload.payload_inner.fee_recipient, + state_root: payload.payload_inner.state_root, + receipts_root: payload.payload_inner.receipts_root, + logs_bloom: payload.payload_inner.logs_bloom, + prev_randao: payload.payload_inner.prev_randao, + block_number: payload.payload_inner.block_number, + gas_limit: payload.payload_inner.gas_limit.into(), + gas_used: payload.payload_inner.gas_used.into(), + timestamp: payload.payload_inner.timestamp, + extra_data: payload.payload_inner.extra_data, + base_fee_per_gas: Self::convert_uint128(payload.payload_inner.base_fee_per_gas), + block_hash: payload.payload_inner.block_hash, + transactions: payload.payload_inner.transactions, + deserialized_transactions: Vec::default(), + withdrawals: Some(Vec::new()), + blob_gas_used: None, + excess_blob_gas: None, + } + } + + fn convert_payload_v3(payload: ExecutionPayloadV3) -> L2ExecutionPayload { + L2ExecutionPayload { + parent_hash: payload.payload_inner.payload_inner.parent_hash, + fee_recipient: payload.payload_inner.payload_inner.fee_recipient, + state_root: payload.payload_inner.payload_inner.state_root, + receipts_root: payload.payload_inner.payload_inner.receipts_root, + logs_bloom: payload.payload_inner.payload_inner.logs_bloom, + prev_randao: payload.payload_inner.payload_inner.prev_randao, + block_number: payload.payload_inner.payload_inner.block_number, + gas_limit: payload.payload_inner.payload_inner.gas_limit.into(), + gas_used: payload.payload_inner.payload_inner.gas_used.into(), + timestamp: payload.payload_inner.payload_inner.timestamp, + extra_data: payload.payload_inner.payload_inner.extra_data, + base_fee_per_gas: Self::convert_uint128( + payload.payload_inner.payload_inner.base_fee_per_gas, + ), + block_hash: payload.payload_inner.payload_inner.block_hash, + transactions: payload.payload_inner.payload_inner.transactions, + deserialized_transactions: Vec::default(), + withdrawals: Some(Vec::new()), + blob_gas_used: Some(payload.blob_gas_used.into()), + excess_blob_gas: Some(payload.excess_blob_gas.into()), + } + } + + fn convert_uint128(value: alloy::primitives::U256) -> Option { + let bytes = value.to_le_bytes_vec(); + let bytes: [u8; 16] = bytes.try_into().ok()?; + Some(u128::from_le_bytes(bytes)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy::hex; + + #[test] + fn decode_v1() { + let data = hex::decode("0xbd04f043128457c6ccf35128497167442bcc0f8cce78cda8b366e6a12e526d938d1e4c1046acffffbfc542a7e212bb7d80d3a4b2f84f7b196d935398a24eb84c519789b401000000fe0300fe0300fe0300fe0300fe0300fe0300a203000c4a8fd56621ad04fc0101067601008ce60be0005b220117c32c0f3b394b346c2aa42cfa8157cd41f891aa0bec485a62fc010000").unwrap(); + let payload_envelop = ExecutionPayloadEnvelope::decode_v1(&data).unwrap(); + assert_eq!(1725271882, payload_envelop.payload.timestamp); + } + + #[test] + fn decode_v2() { + let data = hex::decode("0xc104f0433805080eb36c0b130a7cc1dc74c3f721af4e249aa6f61bb89d1557143e971bb738a3f3b98df7c457e74048e9d2d7e5cd82bb45e3760467e2270e9db86d1271a700000000fe0300fe0300fe0300fe0300fe0300fe0300a203000c6b89d46525ad000205067201009cda69cb5b9b73fc4eb2458b37d37f04ff507fe6c9cd2ab704a05ea9dae3cd61760002000000020000").unwrap(); + let payload_envelop = ExecutionPayloadEnvelope::decode_v2(&data).unwrap(); + assert_eq!(1708427627, payload_envelop.payload.timestamp); + } + + #[test] + fn decode_v3() { + let data = hex::decode("0xf104f0434442b9eb38b259f5b23826e6b623e829d2fb878dac70187a1aecf42a3f9bedfd29793d1fcb5822324be0d3e12340a95855553a65d64b83e5579dffb31470df5d010000006a03000412346a1d00fe0100fe0100fe0100fe0100fe0100fe01004201000cc588d465219504100201067601007cfece77b89685f60e3663b6e0faf2de0734674eb91339700c4858c773a8ff921e014401043e0100").unwrap(); + let payload_envelop = ExecutionPayloadEnvelope::decode_v3(&data).unwrap(); + assert_eq!(1708427461, payload_envelop.payload.timestamp); + } } diff --git a/crates/net/src/types/payload.rs b/crates/net/src/types/payload.rs index c5c5f77..5e45529 100644 --- a/crates/net/src/types/payload.rs +++ b/crates/net/src/types/payload.rs @@ -1,17 +1,7 @@ //! Execution Payload Types use alloy::primitives::{keccak256, B256}; -use kona_primitives::L2ExecutionPayload; -use ssz_rs::{prelude::*, List, Vector, U256}; - -/// A type alias for a vector of 32 bytes, representing a Bytes32 hash -type Bytes32 = Vector; - -/// A type alias for a vector of 20 bytes, representing an address -type VecAddress = Vector; - -/// A type alias for a byte list, representing a transaction -type Transaction = List; +use ssz_rs::prelude::*; /// Represents the Keccak256 hash of the block #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] @@ -35,245 +25,12 @@ impl PayloadHash { } } -/// The pre Canyon/Shanghai [L2ExecutionPayload] - the withdrawals field should not exist -#[derive(SimpleSerialize, Default)] -pub struct ExecutionPayloadV1SSZ { - /// Block hash of the parent block - pub parent_hash: Bytes32, - /// Fee recipient of the block. Set to the sequencer fee vault - pub fee_recipient: VecAddress, - /// State root of the block - pub state_root: Bytes32, - /// Receipts root of the block - pub receipts_root: Bytes32, - /// Logs bloom of the block - pub logs_bloom: Vector, - /// The block mix_digest - pub prev_randao: Bytes32, - /// The block number - pub block_number: u64, - /// The block gas limit - pub gas_limit: u64, - /// Total gas used in the block - pub gas_used: u64, - /// Timestamp of the block - pub timestamp: u64, - /// Any extra data included in the block - pub extra_data: List, - /// Base fee per gas of the block - pub base_fee_per_gas: U256, - /// Hash of the block - pub block_hash: Bytes32, - /// Transactions in the block - pub transactions: List, -} - -impl From for L2ExecutionPayload { - fn from(value: ExecutionPayloadV1SSZ) -> Self { - Self { - parent_hash: convert_hash(value.parent_hash), - fee_recipient: convert_address(value.fee_recipient), - state_root: convert_hash(value.state_root), - receipts_root: convert_hash(value.receipts_root), - logs_bloom: convert_bloom(value.logs_bloom), - prev_randao: convert_hash(value.prev_randao), - block_number: value.block_number, - gas_limit: value.gas_limit.into(), - gas_used: value.gas_used.into(), - timestamp: value.timestamp, - extra_data: convert_byte_list(value.extra_data), - base_fee_per_gas: convert_uint(value.base_fee_per_gas), - block_hash: convert_hash(value.block_hash), - transactions: convert_tx_list(value.transactions), - deserialized_transactions: Vec::default(), - withdrawals: None, - blob_gas_used: None, - excess_blob_gas: None, - } - } -} - -/// The Canyon/Shanghai [L2ExecutionPayload] - the withdrawals field should be an empty [List] -#[derive(SimpleSerialize, Default)] -pub struct ExecutionPayloadV2SSZ { - /// Block hash of the parent block - pub parent_hash: Bytes32, - /// Fee recipient of the block. Set to the sequencer fee vault - pub fee_recipient: VecAddress, - /// State root of the block - pub state_root: Bytes32, - /// Receipts root of the block - pub receipts_root: Bytes32, - /// Logs bloom of the block - pub logs_bloom: Vector, - /// The block mix_digest - pub prev_randao: Bytes32, - /// The block number - pub block_number: u64, - /// The block gas limit - pub gas_limit: u64, - /// Total gas used in the block - pub gas_used: u64, - /// Timestamp of the block - pub timestamp: u64, - /// Any extra data included in the block - pub extra_data: List, - /// Base fee per gas of the block - pub base_fee_per_gas: U256, - /// Hash of the block - pub block_hash: Bytes32, - /// Transactions in the block - pub transactions: List, - /// An empty list. This is unused and only exists for L1 compatibility. - pub withdrawals: List, -} - -/// This represents an L1 validator Withdrawal, and is unused in OP stack rollups. -/// Exists only for L1 compatibility -#[derive(SimpleSerialize, Default)] -pub struct Withdrawal { - /// Index of the withdrawal - index: u64, - /// Index of the validator - validator_index: u64, - /// Account address that has withdrawn - address: VecAddress, - /// The amount withdrawn - amount: u64, -} - -impl From for L2ExecutionPayload { - /// Converts an ExecutionPayloadV2SSZ received via p2p gossip into an [L2ExecutionPayload] used - /// by the engine. - fn from(value: ExecutionPayloadV2SSZ) -> Self { - Self { - parent_hash: convert_hash(value.parent_hash), - fee_recipient: convert_address(value.fee_recipient), - state_root: convert_hash(value.state_root), - receipts_root: convert_hash(value.receipts_root), - logs_bloom: convert_bloom(value.logs_bloom), - prev_randao: convert_hash(value.prev_randao), - block_number: value.block_number, - gas_limit: value.gas_limit.into(), - gas_used: value.gas_used.into(), - timestamp: value.timestamp, - extra_data: convert_byte_list(value.extra_data), - base_fee_per_gas: convert_uint(value.base_fee_per_gas), - block_hash: convert_hash(value.block_hash), - transactions: convert_tx_list(value.transactions), - deserialized_transactions: Vec::default(), - withdrawals: Some(Vec::new()), - blob_gas_used: None, - excess_blob_gas: None, - } - } -} - -/// The Ecotone [L2ExecutionPayload] - Adds Eip 4844 fields to the payload -/// - `blob_gas_used` -/// - `excess_blob_gas` -#[derive(SimpleSerialize, Default)] -pub struct ExecutionPayloadV3SSZ { - /// Block hash of the parent block - pub parent_hash: Bytes32, - /// Fee recipient of the block. Set to the sequencer fee vault - pub fee_recipient: VecAddress, - /// State root of the block - pub state_root: Bytes32, - /// Receipts root of the block - pub receipts_root: Bytes32, - /// Logs bloom of the block - pub logs_bloom: Vector, - /// The block mix_digest - pub prev_randao: Bytes32, - /// The block number - pub block_number: u64, - /// The block gas limit - pub gas_limit: u64, - /// Total gas used in the block - pub gas_used: u64, - /// Timestamp of the block - pub timestamp: u64, - /// Any extra data included in the block - pub extra_data: List, - /// Base fee per gas of the block - pub base_fee_per_gas: U256, - /// Hash of the block - pub block_hash: Bytes32, - /// Transactions in the block - pub transactions: List, - /// An empty list. This is unused and only exists for L1 compatibility. - pub withdrawals: List, - /// The total gas used by the blob in the block - pub blob_gas_used: u64, - /// The excess gas used by the blob in the block - pub excess_blob_gas: u64, -} - -impl From for L2ExecutionPayload { - fn from(value: ExecutionPayloadV3SSZ) -> Self { - Self { - parent_hash: convert_hash(value.parent_hash), - fee_recipient: convert_address(value.fee_recipient), - state_root: convert_hash(value.state_root), - receipts_root: convert_hash(value.receipts_root), - logs_bloom: convert_bloom(value.logs_bloom), - prev_randao: convert_hash(value.prev_randao), - block_number: value.block_number, - gas_limit: value.gas_limit.into(), - gas_used: value.gas_used.into(), - timestamp: value.timestamp, - extra_data: convert_byte_list(value.extra_data), - base_fee_per_gas: convert_uint(value.base_fee_per_gas), - block_hash: convert_hash(value.block_hash), - transactions: convert_tx_list(value.transactions), - deserialized_transactions: Vec::default(), - withdrawals: Some(Vec::new()), - blob_gas_used: Some(value.blob_gas_used.into()), - excess_blob_gas: Some(value.excess_blob_gas.into()), - } - } -} - -/// Converts an [ssz_rs::Vector] of bytes into [alloy::primitives::Bloom] -fn convert_bloom(vector: Vector) -> alloy::primitives::Bloom { - let mut bloom = [0u8; 256]; - bloom.copy_from_slice(vector.as_ref()); - alloy::primitives::Bloom::from(bloom) -} - -/// Converts [Bytes32] into [alloy::primitives::B256] -fn convert_hash(bytes: Bytes32) -> alloy::primitives::B256 { - alloy::primitives::B256::from_slice(bytes.as_slice()) -} - -/// Converts [VecAddress] into [alloy::primitives::Address] -fn convert_address(address: VecAddress) -> alloy::primitives::Address { - alloy::primitives::Address::from_slice(address.as_slice()) -} - -/// Converts an [ssz_rs::List] of bytes into [alloy::primitives::Bytes] -fn convert_byte_list(list: List) -> alloy::primitives::Bytes { - alloy::primitives::Bytes::from(list.to_vec()) -} - -/// Converts a [U256] into [u128] -fn convert_uint(value: U256) -> Option { - let bytes = value.to_bytes_le(); - let bytes: [u8; 16] = bytes.try_into().ok()?; - Some(u128::from_le_bytes(bytes)) -} - -/// Converts [ssz_rs::List] of [Transaction] into a vector of [alloy::primitives::Bytes] -fn convert_tx_list(value: List) -> Vec { - value.iter().map(|tx| alloy::primitives::Bytes::from(tx.to_vec())).collect() -} - #[cfg(test)] mod tests { - use super::*; use alloy::primitives::b256; + use super::*; + #[test] fn test_signature_message() { let inner = b256!("9999999999999999999999999999999999999999999999999999999999999999");