From c825aafbc8a477d6c4da125a66098f4b3ca685f5 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:40:38 -0400 Subject: [PATCH] fix: only accept rpc enveloped format for transactions in payload (#4928) --- crates/payload/builder/src/payload.rs | 8 +- crates/primitives/src/transaction/mod.rs | 60 ++++++++-- crates/primitives/src/transaction/pooled.rs | 26 ++++- crates/rpc/rpc-engine-api/tests/it/payload.rs | 4 +- crates/rpc/rpc-types-compat/src/engine/mod.rs | 2 +- .../rpc-types-compat/src/engine/payload.rs | 107 ++++++++++++++++-- 6 files changed, 175 insertions(+), 32 deletions(-) diff --git a/crates/payload/builder/src/payload.rs b/crates/payload/builder/src/payload.rs index 64a25a6ff..519dadd71 100644 --- a/crates/payload/builder/src/payload.rs +++ b/crates/payload/builder/src/payload.rs @@ -10,8 +10,8 @@ use reth_rpc_types::engine::{ PayloadId, }; use reth_rpc_types_compat::engine::payload::{ - convert_block_to_payload_field_v2, convert_standalonewithdraw_to_withdrawal, - try_block_to_payload_v1, try_block_to_payload_v3, + block_to_payload_v3, convert_block_to_payload_field_v2, + convert_standalone_withdraw_to_withdrawal, try_block_to_payload_v1, }; use revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, SpecId}; /// Contains the built payload. @@ -100,7 +100,7 @@ impl From for ExecutionPayloadEnvelopeV3 { let BuiltPayload { block, fees, sidecars, .. } = value; ExecutionPayloadEnvelopeV3 { - execution_payload: try_block_to_payload_v3(block), + execution_payload: block_to_payload_v3(block), block_value: fees, // From the engine API spec: // @@ -148,7 +148,7 @@ impl PayloadBuilderAttributes { |withdrawals: Vec| { withdrawals .into_iter() - .map(convert_standalonewithdraw_to_withdrawal) // Removed the parentheses here + .map(convert_standalone_withdraw_to_withdrawal) // Removed the parentheses here .collect::>() }, ); diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index d0562dcf3..65bedeb52 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -876,7 +876,7 @@ impl TransactionSigned { /// /// For legacy transactions, it encodes the RLP of the transaction into the buffer: `rlp(tx)` /// For EIP-2718 typed it encodes the type of the transaction followed by the rlp of the - /// transaction: `type` + `rlp(tx)` + /// transaction: `type || rlp(tx)` pub fn encode_enveloped(&self, out: &mut dyn bytes::BufMut) { self.encode_inner(out, false) } @@ -927,6 +927,9 @@ impl TransactionSigned { /// Decodes legacy transaction from the data buffer into a tuple. /// /// This expects `rlp(legacy_tx)` + /// + /// Refer to the docs for [Self::decode_rlp_legacy_transaction] for details on the exact + /// format expected. // TODO: make buf advancement semantics consistent with `decode_enveloped_typed_transaction`, // so decoding methods do not need to manually advance the buffer pub(crate) fn decode_rlp_legacy_transaction_tuple( @@ -956,6 +959,12 @@ impl TransactionSigned { /// Decodes legacy transaction from the data buffer. /// + /// This should be used _only_ be used in general transaction decoding methods, which have + /// already ensured that the input is a legacy transaction with the following format: + /// `rlp(legacy_tx)` + /// + /// Legacy transactions are encoded as lists, so the input should start with a RLP list header. + /// /// This expects `rlp(legacy_tx)` // TODO: make buf advancement semantics consistent with `decode_enveloped_typed_transaction`, // so decoding methods do not need to manually advance the buffer @@ -969,7 +978,14 @@ impl TransactionSigned { /// Decodes en enveloped EIP-2718 typed transaction. /// - /// CAUTION: this expects that `data` is `[id, rlp(tx)]` + /// This should be used _only_ be used internally in general transaction decoding methods, + /// which have already ensured that the input is a typed transaction with the following format: + /// `tx_type || rlp(tx)` + /// + /// Note that this format does not start with any RLP header, and instead starts with a single + /// byte indicating the transaction type. + /// + /// CAUTION: this expects that `data` is `tx_type || rlp(tx)` pub fn decode_enveloped_typed_transaction( data: &mut &[u8], ) -> alloy_rlp::Result { @@ -1003,12 +1019,23 @@ impl TransactionSigned { Ok(signed) } - /// Decodes the "raw" format of transaction (e.g. `eth_sendRawTransaction`). + /// Decodes the "raw" format of transaction (similar to `eth_sendRawTransaction`). /// - /// The raw transaction is either a legacy transaction or EIP-2718 typed transaction - /// For legacy transactions, the format is encoded as: `rlp(tx)` - /// For EIP-2718 typed transaction, the format is encoded as the type of the transaction - /// followed by the rlp of the transaction: `type` + `rlp(tx)` + /// This should be used for any RPC method that accepts a raw transaction, **excluding** raw + /// EIP-4844 transactions in `eth_sendRawTransaction`. Currently, this includes: + /// * `eth_sendRawTransaction` for non-EIP-4844 transactions. + /// * All versions of `engine_newPayload`, in the `transactions` field. + /// + /// A raw transaction is either a legacy transaction or EIP-2718 typed transaction. + /// + /// For legacy transactions, the format is encoded as: `rlp(tx)`. This format will start with a + /// RLP list header. + /// + /// For EIP-2718 typed transactions, the format is encoded as the type of the transaction + /// followed by the rlp of the transaction: `type || rlp(tx)`. + /// + /// To decode EIP-4844 transactions in `eth_sendRawTransaction`, use + /// [PooledTransactionsElement::decode_enveloped]. pub fn decode_enveloped(tx: Bytes) -> alloy_rlp::Result { let mut data = tx.as_ref(); @@ -1045,7 +1072,24 @@ impl Encodable for TransactionSigned { /// This `Decodable` implementation only supports decoding rlp encoded transactions as it's used by /// p2p. /// -/// CAUTION: this expects that the given buf contains rlp +/// The p2p encoding format always includes an RLP header, although the type RLP header depends on +/// whether or not the transaction is a legacy transaction. +/// +/// If the transaction is a legacy transaction, it is just encoded as a RLP list: `rlp(tx)`. +/// +/// If the transaction is a typed transaction, it is encoded as a RLP string: +/// `rlp(type || rlp(tx))` +/// +/// This cannot be used for decoding EIP-4844 transactions in p2p, since the EIP-4844 variant of +/// [TransactionSigned] does not include the blob sidecar. For a general purpose decoding method +/// suitable for decoding transactions from p2p, see [PooledTransactionsElement]. +/// +/// CAUTION: Due to a quirk in [Header::decode], this method will succeed even if a typed +/// transaction is encoded in the RPC format, and does not start with a RLP header. This is because +/// [Header::decode] does not advance the buffer, and returns a length-1 string header if the first +/// byte is less than `0xf7`. This causes this decode implementation to pass unaltered buffer to +/// [TransactionSigned::decode_enveloped_typed_transaction], which expects the RPC format. Despite +/// this quirk, this should **not** be used for RPC methods that accept raw transactions. impl Decodable for TransactionSigned { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { // decode header diff --git a/crates/primitives/src/transaction/pooled.rs b/crates/primitives/src/transaction/pooled.rs index a2c5a2aa7..8f32435ef 100644 --- a/crates/primitives/src/transaction/pooled.rs +++ b/crates/primitives/src/transaction/pooled.rs @@ -109,12 +109,28 @@ impl PooledTransactionsElement { /// Decodes the "raw" format of transaction (e.g. `eth_sendRawTransaction`). /// - /// The raw transaction is either a legacy transaction or EIP-2718 typed transaction - /// For legacy transactions, the format is encoded as: `rlp(tx)` - /// For EIP-2718 typed transaction, the format is encoded as the type of the transaction - /// followed by the rlp of the transaction: `type` + `rlp(tx)` + /// This should be used for `eth_sendRawTransaction`, for any transaction type. Blob + /// transactions **must** include the blob sidecar as part of the raw encoding. /// - /// For encoded EIP-4844 transactions, the blob sidecar _must_ be included. + /// This method can not be used for decoding the `transactions` field of `engine_newPayload`, + /// because EIP-4844 transactions for that method do not include the blob sidecar. The blobs + /// are supplied in an argument separate from the payload. + /// + /// A raw transaction is either a legacy transaction or EIP-2718 typed transaction, with a + /// special case for EIP-4844 transactions. + /// + /// For legacy transactions, the format is encoded as: `rlp(tx)`. This format will start with a + /// RLP list header. + /// + /// For EIP-2718 typed transactions, the format is encoded as the type of the transaction + /// followed by the rlp of the transaction: `type || rlp(tx)`. + /// + /// For EIP-4844 transactions, the format includes a blob sidecar (the blobs, commitments, and + /// proofs) after the transaction: + /// `type || rlp([tx_payload_body, blobs, commitments, proofs])` + /// + /// Where `tx_payload_body` is encoded as a RLP list: + /// `[chain_id, nonce, max_priority_fee_per_gas, ..., y_parity, r, s]` pub fn decode_enveloped(tx: Bytes) -> alloy_rlp::Result { let mut data = tx.as_ref(); diff --git a/crates/rpc/rpc-engine-api/tests/it/payload.rs b/crates/rpc/rpc-engine-api/tests/it/payload.rs index f8aa231af..5b012726c 100644 --- a/crates/rpc/rpc-engine-api/tests/it/payload.rs +++ b/crates/rpc/rpc-engine-api/tests/it/payload.rs @@ -13,7 +13,7 @@ use reth_rpc_types::engine::{ ExecutionPayload, ExecutionPayloadBodyV1, ExecutionPayloadV1, PayloadError, }; use reth_rpc_types_compat::engine::payload::{ - convert_standalonewithdraw_to_withdrawal, convert_to_payload_body_v1, try_block_to_payload, + convert_standalone_withdraw_to_withdrawal, convert_to_payload_body_v1, try_block_to_payload, try_block_to_payload_v1, try_into_sealed_block, try_payload_v1_to_block, }; @@ -49,7 +49,7 @@ fn payload_body_roundtrip() { let withdraw = payload_body.withdrawals.map(|withdrawals| { withdrawals .into_iter() - .map(convert_standalonewithdraw_to_withdrawal) + .map(convert_standalone_withdraw_to_withdrawal) .collect::>() }); assert_eq!(block.withdrawals, withdraw); diff --git a/crates/rpc/rpc-types-compat/src/engine/mod.rs b/crates/rpc/rpc-types-compat/src/engine/mod.rs index 10e60327b..e03ba6f4c 100644 --- a/crates/rpc/rpc-types-compat/src/engine/mod.rs +++ b/crates/rpc/rpc-types-compat/src/engine/mod.rs @@ -1,6 +1,6 @@ //! Standalone functions for engine specific rpc type conversions pub mod payload; pub use payload::{ - convert_standalonewithdraw_to_withdrawal, convert_withdrawal_to_standalonewithdraw, + convert_standalone_withdraw_to_withdrawal, convert_withdrawal_to_standalone_withdraw, try_block_to_payload_v1, try_into_sealed_block, try_payload_v1_to_block, }; diff --git a/crates/rpc/rpc-types-compat/src/engine/payload.rs b/crates/rpc/rpc-types-compat/src/engine/payload.rs index d69b5af85..e0fad5060 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -1,6 +1,5 @@ //! Standalone Conversion Functions for Handling Different Versions of Execution Payloads in //! Ethereum's Engine -use alloy_rlp::Decodable; use reth_primitives::{ constants::{MAXIMUM_EXTRA_DATA_SIZE, MIN_PROTOCOL_BASE_FEE_U256}, proofs::{self, EMPTY_LIST_HASH}, @@ -23,8 +22,8 @@ pub fn try_payload_v1_to_block(payload: ExecutionPayloadV1) -> Result, _>>()?; let transactions_root = proofs::calculate_transaction_root(&transactions); @@ -68,7 +67,7 @@ pub fn try_payload_v2_to_block(payload: ExecutionPayloadV2) -> Result = payload .withdrawals .iter() - .map(|w| convert_standalonewithdraw_to_withdrawal(w.clone())) + .map(|w| convert_standalone_withdraw_to_withdrawal(w.clone())) .collect(); let withdrawals_root = proofs::calculate_withdrawals_root(&withdrawals); base_sealed_block.withdrawals = Some(withdrawals); @@ -92,7 +91,7 @@ pub fn try_payload_v3_to_block(payload: ExecutionPayloadV3) -> Result ExecutionPayload { if value.header.parent_beacon_block_root.is_some() { // block with parent beacon block root: V3 - ExecutionPayload::V3(try_block_to_payload_v3(value)) + ExecutionPayload::V3(block_to_payload_v3(value)) } else if value.withdrawals.is_some() { // block with withdrawals: V2 ExecutionPayload::V2(try_block_to_payload_v2(value)) @@ -147,7 +146,7 @@ pub fn try_block_to_payload_v2(value: SealedBlock) -> ExecutionPayloadV2 { .clone() .unwrap_or_default() .into_iter() - .map(convert_withdrawal_to_standalonewithdraw) + .map(convert_withdrawal_to_standalone_withdraw) .collect(); ExecutionPayloadV2 { @@ -172,7 +171,7 @@ pub fn try_block_to_payload_v2(value: SealedBlock) -> ExecutionPayloadV2 { } /// Converts [SealedBlock] to [ExecutionPayloadV3] -pub fn try_block_to_payload_v3(value: SealedBlock) -> ExecutionPayloadV3 { +pub fn block_to_payload_v3(value: SealedBlock) -> ExecutionPayloadV3 { let transactions = value .body .iter() @@ -188,7 +187,7 @@ pub fn try_block_to_payload_v3(value: SealedBlock) -> ExecutionPayloadV3 { .clone() .unwrap_or_default() .into_iter() - .map(convert_withdrawal_to_standalonewithdraw) + .map(convert_withdrawal_to_standalone_withdraw) .collect(); ExecutionPayloadV3 { @@ -249,7 +248,7 @@ pub fn convert_payload_input_v2_to_payload(value: ExecutionPayloadInputV2) -> Ex /// Converts [SealedBlock] to [ExecutionPayloadInputV2] pub fn convert_block_to_payload_input_v2(value: SealedBlock) -> ExecutionPayloadInputV2 { let withdraw = value.withdrawals.clone().map(|withdrawals| { - withdrawals.into_iter().map(convert_withdrawal_to_standalonewithdraw).collect::>() + withdrawals.into_iter().map(convert_withdrawal_to_standalone_withdraw).collect::>() }); ExecutionPayloadInputV2 { withdrawals: withdraw, @@ -321,7 +320,7 @@ pub fn validate_block_hash( } /// Converts [Withdrawal] to [reth_rpc_types::engine::payload::Withdrawal] -pub fn convert_withdrawal_to_standalonewithdraw( +pub fn convert_withdrawal_to_standalone_withdraw( withdrawal: Withdrawal, ) -> reth_rpc_types::engine::payload::Withdrawal { reth_rpc_types::engine::payload::Withdrawal { @@ -333,7 +332,7 @@ pub fn convert_withdrawal_to_standalonewithdraw( } /// Converts [reth_rpc_types::engine::payload::Withdrawal] to [Withdrawal] -pub fn convert_standalonewithdraw_to_withdrawal( +pub fn convert_standalone_withdraw_to_withdrawal( standalone: reth_rpc_types::engine::payload::Withdrawal, ) -> Withdrawal { Withdrawal { @@ -355,8 +354,92 @@ pub fn convert_to_payload_body_v1(value: Block) -> ExecutionPayloadBodyV1 { value.withdrawals.map(|withdrawals| { withdrawals .into_iter() - .map(convert_withdrawal_to_standalonewithdraw) + .map(convert_withdrawal_to_standalone_withdraw) .collect::>() }); ExecutionPayloadBodyV1 { transactions: transactions.collect(), withdrawals: withdraw } } + +#[cfg(test)] +mod tests { + use reth_primitives::{hex, Bytes, U256, U64}; + use reth_rpc_types::{engine::ExecutionPayloadV3, ExecutionPayloadV1, ExecutionPayloadV2}; + + use super::{block_to_payload_v3, try_payload_v3_to_block}; + + #[test] + fn roundtrip_payload_to_block() { + let first_transaction_raw = Bytes::from_static(&hex!("02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]); + let second_transaction_raw = Bytes::from_static(&hex!("03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]); + + let new_payload = ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + base_fee_per_gas: U256::from(7u64), + block_number: U64::from(0xa946u64), + block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(), + logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(), + extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(), + gas_limit: U64::from(0x1c9c380), + gas_used: U64::from(0x1f4a9), + timestamp: U64::from(0x651f35b8), + fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(), + parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(), + prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(), + receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(), + state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(), + transactions: vec![first_transaction_raw, second_transaction_raw], + }, + withdrawals: vec![], + }, + blob_gas_used: U64::from(0xc0000), + excess_blob_gas: U64::from(0x580000), + }; + + let mut block = try_payload_v3_to_block(new_payload.clone()).unwrap(); + + // this newPayload came with a parent beacon block root, we need to manually insert it + // before hashing + let parent_beacon_block_root = + hex!("531cd53b8e68deef0ea65edfa3cda927a846c307b0907657af34bc3f313b5871"); + block.header.parent_beacon_block_root = Some(parent_beacon_block_root.into()); + + let converted_payload = block_to_payload_v3(block.seal_slow()); + + // ensure the payloads are the same + assert_eq!(new_payload, converted_payload); + } + + #[test] + fn payload_to_block_rejects_network_encoded_tx() { + let first_transaction_raw = Bytes::from_static(&hex!("b9017e02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]); + let second_transaction_raw = Bytes::from_static(&hex!("b9013c03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]); + + let new_payload = ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + base_fee_per_gas: U256::from(7u64), + block_number: U64::from(0xa946u64), + block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(), + logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(), + extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(), + gas_limit: U64::from(0x1c9c380), + gas_used: U64::from(0x1f4a9), + timestamp: U64::from(0x651f35b8), + fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(), + parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(), + prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(), + receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(), + state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(), + transactions: vec![first_transaction_raw, second_transaction_raw], + }, + withdrawals: vec![], + }, + blob_gas_used: U64::from(0xc0000), + excess_blob_gas: U64::from(0x580000), + }; + + let _block = try_payload_v3_to_block(new_payload.clone()) + .expect_err("execution payload conversion requires typed txs without a rlp header"); + } +}