From ff65983bc1cf1f8981f133d79eb8ad74ea5bb888 Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Wed, 11 Dec 2024 14:19:05 -0800 Subject: [PATCH 01/12] feat(relay): Add ExecutionRequestsV4 for relay submissions. Re-add {Deposit,Withdrawal,Consolidation}Request structs to relevant EIPs. --- crates/eips/Cargo.toml | 3 +- crates/eips/src/eip6110.rs | 65 ++++++- crates/eips/src/eip7002.rs | 64 ++++++- crates/eips/src/eip7251.rs | 61 ++++++- crates/rpc-types-beacon/Cargo.toml | 1 - ...y_builder_block_validation_request_v4.json | 164 ++++++++++++++++++ crates/rpc-types-beacon/src/lib.rs | 3 + crates/rpc-types-beacon/src/relay.rs | 18 +- crates/rpc-types-beacon/src/requests.rs | 18 ++ 9 files changed, 385 insertions(+), 12 deletions(-) create mode 100644 crates/rpc-types-beacon/src/examples/relay_builder_block_validation_request_v4.json create mode 100644 crates/rpc-types-beacon/src/requests.rs diff --git a/crates/eips/Cargo.toml b/crates/eips/Cargo.toml index 6c55582a7d8..900af7c4762 100644 --- a/crates/eips/Cargo.toml +++ b/crates/eips/Cargo.toml @@ -29,6 +29,7 @@ alloy-rlp = { workspace = true, features = ["derive"] } # serde alloy-serde = { workspace = true, optional = true } serde = { workspace = true, optional = true } +serde_with = { workspace = true, optional = true } # kzg c-kzg = { workspace = true, optional = true } @@ -64,7 +65,7 @@ rand.workspace = true default = ["std", "kzg-sidecar"] std = ["alloy-primitives/std", "alloy-rlp/std", "serde?/std", "c-kzg?/std", "once_cell?/std"] -serde = ["dep:alloy-serde", "dep:serde", "alloy-primitives/serde", +serde = ["dep:alloy-serde", "dep:serde", "dep:serde_with", "alloy-primitives/serde", "c-kzg?/serde", "alloy-eip2930/serde", "alloy-eip7702/serde"] serde-bincode-compat = ["alloy-eip7702/serde-bincode-compat"] kzg = ["kzg-sidecar", "sha2", "dep:c-kzg", "dep:once_cell"] diff --git a/crates/eips/src/eip6110.rs b/crates/eips/src/eip6110.rs index 7dee0fa09c4..26ce395756b 100644 --- a/crates/eips/src/eip6110.rs +++ b/crates/eips/src/eip6110.rs @@ -1,8 +1,10 @@ -//! Contains Deposit request constants, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md). +//! Contains Deposit request types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md). //! //! See also [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110): Supply validator deposits on chain -use alloy_primitives::{address, Address}; +use alloy_primitives::{address, Address, FixedBytes, B256}; +#[cfg(feature = "serde")] +use serde_with::{serde_as, DisplayFromStr}; /// Mainnet deposit contract address. pub const MAINNET_DEPOSIT_CONTRACT_ADDRESS: Address = @@ -10,3 +12,62 @@ pub const MAINNET_DEPOSIT_CONTRACT_ADDRESS: Address = /// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for deposit requests. pub const DEPOSIT_REQUEST_TYPE: u8 = 0x00; + +/// This structure maps onto the deposit object from [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110). +#[cfg_attr(feature = "serde", serde_as)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +pub struct DepositRequest { + /// Validator public key + pub pubkey: FixedBytes<48>, + /// Withdrawal credentials + pub withdrawal_credentials: B256, + /// Amount of ether deposited in gwei + #[serde_as(as = "DisplayFromStr")] + pub amount: u64, + /// Deposit signature + pub signature: FixedBytes<96>, + /// Deposit index + #[serde_as(as = "DisplayFromStr")] + pub index: u64, +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + + #[test] + fn test_serde_deposit_request() { + // Sample JSON input representing a deposit request + let json_data = r#"{"pubkey":"0x8e01a8f21bdc38991ada53ca86d6c78d874675a450a38431cc6aa0f12d5661e344784c56c8a211f7025224d1303ee801","withdrawal_credentials":"0x010000000000000000000000af6df504f08ddf582d604d2f0a593bc153c25dbd","amount":"18112749083033600","signature":"0xb65f3db79405544528d6d92040282f29171f4ff6e5abb2d59f9ee1f1254aced2a7000f87bc2684f543e913a7cc1007ea0e97289b349c553eecdf253cd3ef5814088ba3d4ac286f2634dac3d026d9a01e4c166dc75e249d626a0f1c180dab75ce","index":"13343631333247680512"}"#; + + // Deserialize the JSON into a DepositRequest struct + let deposit_request: DepositRequest = + serde_json::from_str(json_data).expect("Failed to deserialize"); + + // Verify the deserialized content + assert_eq!( + deposit_request.pubkey, + FixedBytes::<48>::from(hex!("8E01A8F21BDC38991ADA53CA86D6C78D874675A450A38431CC6AA0F12D5661E344784C56C8A211F7025224D1303EE801")) + ); + assert_eq!( + deposit_request.withdrawal_credentials, + B256::from(hex!("010000000000000000000000AF6DF504F08DDF582D604D2F0A593BC153C25DBD")) + ); + assert_eq!(deposit_request.amount, 0x0040597307000000u64); + assert_eq!( + deposit_request.signature, + FixedBytes::<96>::from(hex!("B65F3DB79405544528D6D92040282F29171F4FF6E5ABB2D59F9EE1F1254ACED2A7000F87BC2684F543E913A7CC1007EA0E97289B349C553EECDF253CD3EF5814088BA3D4AC286F2634DAC3D026D9A01E4C166DC75E249D626A0F1C180DAB75CE")) + ); + assert_eq!(deposit_request.index, 0xB92E1A0000000000u64); + + // Serialize the struct back into JSON + let serialized_json = serde_json::to_string(&deposit_request).expect("Failed to serialize"); + + // Check if the serialized JSON matches the expected JSON structure + assert_eq!(serialized_json, json_data); + } +} diff --git a/crates/eips/src/eip7002.rs b/crates/eips/src/eip7002.rs index b8dfef97e53..8f881c5e172 100644 --- a/crates/eips/src/eip7002.rs +++ b/crates/eips/src/eip7002.rs @@ -1,8 +1,10 @@ -//! Contains the system contract, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md). +//! Contains the system contract and [WithdrawalRequest] types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md). //! //! See also [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002): Execution layer triggerable withdrawals -use alloy_primitives::{address, bytes, Address, Bytes}; +use alloy_primitives::{address, bytes, Address, Bytes, FixedBytes}; +#[cfg(feature = "serde")] +use serde_with::{serde_as, DisplayFromStr}; /// The caller to be used when calling the EIP-7002 withdrawal requests contract at the end of the /// block. @@ -17,3 +19,61 @@ pub static WITHDRAWAL_REQUEST_PREDEPLOY_CODE: Bytes = bytes!(" 3373fffffffffff /// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for withdrawal requests. pub const WITHDRAWAL_REQUEST_TYPE: u8 = 0x01; + +/// Represents an execution layer triggerable withdrawal request. +/// +/// See [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002). +#[cfg_attr(feature = "serde", serde_as)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +pub struct WithdrawalRequest { + /// Address of the source of the exit. + pub source_address: Address, + /// Validator public key. + pub validator_pubkey: FixedBytes<48>, + /// Amount of withdrawn ether in gwei. + #[serde_as(as = "DisplayFromStr")] + pub amount: u64, +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + use core::str::FromStr; + + #[test] + fn test_serde_withdrawal_request() { + // Sample JSON input representing a withdrawal request + let json_data = r#"{ + "source_address":"0xAE0E8770147AaA6828a0D6f642504663F10F7d1E", + "validator_pubkey":"0x8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b", + "amount":"1" + }"#; + + // Deserialize the JSON into a WithdrawalRequest struct + let withdrawal_request: WithdrawalRequest = + serde_json::from_str(json_data).expect("Failed to deserialize"); + + // Verify the deserialized content + assert_eq!( + withdrawal_request.source_address, + Address::from_str("0xAE0E8770147AaA6828a0D6f642504663F10F7d1E").unwrap() + ); + assert_eq!( + withdrawal_request.validator_pubkey, + FixedBytes::<48>::from(hex!("8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b")) + ); + assert_eq!(withdrawal_request.amount, 1); + + // Serialize the struct back into JSON + let serialized_json = + serde_json::to_string(&withdrawal_request).expect("Failed to serialize"); + + // Check if the serialized JSON matches the expected JSON structure + let expected_json = r#"{"source_address":"0xae0e8770147aaa6828a0d6f642504663f10f7d1e","validator_pubkey":"0x8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b","amount":"1"}"#; + assert_eq!(serialized_json, expected_json); + } +} diff --git a/crates/eips/src/eip7251.rs b/crates/eips/src/eip7251.rs index ead24920a23..158f59bd0a5 100644 --- a/crates/eips/src/eip7251.rs +++ b/crates/eips/src/eip7251.rs @@ -1,8 +1,8 @@ -//! Contains consolidation code, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md). +//! Contains consolidation types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md). //! //! See also [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251): Increase the MAX_EFFECTIVE_BALANCE -use alloy_primitives::{address, bytes, Address, Bytes}; +use alloy_primitives::{address, bytes, Address, Bytes, FixedBytes}; /// The address for the EIP-7251 consolidation requests contract: /// `0x00b42dbF2194e931E80326D950320f7d9Dbeac02` @@ -14,3 +14,60 @@ pub static CONSOLIDATION_REQUEST_PREDEPLOY_CODE: Bytes = bytes!("3373fffffffffff /// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for consolidation requests. pub const CONSOLIDATION_REQUEST_TYPE: u8 = 0x02; + +/// This structure maps onto the consolidation request object from [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +pub struct ConsolidationRequest { + /// Source address + pub source_address: Address, + /// Source public key + pub source_pubkey: FixedBytes<48>, + /// Target public key + pub target_pubkey: FixedBytes<48>, +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + use core::str::FromStr; + + #[test] + fn test_serde_consolidation_request() { + // Sample JSON input representing a consolidation request + let json_data = r#"{ + "source_address":"0x007eABCA654E67103dF02f49EbdC5f6Cd9387a07", + "source_pubkey":"0xb13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7", + "target_pubkey":"0xd0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f" + }"#; + + // Deserialize the JSON into a ConsolidationRequest struct + let consolidation_request: ConsolidationRequest = + serde_json::from_str(json_data).expect("Failed to deserialize"); + + // Verify the deserialized content + assert_eq!( + consolidation_request.source_address, + Address::from_str("0x007eABCA654E67103dF02f49EbdC5f6Cd9387a07").unwrap() + ); + assert_eq!( + consolidation_request.source_pubkey, + FixedBytes::<48>::from(hex!("b13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7")) + ); + assert_eq!( + consolidation_request.target_pubkey, + FixedBytes::<48>::from(hex!("d0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f")) + ); + + // Serialize the struct back into JSON + let serialized_json = + serde_json::to_string(&consolidation_request).expect("Failed to serialize"); + + // Check if the serialized JSON matches the expected JSON structure + let expected_json = r#"{"source_address":"0x007eabca654e67103df02f49ebdc5f6cd9387a07","source_pubkey":"0xb13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7","target_pubkey":"0xd0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f"}"#; + assert_eq!(serialized_json, expected_json); + } +} diff --git a/crates/rpc-types-beacon/Cargo.toml b/crates/rpc-types-beacon/Cargo.toml index 85eec6b6f02..9e9c60549f6 100644 --- a/crates/rpc-types-beacon/Cargo.toml +++ b/crates/rpc-types-beacon/Cargo.toml @@ -22,7 +22,6 @@ workspace = true alloy-eips = { workspace = true, features = ["serde"] } alloy-rpc-types-engine = { workspace = true, features = ["serde"] } alloy-primitives.workspace = true -alloy-serde.workspace = true # ssz ethereum_ssz_derive = { workspace = true, optional = true } diff --git a/crates/rpc-types-beacon/src/examples/relay_builder_block_validation_request_v4.json b/crates/rpc-types-beacon/src/examples/relay_builder_block_validation_request_v4.json new file mode 100644 index 00000000000..e61d8277280 --- /dev/null +++ b/crates/rpc-types-beacon/src/examples/relay_builder_block_validation_request_v4.json @@ -0,0 +1,164 @@ +{ + "message": { + "slot": "6", + "parent_hash": "0x139b559e427d726a8cf7eda9c11a2586ed827a7619ffaeed3033845dc06b88e8", + "block_hash": "0x2982946c9fb44951fd9f65f73446a49d4fd6e0140b3f3ea857ff50eac7be69d7", + "builder_pubkey": "0xa1885d66bef164889a2e35845c3b626545d7b0e513efe335e97c3a45e534013fa3bc38c3b7e6143695aecc4872ac52c4", + "proposer_pubkey": "0x903e2989e7442ee0a8958d020507a8bd985d3974f5e8273093be00db3935f0500e141b252bd09e3728892c7a8443863c", + "proposer_fee_recipient": "0x690b9a9e9aa1c9db991c7721a92d351db4fac990", + "gas_limit": "30000000", + "gas_used": "42000", + "value": "999990575298322000" + }, + "execution_payload": { + "parent_hash": "0x139b559e427d726a8cf7eda9c11a2586ed827a7619ffaeed3033845dc06b88e8", + "fee_recipient": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "state_root": "0x0f1109a777d62875f91b9457eb41122e27cf0d5eb478e475c46e57c6ee53ebcf", + "receipts_root": "0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1", + "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prev_randao": "0x790a18ca85bf4a2e1f14679abda509fcdda1e322b6a2adbcabee1727793960d7", + "block_number": "6", + "gas_limit": "30000000", + "gas_used": "42000", + "timestamp": "1733949274", + "extra_data": "0xe29aa1f09fa496", + "base_fee_per_gas": "448795319", + "block_hash": "0x2982946c9fb44951fd9f65f73446a49d4fd6e0140b3f3ea857ff50eac7be69d7", + "transactions": [ + "0x02f87082053901018445e0d81382520894f39fd6e51aad88f6f4ce6ab8827279cfffb92266880de0b6b3a764000080c001a0650262f2686338f44fc10d7da58e1aea564840d0c98ff11c44e0032143a52738a03d5bf4978d9d3429da0be7a5f0f82d1408a1a4c97ae2c72b7625c778d08190bd", + "0x02f8708205390180841ac012b782520894690b9a9e9aa1c9db991c7721a92d351db4fac990880de0ae214b651e5080c001a03ef471558fe8f14fbfc1e094e0e37da8f6fe16272e388fd00c47e438e4f7e68ea011a49e0f3fe3833c7629f809612c22dbba3361902721885bb0e2ce15e3efc16b" + ], + "withdrawals": [ + { + "index": "33", + "validator_index": "84", + "address": "0x8794388915e86e4988363cdd4289ad19182209c8", + "amount": "4578" + }, + { + "index": "34", + "validator_index": "85", + "address": "0xa3862121db5914d7272b0b705e6e3c5336b79e31", + "amount": "5886" + }, + { + "index": "35", + "validator_index": "86", + "address": "0x96ef954b331a534199f4f113d993a50ec7a781fc", + "amount": "5232" + }, + { + "index": "36", + "validator_index": "87", + "address": "0x96c8d3dd08724624017f178393d176b425dab9df", + "amount": "4578" + }, + { + "index": "37", + "validator_index": "88", + "address": "0x92bd81b8e9099b9ca87a2033fdd84475752dc34a", + "amount": "2616" + }, + { + "index": "38", + "validator_index": "89", + "address": "0x83802cd575a3cea7e3e38fc1a73d94a9e4fdb999", + "amount": "654" + }, + { + "index": "39", + "validator_index": "90", + "address": "0xb451eb0ff4990917aba6e3d80c34aee91ea1ce49", + "amount": "5886" + }, + { + "index": "40", + "validator_index": "91", + "address": "0xa7f711233af57440e9ea700113fc4dbaef97e7da", + "amount": "1962" + }, + { + "index": "41", + "validator_index": "92", + "address": "0xaca5e4979f281b5ab0ea0f549d6dcc34989607c3", + "amount": "4578" + }, + { + "index": "42", + "validator_index": "93", + "address": "0x984620db3658a19769475080998db9e7f5bcd425", + "amount": "5886" + }, + { + "index": "43", + "validator_index": "94", + "address": "0x8f1ef3639aea57fef705847e251b785bb608a848", + "amount": "2616" + }, + { + "index": "44", + "validator_index": "95", + "address": "0x8967da3c8071ba2bf632cd40ae08fbbf0a203c47", + "amount": "7194" + }, + { + "index": "45", + "validator_index": "96", + "address": "0x8d58f7e2e58471b46d20a66a61f4cde3c78ab6c0", + "amount": "7194" + }, + { + "index": "46", + "validator_index": "97", + "address": "0x8db9f236d3483af79703244c7034b5267a0546c3", + "amount": "3270" + }, + { + "index": "47", + "validator_index": "98", + "address": "0xb7721412ae5a793f34ac8866698b221c67ef8272", + "amount": "3270" + }, + { + "index": "48", + "validator_index": "99", + "address": "0x99f6e5b80dc52407f0436d3474bd5da5ff23a19c", + "amount": "2616" + } + ], + "blob_gas_used": "0", + "excess_blob_gas": "0" + }, + "blobs_bundle": { + "commitments": [], + "proofs": [], + "blobs": [] + }, + "execution_requests": { + "deposits": [ + { + "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", + "withdrawal_credentials": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "amount": "1", + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", + "index": "1" + } + ], + "withdrawals": [ + { + "source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09", + "validator_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", + "amount": "1" + } + ], + "consolidations": [ + { + "source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09", + "source_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", + "target_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" + } + ] + }, + "target_blobs_per_block": "6", + "signature": "0xa37afc405ef69e4f331e79f9de77e0df870609898c0e10a1cfcd8162e8771c4e4fefa7258059d83f72fb599b12f1bb73068476ebfaedc65e5a068425693ba272f277d83e11334e87a7d1425a2fbd369ed9351f0eb14fdc8bd93115543f6a4c67" +} \ No newline at end of file diff --git a/crates/rpc-types-beacon/src/lib.rs b/crates/rpc-types-beacon/src/lib.rs index 9097056cc37..897cea89a2b 100644 --- a/crates/rpc-types-beacon/src/lib.rs +++ b/crates/rpc-types-beacon/src/lib.rs @@ -24,6 +24,9 @@ pub mod payload; /// Types and functions related to the relay mechanism. pub mod relay; +/// Types and functions related to execution requests. +pub mod requests; + /// Types and functions related to the sidecar. pub mod sidecar; diff --git a/crates/rpc-types-beacon/src/relay.rs b/crates/rpc-types-beacon/src/relay.rs index 2940256c47e..2a4fe0e55dc 100644 --- a/crates/rpc-types-beacon/src/relay.rs +++ b/crates/rpc-types-beacon/src/relay.rs @@ -2,8 +2,8 @@ //! //! See also -use crate::{BlsPublicKey, BlsSignature}; -use alloy_primitives::{Address, Bytes, B256, U256}; +use crate::{requests::ExecutionRequestsV4, BlsPublicKey, BlsSignature}; +use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types_engine::{ BlobsBundleV1, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, }; @@ -146,6 +146,7 @@ pub struct SignedBidSubmissionV3 { } /// Submission for the `/relay/v1/builder/blocks` endpoint (Electra). +#[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))] @@ -158,9 +159,9 @@ pub struct SignedBidSubmissionV4 { /// The Electra block bundle for this bid. pub blobs_bundle: BlobsBundleV1, /// The Pectra execution requests for this bid. - pub execution_requests: Vec, + pub execution_requests: ExecutionRequestsV4, /// The EIP-7742 blobs per block for this bid. - #[serde(with = "alloy_serde::quantity")] + #[serde_as(as = "DisplayFromStr")] pub target_blobs_per_block: u64, /// The signature associated with the submission. pub signature: BlsSignature, @@ -479,6 +480,15 @@ mod tests { assert_eq!(json, serde_json::to_value(bid).unwrap()); } + #[test] + fn electra_bid_submission() { + let s = include_str!("examples/relay_builder_block_validation_request_v4.json"); + + let bid = serde_json::from_str::(s).unwrap(); + let json: serde_json::Value = serde_json::from_str(s).unwrap(); + assert_eq!(json, serde_json::to_value(bid).unwrap()); + } + #[cfg(feature = "ssz")] #[test] fn capella_bid_submission_ssz() { diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs new file mode 100644 index 00000000000..31092fb23d1 --- /dev/null +++ b/crates/rpc-types-beacon/src/requests.rs @@ -0,0 +1,18 @@ +use alloy_eips::{ + eip6110::DepositRequest, eip7002::WithdrawalRequest, eip7251::ConsolidationRequest, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(rename_all = "snake_case")] +#[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))] +/// An Electra-compatible execution requests payload. +pub struct ExecutionRequestsV4 { + /// The requested deposits. + pub deposits: Vec, + /// The requested withdrawals. + pub withdrawals: Vec, + /// The requested consolidations. + pub consolidations: Vec, +} From 679f0eda03dc038c061be9b8b4a45fce8ed93f3e Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Thu, 12 Dec 2024 12:35:35 -0800 Subject: [PATCH 02/12] feat(requests): Add ExecutionRequestsV4::TryFrom<&Requests> using ssz_types. --- crates/rpc-types-beacon/Cargo.toml | 2 + crates/rpc-types-beacon/src/requests.rs | 132 ++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/crates/rpc-types-beacon/Cargo.toml b/crates/rpc-types-beacon/Cargo.toml index 9e9c60549f6..c74ad21cd81 100644 --- a/crates/rpc-types-beacon/Cargo.toml +++ b/crates/rpc-types-beacon/Cargo.toml @@ -31,6 +31,7 @@ serde.workspace = true serde_with = { workspace = true, features = ["alloc"] } thiserror.workspace = true +ssz_types = { version = "0.9.0", optional = true } [dev-dependencies] serde_json.workspace = true @@ -42,3 +43,4 @@ ssz = [ "dep:ethereum_ssz_derive", "alloy-rpc-types-engine/ssz", ] +ssz_types = ["dep:ssz_types"] diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs index 31092fb23d1..f44b68f9906 100644 --- a/crates/rpc-types-beacon/src/requests.rs +++ b/crates/rpc-types-beacon/src/requests.rs @@ -16,3 +16,135 @@ pub struct ExecutionRequestsV4 { /// The requested consolidations. pub consolidations: Vec, } + +#[cfg(feature = "ssz_types")] +mod from_requests { + use crate::requests::ExecutionRequestsV4; + use alloy_eips::{ + eip6110::DepositRequest, eip7002::WithdrawalRequest, eip7251::ConsolidationRequest, + eip7685::Requests, + }; + use ssz::DecodeError; + + impl TryFrom<&Requests> for ExecutionRequestsV4 { + type Error = Error; + fn try_from(value: &Requests) -> Result { + use alloy_eips::{ + eip6110::DEPOSIT_REQUEST_TYPE, eip7002::WITHDRAWAL_REQUEST_TYPE, + eip7251::CONSOLIDATION_REQUEST_TYPE, + }; + use ssz::Decode; + use ssz_types::{typenum, VariableList}; + + type MaxConsolidationRequestsPerPayload = typenum::U1; + type MaxDepositRequestsPerPayload = typenum::U8192; + type MaxWithdrawalRequestsPerPayload = typenum::U16; + + let (deposits, withdrawals, consolidations) = value.iter().try_fold( + (Vec::new(), Vec::new(), Vec::new()), + |mut acc, request| { + if request.is_empty() { + return Err(Error::EmptyRequest); + } + + match request[0] { + DEPOSIT_REQUEST_TYPE => { + let list: VariableList< + DepositRequest, + MaxWithdrawalRequestsPerPayload, + > = VariableList::from_ssz_bytes(&request[1..])?; + acc.0.extend(Vec::::from(list)); + } + WITHDRAWAL_REQUEST_TYPE => { + let list: VariableList< + WithdrawalRequest, + MaxDepositRequestsPerPayload, + > = VariableList::from_ssz_bytes(&request[1..])?; + acc.1.extend(Vec::::from(list)); + } + CONSOLIDATION_REQUEST_TYPE => { + let list: VariableList< + ConsolidationRequest, + MaxConsolidationRequestsPerPayload, + > = VariableList::from_ssz_bytes(&request[1..])?; + acc.2.extend(Vec::::from(list)); + } + unknown => return Err(Error::UnknownRequestType(unknown)), + } + Ok(acc) + }, + )?; + + Ok(Self { deposits, withdrawals, consolidations }) + } + } + + /// Errors possible converting a [Requests] to [ExecutionRequestsV4] + #[derive(Debug)] + pub enum Error { + /// One of the Bytes is empty. + EmptyRequest, + /// Bytes prefix is not a known EIP-7685 request_type in Electra. + UnknownRequestType(u8), + /// Remaining bytes could not be decoded as SSZ requests_data. + SszDecodeError(ssz::DecodeError), + } + + impl From for Error { + fn from(value: DecodeError) -> Self { + Self::SszDecodeError(value) + } + } + + #[cfg(test)] + mod tests { + use super::*; + use alloy_primitives::Bytes; + use std::str::FromStr; + #[test] + fn test_from_requests() -> Result<(), Error> { + let original = Requests::new(vec![ + // Taken from: https://github.com/ensi321/execution-apis/blob/88c08d6104e9e8ae1d369c2b26c393a0df599e9a/src/engine/openrpc/methods/payload.yaml#L554-L556 + Bytes::from_str("0x0096a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef20100000000000000b1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9f000000000000000a5c85a60ba2905c215f6a12872e62b1ee037051364244043a5f639aa81b04a204c55e7cc851f29c7c183be253ea1510b001db70c485b6264692f26b8aeaab5b0c384180df8e2184a21a808a3ec8e86ca01000000000000009561731785b48cf1886412234531e4940064584463e96ac63a1a154320227e333fb51addc4a89b7e0d3f862d7c1fd4ea03bd8eb3d8806f1e7daf591cbbbb92b0beb74d13c01617f22c5026b4f9f9f294a8a7c32db895de3b01bee0132c9209e1f100000000000000").unwrap(), + Bytes::from_str("0x01a94f5374fce5edbc8e2a8697c15331677e6ebf0b85103a5617937691dfeeb89b86a80d5dc9e3c9d3a1a0e7ce311e26e0bb732eabaa47ffa288f0d54de28209a62a7d29d0000000000000000000000000000000000000000000000000000010f698daeed734da114470da559bd4b4c7259e1f7952555241dcbc90cf194a2ef676fc6005f3672fada2a3645edb297a75530100000000000000").unwrap(), + Bytes::from_str("0x02a94f5374fce5edbc8e2a8697c15331677e6ebf0b85103a5617937691dfeeb89b86a80d5dc9e3c9d3a1a0e7ce311e26e0bb732eabaa47ffa288f0d54de28209a62a7d29d098daeed734da114470da559bd4b4c7259e1f7952555241dcbc90cf194a2ef676fc6005f3672fada2a3645edb297a7553").unwrap(), + ]); + + let requests = ExecutionRequestsV4::try_from(&original)?; + assert_eq!(requests.deposits.len(), 2); + assert_eq!(requests.withdrawals.len(), 2); + assert_eq!(requests.consolidations.len(), 1); + + let round_trip: Requests = (&requests).into(); + assert_eq!(original, round_trip); + Ok(()) + } + } +} + +#[cfg(feature = "ssz")] +mod into_requests { + use super::*; + use alloy_eips::{ + eip6110::DEPOSIT_REQUEST_TYPE, eip7002::WITHDRAWAL_REQUEST_TYPE, + eip7251::CONSOLIDATION_REQUEST_TYPE, eip7685::Requests, + }; + use ssz::Encode; + + impl From<&ExecutionRequestsV4> for Requests { + fn from(val: &ExecutionRequestsV4) -> Self { + let deposit_bytes = val.deposits.as_ssz_bytes(); + let withdrawals_bytes = val.withdrawals.as_ssz_bytes(); + let consolidations_bytes = val.consolidations.as_ssz_bytes(); + + let mut requests = Self::default(); + requests.push_request_with_type(DEPOSIT_REQUEST_TYPE, deposit_bytes); + requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, withdrawals_bytes); + requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, consolidations_bytes); + requests + } + } +} + +#[cfg(feature = "ssz_types")] +pub use from_requests::Error as FromRequestsError; From 616de8b41e25989fafe248a8968cc9be9ddb3b7c Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Thu, 12 Dec 2024 15:28:56 -0800 Subject: [PATCH 03/12] fixup(serde): Fix serde conditionals on eip6110/7002 using cfg_eval --- crates/eips/Cargo.toml | 1 + crates/eips/src/eip6110.rs | 7 ++++--- crates/eips/src/eip7002.rs | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/eips/Cargo.toml b/crates/eips/Cargo.toml index 900af7c4762..e90cf66759b 100644 --- a/crates/eips/Cargo.toml +++ b/crates/eips/Cargo.toml @@ -49,6 +49,7 @@ ethereum_ssz = { workspace = true, optional = true } # arbitrary arbitrary = { workspace = true, features = ["derive"], optional = true } +cfg_eval = "0.1.2" [dev-dependencies] diff --git a/crates/eips/src/eip6110.rs b/crates/eips/src/eip6110.rs index 26ce395756b..cd54066cc80 100644 --- a/crates/eips/src/eip6110.rs +++ b/crates/eips/src/eip6110.rs @@ -14,8 +14,8 @@ pub const MAINNET_DEPOSIT_CONTRACT_ADDRESS: Address = pub const DEPOSIT_REQUEST_TYPE: u8 = 0x00; /// This structure maps onto the deposit object from [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110). -#[cfg_attr(feature = "serde", serde_as)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] @@ -25,12 +25,12 @@ pub struct DepositRequest { /// Withdrawal credentials pub withdrawal_credentials: B256, /// Amount of ether deposited in gwei - #[serde_as(as = "DisplayFromStr")] + #[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))] pub amount: u64, /// Deposit signature pub signature: FixedBytes<96>, /// Deposit index - #[serde_as(as = "DisplayFromStr")] + #[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))] pub index: u64, } @@ -40,6 +40,7 @@ mod tests { use alloy_primitives::hex; #[test] + #[cfg(feature = "serde")] fn test_serde_deposit_request() { // Sample JSON input representing a deposit request let json_data = r#"{"pubkey":"0x8e01a8f21bdc38991ada53ca86d6c78d874675a450a38431cc6aa0f12d5661e344784c56c8a211f7025224d1303ee801","withdrawal_credentials":"0x010000000000000000000000af6df504f08ddf582d604d2f0a593bc153c25dbd","amount":"18112749083033600","signature":"0xb65f3db79405544528d6d92040282f29171f4ff6e5abb2d59f9ee1f1254aced2a7000f87bc2684f543e913a7cc1007ea0e97289b349c553eecdf253cd3ef5814088ba3d4ac286f2634dac3d026d9a01e4c166dc75e249d626a0f1c180dab75ce","index":"13343631333247680512"}"#; diff --git a/crates/eips/src/eip7002.rs b/crates/eips/src/eip7002.rs index 8f881c5e172..6f6a83cf8d8 100644 --- a/crates/eips/src/eip7002.rs +++ b/crates/eips/src/eip7002.rs @@ -23,8 +23,8 @@ pub const WITHDRAWAL_REQUEST_TYPE: u8 = 0x01; /// Represents an execution layer triggerable withdrawal request. /// /// See [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002). -#[cfg_attr(feature = "serde", serde_as)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] @@ -34,7 +34,7 @@ pub struct WithdrawalRequest { /// Validator public key. pub validator_pubkey: FixedBytes<48>, /// Amount of withdrawn ether in gwei. - #[serde_as(as = "DisplayFromStr")] + #[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))] pub amount: u64, } @@ -45,6 +45,7 @@ mod tests { use core::str::FromStr; #[test] + #[cfg(feature = "serde")] fn test_serde_withdrawal_request() { // Sample JSON input representing a withdrawal request let json_data = r#"{ From a0cded7b7d56a30312fdce597216127875333592 Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Thu, 12 Dec 2024 17:28:07 -0800 Subject: [PATCH 04/12] fixup: feature ssz_types should require ssz. --- crates/rpc-types-beacon/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc-types-beacon/Cargo.toml b/crates/rpc-types-beacon/Cargo.toml index c74ad21cd81..63d96c59514 100644 --- a/crates/rpc-types-beacon/Cargo.toml +++ b/crates/rpc-types-beacon/Cargo.toml @@ -43,4 +43,4 @@ ssz = [ "dep:ethereum_ssz_derive", "alloy-rpc-types-engine/ssz", ] -ssz_types = ["dep:ssz_types"] +ssz_types = ["ssz", "dep:ssz_types"] From 7842dcccf7f3074b2128bde8f080f4398b41a0a0 Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Fri, 13 Dec 2024 08:39:32 -0800 Subject: [PATCH 05/12] fixup: deps cleanup - remove ssz_types, cfg_eval to workspace --- Cargo.toml | 1 + crates/eips/Cargo.toml | 5 +- crates/eips/src/eip6110.rs | 7 +- crates/eips/src/eip7002.rs | 7 +- crates/eips/src/eip7251.rs | 3 + crates/rpc-types-beacon/Cargo.toml | 2 - crates/rpc-types-beacon/src/requests.rs | 128 ++++++++++++------------ 7 files changed, 81 insertions(+), 72 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3bfe2e38647..28c8f38c1f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,6 +136,7 @@ serde_with = { version = "3", default-features = false, features = ["macros"] } auto_impl = "1.2" base64 = "0.22" bimap = "0.6" +cfg_eval = "0.1.2" cfg-if = "1" derive_more = { version = "1.0.0", default-features = false } home = "0.5" diff --git a/crates/eips/Cargo.toml b/crates/eips/Cargo.toml index e90cf66759b..53bfd95bfa8 100644 --- a/crates/eips/Cargo.toml +++ b/crates/eips/Cargo.toml @@ -30,6 +30,7 @@ alloy-rlp = { workspace = true, features = ["derive"] } alloy-serde = { workspace = true, optional = true } serde = { workspace = true, optional = true } serde_with = { workspace = true, optional = true } +cfg_eval = { workspace = true, optional = true } # kzg c-kzg = { workspace = true, optional = true } @@ -49,8 +50,6 @@ ethereum_ssz = { workspace = true, optional = true } # arbitrary arbitrary = { workspace = true, features = ["derive"], optional = true } -cfg_eval = "0.1.2" - [dev-dependencies] alloy-primitives = { workspace = true, features = [ @@ -67,7 +66,7 @@ default = ["std", "kzg-sidecar"] std = ["alloy-primitives/std", "alloy-rlp/std", "serde?/std", "c-kzg?/std", "once_cell?/std"] serde = ["dep:alloy-serde", "dep:serde", "dep:serde_with", "alloy-primitives/serde", -"c-kzg?/serde", "alloy-eip2930/serde", "alloy-eip7702/serde"] +"c-kzg?/serde", "alloy-eip2930/serde", "alloy-eip7702/serde", "dep:cfg_eval"] serde-bincode-compat = ["alloy-eip7702/serde-bincode-compat"] kzg = ["kzg-sidecar", "sha2", "dep:c-kzg", "dep:once_cell"] kzg-sidecar = ["sha2"] diff --git a/crates/eips/src/eip6110.rs b/crates/eips/src/eip6110.rs index cd54066cc80..22db4b6bf60 100644 --- a/crates/eips/src/eip6110.rs +++ b/crates/eips/src/eip6110.rs @@ -4,6 +4,8 @@ use alloy_primitives::{address, Address, FixedBytes, B256}; #[cfg(feature = "serde")] +use cfg_eval::cfg_eval; +#[cfg(feature = "serde")] use serde_with::{serde_as, DisplayFromStr}; /// Mainnet deposit contract address. @@ -13,9 +15,12 @@ pub const MAINNET_DEPOSIT_CONTRACT_ADDRESS: Address = /// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for deposit requests. pub const DEPOSIT_REQUEST_TYPE: u8 = 0x00; +/// The [EIP-6110 Consensus Specs](https://github.com/ethereum/consensus-specs/blob/2660af05390aa61f06142e1c6311a3a3c633f720/specs/_features/eip6110/beacon-chain.md#constants) defined maximum payload size. +pub const MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: usize = 8192; + /// This structure maps onto the deposit object from [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110). #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", cfg_eval, serde_as)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] diff --git a/crates/eips/src/eip7002.rs b/crates/eips/src/eip7002.rs index 6f6a83cf8d8..85795df7d87 100644 --- a/crates/eips/src/eip7002.rs +++ b/crates/eips/src/eip7002.rs @@ -4,6 +4,8 @@ use alloy_primitives::{address, bytes, Address, Bytes, FixedBytes}; #[cfg(feature = "serde")] +use cfg_eval::cfg_eval; +#[cfg(feature = "serde")] use serde_with::{serde_as, DisplayFromStr}; /// The caller to be used when calling the EIP-7002 withdrawal requests contract at the end of the @@ -20,11 +22,14 @@ pub static WITHDRAWAL_REQUEST_PREDEPLOY_CODE: Bytes = bytes!(" 3373fffffffffff /// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for withdrawal requests. pub const WITHDRAWAL_REQUEST_TYPE: u8 = 0x01; +/// The [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) defined maximum withdrawal requests per block. +pub const MAX_WITHDRAWAL_REQUESTS_PER_BLOCK: usize = 16; + /// Represents an execution layer triggerable withdrawal request. /// /// See [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002). #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", cfg_eval, serde_as)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] diff --git a/crates/eips/src/eip7251.rs b/crates/eips/src/eip7251.rs index 158f59bd0a5..8a4e701b242 100644 --- a/crates/eips/src/eip7251.rs +++ b/crates/eips/src/eip7251.rs @@ -15,6 +15,9 @@ pub static CONSOLIDATION_REQUEST_PREDEPLOY_CODE: Bytes = bytes!("3373fffffffffff /// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for consolidation requests. pub const CONSOLIDATION_REQUEST_TYPE: u8 = 0x02; +/// The [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) defined maximum number of consolidation requests per block. +pub const MAX_CONSOLIDATION_REQUESTS_PER_BLOCK: usize = 2; + /// This structure maps onto the consolidation request object from [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251). #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/rpc-types-beacon/Cargo.toml b/crates/rpc-types-beacon/Cargo.toml index 63d96c59514..9e9c60549f6 100644 --- a/crates/rpc-types-beacon/Cargo.toml +++ b/crates/rpc-types-beacon/Cargo.toml @@ -31,7 +31,6 @@ serde.workspace = true serde_with = { workspace = true, features = ["alloc"] } thiserror.workspace = true -ssz_types = { version = "0.9.0", optional = true } [dev-dependencies] serde_json.workspace = true @@ -43,4 +42,3 @@ ssz = [ "dep:ethereum_ssz_derive", "alloy-rpc-types-engine/ssz", ] -ssz_types = ["ssz", "dep:ssz_types"] diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs index f44b68f9906..7d6a8d5741e 100644 --- a/crates/rpc-types-beacon/src/requests.rs +++ b/crates/rpc-types-beacon/src/requests.rs @@ -17,59 +17,68 @@ pub struct ExecutionRequestsV4 { pub consolidations: Vec, } -#[cfg(feature = "ssz_types")] -mod from_requests { - use crate::requests::ExecutionRequestsV4; +#[cfg(feature = "ssz")] +pub use ssz_requests_conversions::TryFromRequestsError; + +#[cfg(feature = "ssz")] +mod ssz_requests_conversions { + use super::*; use alloy_eips::{ - eip6110::DepositRequest, eip7002::WithdrawalRequest, eip7251::ConsolidationRequest, + eip6110::{DepositRequest, DEPOSIT_REQUEST_TYPE, MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD}, + eip7002::{WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_BLOCK, WITHDRAWAL_REQUEST_TYPE}, + eip7251::{ + ConsolidationRequest, CONSOLIDATION_REQUEST_TYPE, MAX_CONSOLIDATION_REQUESTS_PER_BLOCK, + }, eip7685::Requests, }; - use ssz::DecodeError; + use ssz::{Decode, DecodeError, Encode}; impl TryFrom<&Requests> for ExecutionRequestsV4 { - type Error = Error; + type Error = TryFromRequestsError; fn try_from(value: &Requests) -> Result { - use alloy_eips::{ - eip6110::DEPOSIT_REQUEST_TYPE, eip7002::WITHDRAWAL_REQUEST_TYPE, - eip7251::CONSOLIDATION_REQUEST_TYPE, - }; - use ssz::Decode; - use ssz_types::{typenum, VariableList}; - - type MaxConsolidationRequestsPerPayload = typenum::U1; - type MaxDepositRequestsPerPayload = typenum::U8192; - type MaxWithdrawalRequestsPerPayload = typenum::U16; - let (deposits, withdrawals, consolidations) = value.iter().try_fold( (Vec::new(), Vec::new(), Vec::new()), |mut acc, request| { if request.is_empty() { - return Err(Error::EmptyRequest); + return Err(TryFromRequestsError::EmptyRequest); } match request[0] { DEPOSIT_REQUEST_TYPE => { - let list: VariableList< - DepositRequest, - MaxWithdrawalRequestsPerPayload, - > = VariableList::from_ssz_bytes(&request[1..])?; - acc.0.extend(Vec::::from(list)); + let list: Vec = Vec::from_ssz_bytes(&request[1..])?; + let size = list.len(); + if size > MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD { + return Err(TryFromRequestsError::RequestPayloadSizeExceeded( + DEPOSIT_REQUEST_TYPE, + size, + )); + } + acc.0.extend(list); } WITHDRAWAL_REQUEST_TYPE => { - let list: VariableList< - WithdrawalRequest, - MaxDepositRequestsPerPayload, - > = VariableList::from_ssz_bytes(&request[1..])?; - acc.1.extend(Vec::::from(list)); + let list: Vec = Vec::from_ssz_bytes(&request[1..])?; + let size = list.len(); + if size > MAX_WITHDRAWAL_REQUESTS_PER_BLOCK { + return Err(TryFromRequestsError::RequestPayloadSizeExceeded( + WITHDRAWAL_REQUEST_TYPE, + size, + )); + } + acc.1.extend(list); } CONSOLIDATION_REQUEST_TYPE => { - let list: VariableList< - ConsolidationRequest, - MaxConsolidationRequestsPerPayload, - > = VariableList::from_ssz_bytes(&request[1..])?; - acc.2.extend(Vec::::from(list)); + let list: Vec = + Vec::from_ssz_bytes(&request[1..])?; + let size = list.len(); + if size > MAX_CONSOLIDATION_REQUESTS_PER_BLOCK { + return Err(TryFromRequestsError::RequestPayloadSizeExceeded( + CONSOLIDATION_REQUEST_TYPE, + size, + )); + } + acc.2.extend(list); } - unknown => return Err(Error::UnknownRequestType(unknown)), + unknown => return Err(TryFromRequestsError::UnknownRequestType(unknown)), } Ok(acc) }, @@ -81,28 +90,44 @@ mod from_requests { /// Errors possible converting a [Requests] to [ExecutionRequestsV4] #[derive(Debug)] - pub enum Error { + pub enum TryFromRequestsError { /// One of the Bytes is empty. EmptyRequest, /// Bytes prefix is not a known EIP-7685 request_type in Electra. UnknownRequestType(u8), /// Remaining bytes could not be decoded as SSZ requests_data. - SszDecodeError(ssz::DecodeError), + SszDecodeError(DecodeError), + /// Requests of request_type exceeds Electra size limits + RequestPayloadSizeExceeded(u8, usize), } - impl From for Error { + impl From for TryFromRequestsError { fn from(value: DecodeError) -> Self { Self::SszDecodeError(value) } } + impl From<&ExecutionRequestsV4> for Requests { + fn from(val: &ExecutionRequestsV4) -> Self { + let deposit_bytes = val.deposits.as_ssz_bytes(); + let withdrawals_bytes = val.withdrawals.as_ssz_bytes(); + let consolidations_bytes = val.consolidations.as_ssz_bytes(); + + let mut requests = Self::default(); + requests.push_request_with_type(DEPOSIT_REQUEST_TYPE, deposit_bytes); + requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, withdrawals_bytes); + requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, consolidations_bytes); + requests + } + } + #[cfg(test)] mod tests { use super::*; use alloy_primitives::Bytes; use std::str::FromStr; #[test] - fn test_from_requests() -> Result<(), Error> { + fn test_from_requests() -> Result<(), TryFromRequestsError> { let original = Requests::new(vec![ // Taken from: https://github.com/ensi321/execution-apis/blob/88c08d6104e9e8ae1d369c2b26c393a0df599e9a/src/engine/openrpc/methods/payload.yaml#L554-L556 Bytes::from_str("0x0096a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef20100000000000000b1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9f000000000000000a5c85a60ba2905c215f6a12872e62b1ee037051364244043a5f639aa81b04a204c55e7cc851f29c7c183be253ea1510b001db70c485b6264692f26b8aeaab5b0c384180df8e2184a21a808a3ec8e86ca01000000000000009561731785b48cf1886412234531e4940064584463e96ac63a1a154320227e333fb51addc4a89b7e0d3f862d7c1fd4ea03bd8eb3d8806f1e7daf591cbbbb92b0beb74d13c01617f22c5026b4f9f9f294a8a7c32db895de3b01bee0132c9209e1f100000000000000").unwrap(), @@ -121,30 +146,3 @@ mod from_requests { } } } - -#[cfg(feature = "ssz")] -mod into_requests { - use super::*; - use alloy_eips::{ - eip6110::DEPOSIT_REQUEST_TYPE, eip7002::WITHDRAWAL_REQUEST_TYPE, - eip7251::CONSOLIDATION_REQUEST_TYPE, eip7685::Requests, - }; - use ssz::Encode; - - impl From<&ExecutionRequestsV4> for Requests { - fn from(val: &ExecutionRequestsV4) -> Self { - let deposit_bytes = val.deposits.as_ssz_bytes(); - let withdrawals_bytes = val.withdrawals.as_ssz_bytes(); - let consolidations_bytes = val.consolidations.as_ssz_bytes(); - - let mut requests = Self::default(); - requests.push_request_with_type(DEPOSIT_REQUEST_TYPE, deposit_bytes); - requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, withdrawals_bytes); - requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, consolidations_bytes); - requests - } - } -} - -#[cfg(feature = "ssz_types")] -pub use from_requests::Error as FromRequestsError; From b3212483f94fa440a5b5b84163851ed5bcb2633b Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Fri, 13 Dec 2024 12:52:22 -0800 Subject: [PATCH 06/12] feat(serde): replace serde_with::DisplayFromStr with alloy_serde::ssz::json::uint --- crates/eips/Cargo.toml | 6 +-- crates/eips/src/eip6110.rs | 9 +---- crates/eips/src/eip7002.rs | 10 +---- crates/rpc-types-beacon/src/requests.rs | 9 ++++- crates/serde/src/lib.rs | 3 ++ crates/serde/src/ssz.rs | 50 +++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 crates/serde/src/ssz.rs diff --git a/crates/eips/Cargo.toml b/crates/eips/Cargo.toml index 53bfd95bfa8..b57fd7ae1de 100644 --- a/crates/eips/Cargo.toml +++ b/crates/eips/Cargo.toml @@ -29,8 +29,6 @@ alloy-rlp = { workspace = true, features = ["derive"] } # serde alloy-serde = { workspace = true, optional = true } serde = { workspace = true, optional = true } -serde_with = { workspace = true, optional = true } -cfg_eval = { workspace = true, optional = true } # kzg c-kzg = { workspace = true, optional = true } @@ -65,8 +63,8 @@ rand.workspace = true default = ["std", "kzg-sidecar"] std = ["alloy-primitives/std", "alloy-rlp/std", "serde?/std", "c-kzg?/std", "once_cell?/std"] -serde = ["dep:alloy-serde", "dep:serde", "dep:serde_with", "alloy-primitives/serde", -"c-kzg?/serde", "alloy-eip2930/serde", "alloy-eip7702/serde", "dep:cfg_eval"] +serde = ["dep:alloy-serde", "dep:serde", "alloy-primitives/serde", +"c-kzg?/serde", "alloy-eip2930/serde", "alloy-eip7702/serde"] serde-bincode-compat = ["alloy-eip7702/serde-bincode-compat"] kzg = ["kzg-sidecar", "sha2", "dep:c-kzg", "dep:once_cell"] kzg-sidecar = ["sha2"] diff --git a/crates/eips/src/eip6110.rs b/crates/eips/src/eip6110.rs index 22db4b6bf60..9815873c47d 100644 --- a/crates/eips/src/eip6110.rs +++ b/crates/eips/src/eip6110.rs @@ -3,10 +3,6 @@ //! See also [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110): Supply validator deposits on chain use alloy_primitives::{address, Address, FixedBytes, B256}; -#[cfg(feature = "serde")] -use cfg_eval::cfg_eval; -#[cfg(feature = "serde")] -use serde_with::{serde_as, DisplayFromStr}; /// Mainnet deposit contract address. pub const MAINNET_DEPOSIT_CONTRACT_ADDRESS: Address = @@ -20,7 +16,6 @@ pub const MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: usize = 8192; /// This structure maps onto the deposit object from [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110). #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -#[cfg_attr(feature = "serde", cfg_eval, serde_as)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] @@ -30,12 +25,12 @@ pub struct DepositRequest { /// Withdrawal credentials pub withdrawal_credentials: B256, /// Amount of ether deposited in gwei - #[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::ssz::json::uint"))] pub amount: u64, /// Deposit signature pub signature: FixedBytes<96>, /// Deposit index - #[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::ssz::json::uint"))] pub index: u64, } diff --git a/crates/eips/src/eip7002.rs b/crates/eips/src/eip7002.rs index 85795df7d87..33fb71657d2 100644 --- a/crates/eips/src/eip7002.rs +++ b/crates/eips/src/eip7002.rs @@ -3,10 +3,6 @@ //! See also [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002): Execution layer triggerable withdrawals use alloy_primitives::{address, bytes, Address, Bytes, FixedBytes}; -#[cfg(feature = "serde")] -use cfg_eval::cfg_eval; -#[cfg(feature = "serde")] -use serde_with::{serde_as, DisplayFromStr}; /// The caller to be used when calling the EIP-7002 withdrawal requests contract at the end of the /// block. @@ -29,7 +25,6 @@ pub const MAX_WITHDRAWAL_REQUESTS_PER_BLOCK: usize = 16; /// /// See [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002). #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -#[cfg_attr(feature = "serde", cfg_eval, serde_as)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] @@ -39,7 +34,7 @@ pub struct WithdrawalRequest { /// Validator public key. pub validator_pubkey: FixedBytes<48>, /// Amount of withdrawn ether in gwei. - #[cfg_attr(feature = "serde", serde_as(as = "DisplayFromStr"))] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::ssz::json::uint"))] pub amount: u64, } @@ -47,7 +42,6 @@ pub struct WithdrawalRequest { mod tests { use super::*; use alloy_primitives::hex; - use core::str::FromStr; #[test] #[cfg(feature = "serde")] @@ -66,7 +60,7 @@ mod tests { // Verify the deserialized content assert_eq!( withdrawal_request.source_address, - Address::from_str("0xAE0E8770147AaA6828a0D6f642504663F10F7d1E").unwrap() + address!("AE0E8770147AaA6828a0D6f642504663F10F7d1E") ); assert_eq!( withdrawal_request.validator_pubkey, diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs index 7d6a8d5741e..5be23e46cef 100644 --- a/crates/rpc-types-beacon/src/requests.rs +++ b/crates/rpc-types-beacon/src/requests.rs @@ -3,11 +3,11 @@ use alloy_eips::{ }; use serde::{Deserialize, Serialize}; +/// An Electra-compatible execution requests payload. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(rename_all = "snake_case")] #[cfg_attr(feature = "ssz", derive(ssz_derive::Decode, ssz_derive::Encode))] -/// An Electra-compatible execution requests payload. pub struct ExecutionRequestsV4 { /// The requested deposits. pub deposits: Vec, @@ -121,6 +121,13 @@ mod ssz_requests_conversions { } } + impl ExecutionRequestsV4 { + /// Convert the [ExecutionRequestsV4] into a [Requests]. + pub fn to_requests(&self) -> Requests { + self.into() + } + } + #[cfg(test)] mod tests { use super::*; diff --git a/crates/serde/src/lib.rs b/crates/serde/src/lib.rs index 603300801fa..c85eee0bd52 100644 --- a/crates/serde/src/lib.rs +++ b/crates/serde/src/lib.rs @@ -31,6 +31,9 @@ pub mod ttd; pub use ttd::*; mod other; + +pub mod ssz; + pub use other::{OtherFields, WithOtherFields}; /// Serialize a byte vec as a hex string _without_ the "0x" prefix. diff --git a/crates/serde/src/ssz.rs b/crates/serde/src/ssz.rs new file mode 100644 index 00000000000..a47aeeb8cb7 --- /dev/null +++ b/crates/serde/src/ssz.rs @@ -0,0 +1,50 @@ +//! Serde functions for encoding SSZ primitives. + +/// Serde functions for encoding SSZ primitives using the "canonical JSON mapping" described +/// in the consensus-specs here: ,https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#json-mapping> +pub mod json { + /// Serde functions for (de)serializing SSZ `uintN` types as quoted decimal strings. + /// + /// # Example + /// ``` + /// use alloy_serde; + /// use serde::{Deserialize, Serialize}; + /// + /// #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] + /// pub struct Container { + /// #[serde(with = "alloy_serde::ssz::json::uint")] + /// value: u64, + /// } + /// + /// let val = Container { value: 18112749083033600 }; + /// let s = serde_json::to_string(&val).unwrap(); + /// assert_eq!(s, "{\"value\":\"18112749083033600\"}"); + /// + /// let deserialized: Container = serde_json::from_str(&s).unwrap(); + /// assert_eq!(val, deserialized); + /// ``` + pub mod uint { + use crate::alloc::string::{String, ToString}; + use core::{fmt, str::FromStr}; + use serde::{Deserialize, Deserializer, Serializer}; + + /// Serialize a `uintN` compatible type [T] as a decimal quoted string. + pub fn serialize(value: &T, serializer: S) -> Result + where + T: fmt::Display, + S: Serializer, + { + serializer.collect_str(&value.to_string()) + } + + /// Deserialize a decimal quoted string to a `uintN` compatible type [T]. + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: FromStr, + T::Err: fmt::Display, + { + String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom) + } + } +} From bab4a857f858b029d87f6f5e1b2df2de4725f54f Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Fri, 13 Dec 2024 13:02:55 -0800 Subject: [PATCH 07/12] fixup: doc comment typos --- crates/serde/src/ssz.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/serde/src/ssz.rs b/crates/serde/src/ssz.rs index a47aeeb8cb7..2d9d3080d06 100644 --- a/crates/serde/src/ssz.rs +++ b/crates/serde/src/ssz.rs @@ -1,7 +1,7 @@ //! Serde functions for encoding SSZ primitives. /// Serde functions for encoding SSZ primitives using the "canonical JSON mapping" described -/// in the consensus-specs here: ,https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#json-mapping> +/// in the consensus-specs here: pub mod json { /// Serde functions for (de)serializing SSZ `uintN` types as quoted decimal strings. /// @@ -28,7 +28,7 @@ pub mod json { use core::{fmt, str::FromStr}; use serde::{Deserialize, Deserializer, Serializer}; - /// Serialize a `uintN` compatible type [T] as a decimal quoted string. + /// Serialize a `uintN` compatible type `T` as a decimal quoted string. pub fn serialize(value: &T, serializer: S) -> Result where T: fmt::Display, @@ -37,7 +37,7 @@ pub mod json { serializer.collect_str(&value.to_string()) } - /// Deserialize a decimal quoted string to a `uintN` compatible type [T]. + /// Deserialize a decimal quoted string to a `uintN` compatible type `T`. pub fn deserialize<'de, T, D>(deserializer: D) -> Result where D: Deserializer<'de>, From 03ff69eb92352848df1fe6394e997f5d243cb250 Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Fri, 13 Dec 2024 13:05:46 -0800 Subject: [PATCH 08/12] fixup: derive thiserror::Error on TryFromRequestsError --- crates/rpc-types-beacon/src/requests.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs index 5be23e46cef..dcd04a4fcec 100644 --- a/crates/rpc-types-beacon/src/requests.rs +++ b/crates/rpc-types-beacon/src/requests.rs @@ -23,6 +23,7 @@ pub use ssz_requests_conversions::TryFromRequestsError; #[cfg(feature = "ssz")] mod ssz_requests_conversions { use super::*; + use crate::requests::TryFromRequestsError::SszDecodeError; use alloy_eips::{ eip6110::{DepositRequest, DEPOSIT_REQUEST_TYPE, MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD}, eip7002::{WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_BLOCK, WITHDRAWAL_REQUEST_TYPE}, @@ -45,7 +46,8 @@ mod ssz_requests_conversions { match request[0] { DEPOSIT_REQUEST_TYPE => { - let list: Vec = Vec::from_ssz_bytes(&request[1..])?; + let list: Vec = Vec::from_ssz_bytes(&request[1..]) + .map_err(|e| SszDecodeError(DEPOSIT_REQUEST_TYPE, e))?; let size = list.len(); if size > MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD { return Err(TryFromRequestsError::RequestPayloadSizeExceeded( @@ -56,7 +58,9 @@ mod ssz_requests_conversions { acc.0.extend(list); } WITHDRAWAL_REQUEST_TYPE => { - let list: Vec = Vec::from_ssz_bytes(&request[1..])?; + let list: Vec = + Vec::from_ssz_bytes(&request[1..]) + .map_err(|e| SszDecodeError(WITHDRAWAL_REQUEST_TYPE, e))?; let size = list.len(); if size > MAX_WITHDRAWAL_REQUESTS_PER_BLOCK { return Err(TryFromRequestsError::RequestPayloadSizeExceeded( @@ -68,7 +72,8 @@ mod ssz_requests_conversions { } CONSOLIDATION_REQUEST_TYPE => { let list: Vec = - Vec::from_ssz_bytes(&request[1..])?; + Vec::from_ssz_bytes(&request[1..]) + .map_err(|e| SszDecodeError(CONSOLIDATION_REQUEST_TYPE, e))?; let size = list.len(); if size > MAX_CONSOLIDATION_REQUESTS_PER_BLOCK { return Err(TryFromRequestsError::RequestPayloadSizeExceeded( @@ -89,24 +94,22 @@ mod ssz_requests_conversions { } /// Errors possible converting a [Requests] to [ExecutionRequestsV4] - #[derive(Debug)] + #[derive(Debug, thiserror::Error)] pub enum TryFromRequestsError { /// One of the Bytes is empty. + #[error("empty bytes in requests body")] EmptyRequest, /// Bytes prefix is not a known EIP-7685 request_type in Electra. + #[error("unknown request_type prefix: {0}")] UnknownRequestType(u8), /// Remaining bytes could not be decoded as SSZ requests_data. - SszDecodeError(DecodeError), + #[error("ssz error decoding requests_type: {0}")] + SszDecodeError(u8, DecodeError), /// Requests of request_type exceeds Electra size limits + #[error("requests_data payload for request_type {0} exceeds Electra size limit {1}")] RequestPayloadSizeExceeded(u8, usize), } - impl From for TryFromRequestsError { - fn from(value: DecodeError) -> Self { - Self::SszDecodeError(value) - } - } - impl From<&ExecutionRequestsV4> for Requests { fn from(val: &ExecutionRequestsV4) -> Self { let deposit_bytes = val.deposits.as_ssz_bytes(); From 5bf7dc8175de4cb9eaa4866726bab074347988e7 Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Fri, 13 Dec 2024 13:25:54 -0800 Subject: [PATCH 09/12] fixup: use Requests::with_capacity(). --- crates/rpc-types-beacon/src/requests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs index dcd04a4fcec..4a7f52d1178 100644 --- a/crates/rpc-types-beacon/src/requests.rs +++ b/crates/rpc-types-beacon/src/requests.rs @@ -116,7 +116,7 @@ mod ssz_requests_conversions { let withdrawals_bytes = val.withdrawals.as_ssz_bytes(); let consolidations_bytes = val.consolidations.as_ssz_bytes(); - let mut requests = Self::default(); + let mut requests = Self::with_capacity(3); requests.push_request_with_type(DEPOSIT_REQUEST_TYPE, deposit_bytes); requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, withdrawals_bytes); requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, consolidations_bytes); From 14cbaadc897d57171a5918ae5f6af7c73fd23b5a Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Fri, 13 Dec 2024 13:33:35 -0800 Subject: [PATCH 10/12] fixup: cleanup cargo changes --- Cargo.toml | 1 - crates/eips/Cargo.toml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 28c8f38c1f7..3bfe2e38647 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,7 +136,6 @@ serde_with = { version = "3", default-features = false, features = ["macros"] } auto_impl = "1.2" base64 = "0.22" bimap = "0.6" -cfg_eval = "0.1.2" cfg-if = "1" derive_more = { version = "1.0.0", default-features = false } home = "0.5" diff --git a/crates/eips/Cargo.toml b/crates/eips/Cargo.toml index b57fd7ae1de..6c55582a7d8 100644 --- a/crates/eips/Cargo.toml +++ b/crates/eips/Cargo.toml @@ -49,6 +49,7 @@ ethereum_ssz = { workspace = true, optional = true } # arbitrary arbitrary = { workspace = true, features = ["derive"], optional = true } + [dev-dependencies] alloy-primitives = { workspace = true, features = [ "rand", From eb43d13c6068d674be2a4c608c4ffd35609013dd Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Fri, 13 Dec 2024 14:05:49 -0800 Subject: [PATCH 11/12] fixup: More efficient/readable try_from implementation. --- crates/rpc-types-beacon/src/requests.rs | 103 +++++++++++++++--------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs index 4a7f52d1178..ba9f74fe3c9 100644 --- a/crates/rpc-types-beacon/src/requests.rs +++ b/crates/rpc-types-beacon/src/requests.rs @@ -36,60 +36,83 @@ mod ssz_requests_conversions { impl TryFrom<&Requests> for ExecutionRequestsV4 { type Error = TryFromRequestsError; + fn try_from(value: &Requests) -> Result { - let (deposits, withdrawals, consolidations) = value.iter().try_fold( - (Vec::new(), Vec::new(), Vec::new()), - |mut acc, request| { + #[derive(Default)] + struct RequestAccumulator { + deposits: Vec, + withdrawals: Vec, + consolidations: Vec, + } + + impl RequestAccumulator { + fn parse_request_payload( + payload: &[u8], + max_size: usize, + request_type: u8, + ) -> Result, TryFromRequestsError> + where + Vec: Decode + Encode, + { + let list: Vec = Vec::from_ssz_bytes(payload) + .map_err(|e| SszDecodeError(request_type, e))?; + + if list.len() > max_size { + return Err(TryFromRequestsError::RequestPayloadSizeExceeded( + request_type, + list.len(), + )); + } + + Ok(list) + } + + fn accumulate(mut self, request: &[u8]) -> Result { if request.is_empty() { return Err(TryFromRequestsError::EmptyRequest); } - match request[0] { + let (request_type, payload) = + request.split_first().expect("already checked for empty"); + + match *request_type { DEPOSIT_REQUEST_TYPE => { - let list: Vec = Vec::from_ssz_bytes(&request[1..]) - .map_err(|e| SszDecodeError(DEPOSIT_REQUEST_TYPE, e))?; - let size = list.len(); - if size > MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD { - return Err(TryFromRequestsError::RequestPayloadSizeExceeded( - DEPOSIT_REQUEST_TYPE, - size, - )); - } - acc.0.extend(list); + self.deposits = Self::parse_request_payload( + payload, + MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD, + DEPOSIT_REQUEST_TYPE, + )?; } WITHDRAWAL_REQUEST_TYPE => { - let list: Vec = - Vec::from_ssz_bytes(&request[1..]) - .map_err(|e| SszDecodeError(WITHDRAWAL_REQUEST_TYPE, e))?; - let size = list.len(); - if size > MAX_WITHDRAWAL_REQUESTS_PER_BLOCK { - return Err(TryFromRequestsError::RequestPayloadSizeExceeded( - WITHDRAWAL_REQUEST_TYPE, - size, - )); - } - acc.1.extend(list); + self.withdrawals = Self::parse_request_payload( + payload, + MAX_WITHDRAWAL_REQUESTS_PER_BLOCK, + WITHDRAWAL_REQUEST_TYPE, + )?; } CONSOLIDATION_REQUEST_TYPE => { - let list: Vec = - Vec::from_ssz_bytes(&request[1..]) - .map_err(|e| SszDecodeError(CONSOLIDATION_REQUEST_TYPE, e))?; - let size = list.len(); - if size > MAX_CONSOLIDATION_REQUESTS_PER_BLOCK { - return Err(TryFromRequestsError::RequestPayloadSizeExceeded( - CONSOLIDATION_REQUEST_TYPE, - size, - )); - } - acc.2.extend(list); + self.consolidations = Self::parse_request_payload( + payload, + MAX_CONSOLIDATION_REQUESTS_PER_BLOCK, + CONSOLIDATION_REQUEST_TYPE, + )?; } unknown => return Err(TryFromRequestsError::UnknownRequestType(unknown)), } - Ok(acc) - }, - )?; - Ok(Self { deposits, withdrawals, consolidations }) + Ok(self) + } + } + + let accumulator = value + .iter() + .try_fold(RequestAccumulator::default(), |acc, request| acc.accumulate(request))?; + + Ok(Self { + deposits: accumulator.deposits, + withdrawals: accumulator.withdrawals, + consolidations: accumulator.consolidations, + }) } } From daf77039c827bb5491f570fb7b64158339b45682 Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Sat, 14 Dec 2024 13:37:33 -0800 Subject: [PATCH 12/12] fixup: rename to serde::displayfromstr, other nits. --- crates/eips/src/eip6110.rs | 4 +- crates/eips/src/eip7002.rs | 2 +- crates/rpc-types-beacon/src/requests.rs | 17 +++++---- crates/serde/src/displayfromstr.rs | 46 +++++++++++++++++++++++ crates/serde/src/lib.rs | 4 +- crates/serde/src/ssz.rs | 50 ------------------------- 6 files changed, 61 insertions(+), 62 deletions(-) create mode 100644 crates/serde/src/displayfromstr.rs delete mode 100644 crates/serde/src/ssz.rs diff --git a/crates/eips/src/eip6110.rs b/crates/eips/src/eip6110.rs index 9815873c47d..256409d4699 100644 --- a/crates/eips/src/eip6110.rs +++ b/crates/eips/src/eip6110.rs @@ -25,12 +25,12 @@ pub struct DepositRequest { /// Withdrawal credentials pub withdrawal_credentials: B256, /// Amount of ether deposited in gwei - #[cfg_attr(feature = "serde", serde(with = "alloy_serde::ssz::json::uint"))] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::displayfromstr"))] pub amount: u64, /// Deposit signature pub signature: FixedBytes<96>, /// Deposit index - #[cfg_attr(feature = "serde", serde(with = "alloy_serde::ssz::json::uint"))] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::displayfromstr"))] pub index: u64, } diff --git a/crates/eips/src/eip7002.rs b/crates/eips/src/eip7002.rs index 33fb71657d2..4307866cc81 100644 --- a/crates/eips/src/eip7002.rs +++ b/crates/eips/src/eip7002.rs @@ -34,7 +34,7 @@ pub struct WithdrawalRequest { /// Validator public key. pub validator_pubkey: FixedBytes<48>, /// Amount of withdrawn ether in gwei. - #[cfg_attr(feature = "serde", serde(with = "alloy_serde::ssz::json::uint"))] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::displayfromstr"))] pub amount: u64, } diff --git a/crates/rpc-types-beacon/src/requests.rs b/crates/rpc-types-beacon/src/requests.rs index ba9f74fe3c9..6e93bf2a36b 100644 --- a/crates/rpc-types-beacon/src/requests.rs +++ b/crates/rpc-types-beacon/src/requests.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "ssz")] +use alloy_eips::eip7685::Requests; use alloy_eips::{ eip6110::DepositRequest, eip7002::WithdrawalRequest, eip7251::ConsolidationRequest, }; @@ -17,6 +19,14 @@ pub struct ExecutionRequestsV4 { pub consolidations: Vec, } +impl ExecutionRequestsV4 { + /// Convert the [ExecutionRequestsV4] into a [Requests]. + #[cfg(feature = "ssz")] + pub fn to_requests(&self) -> Requests { + self.into() + } +} + #[cfg(feature = "ssz")] pub use ssz_requests_conversions::TryFromRequestsError; @@ -147,13 +157,6 @@ mod ssz_requests_conversions { } } - impl ExecutionRequestsV4 { - /// Convert the [ExecutionRequestsV4] into a [Requests]. - pub fn to_requests(&self) -> Requests { - self.into() - } - } - #[cfg(test)] mod tests { use super::*; diff --git a/crates/serde/src/displayfromstr.rs b/crates/serde/src/displayfromstr.rs new file mode 100644 index 00000000000..6c7dd3b7dee --- /dev/null +++ b/crates/serde/src/displayfromstr.rs @@ -0,0 +1,46 @@ +//! Serde functions for (de)serializing using FromStr and Display +//! +//! Useful for example in encoding SSZ `uintN` primitives using the "canonical JSON mapping" +//! described in the consensus-specs here: +//! +//! # Example +//! ``` +//! use alloy_serde; +//! use serde::{Deserialize, Serialize}; +//! +//! #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +//! pub struct Container { +//! #[serde(with = "alloy_serde::displayfromstr")] +//! value: u64, +//! } +//! +//! let val = Container { value: 18112749083033600 }; +//! let s = serde_json::to_string(&val).unwrap(); +//! assert_eq!(s, "{\"value\":\"18112749083033600\"}"); +//! +//! let deserialized: Container = serde_json::from_str(&s).unwrap(); +//! assert_eq!(val, deserialized); +//! ``` + +use crate::alloc::string::{String, ToString}; +use core::{fmt, str::FromStr}; +use serde::{Deserialize, Deserializer, Serializer}; + +/// Serialize a type `T` that implements [fmt::Display] as a quoted string. +pub fn serialize(value: &T, serializer: S) -> Result +where + T: fmt::Display, + S: Serializer, +{ + serializer.collect_str(&value.to_string()) +} + +/// Deserialize a quoted string to a type `T` using [FromStr]. +pub fn deserialize<'de, T, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: FromStr, + T::Err: fmt::Display, +{ + String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom) +} diff --git a/crates/serde/src/lib.rs b/crates/serde/src/lib.rs index c85eee0bd52..3ca005adc11 100644 --- a/crates/serde/src/lib.rs +++ b/crates/serde/src/lib.rs @@ -18,6 +18,8 @@ use serde::Serializer; mod bool; pub use self::bool::*; +pub mod displayfromstr; + mod optional; pub use self::optional::*; @@ -32,8 +34,6 @@ pub use ttd::*; mod other; -pub mod ssz; - pub use other::{OtherFields, WithOtherFields}; /// Serialize a byte vec as a hex string _without_ the "0x" prefix. diff --git a/crates/serde/src/ssz.rs b/crates/serde/src/ssz.rs deleted file mode 100644 index 2d9d3080d06..00000000000 --- a/crates/serde/src/ssz.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Serde functions for encoding SSZ primitives. - -/// Serde functions for encoding SSZ primitives using the "canonical JSON mapping" described -/// in the consensus-specs here: -pub mod json { - /// Serde functions for (de)serializing SSZ `uintN` types as quoted decimal strings. - /// - /// # Example - /// ``` - /// use alloy_serde; - /// use serde::{Deserialize, Serialize}; - /// - /// #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] - /// pub struct Container { - /// #[serde(with = "alloy_serde::ssz::json::uint")] - /// value: u64, - /// } - /// - /// let val = Container { value: 18112749083033600 }; - /// let s = serde_json::to_string(&val).unwrap(); - /// assert_eq!(s, "{\"value\":\"18112749083033600\"}"); - /// - /// let deserialized: Container = serde_json::from_str(&s).unwrap(); - /// assert_eq!(val, deserialized); - /// ``` - pub mod uint { - use crate::alloc::string::{String, ToString}; - use core::{fmt, str::FromStr}; - use serde::{Deserialize, Deserializer, Serializer}; - - /// Serialize a `uintN` compatible type `T` as a decimal quoted string. - pub fn serialize(value: &T, serializer: S) -> Result - where - T: fmt::Display, - S: Serializer, - { - serializer.collect_str(&value.to_string()) - } - - /// Deserialize a decimal quoted string to a `uintN` compatible type `T`. - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: FromStr, - T::Err: fmt::Display, - { - String::deserialize(deserializer)?.parse().map_err(serde::de::Error::custom) - } - } -}