diff --git a/crates/consensus/src/signed.rs b/crates/consensus/src/signed.rs index 47554129beb..62069923386 100644 --- a/crates/consensus/src/signed.rs +++ b/crates/consensus/src/signed.rs @@ -1,5 +1,7 @@ -use crate::transaction::SignableTransaction; +use crate::transaction::{RlpEcdsaTx, SignableTransaction}; +use alloy_eips::eip2718::Eip2718Result; use alloy_primitives::{Signature, B256}; +use alloy_rlp::BufMut; /// A transaction with a signature and hash seal. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -55,6 +57,76 @@ impl, Sig> Signed { } } +impl Signed +where + T: RlpEcdsaTx, +{ + /// Get the length of the transaction when RLP encoded. + pub fn rlp_encoded_length(&self) -> usize { + self.tx.rlp_encoded_length_with_signature(&self.signature) + } + + /// RLP encode the signed transaction. + pub fn rlp_encode(&self, out: &mut dyn BufMut) { + self.tx.rlp_encode_signed(&self.signature, out); + } + + /// Get the length of the transaction when EIP-2718 encoded. + pub fn eip2718_encoded_length(&self) -> usize { + self.tx.eip2718_encoded_length(&self.signature) + } + + /// EIP-2718 encode the signed transaction with a specified type flag. + pub fn eip2718_encode_with_type(&self, ty: u8, out: &mut dyn BufMut) { + self.tx.eip2718_encode_with_type(&self.signature, ty, out); + } + + /// EIP-2718 encode the signed transaction. + pub fn eip2718_encode(&self, out: &mut dyn BufMut) { + self.tx.eip2718_encode(&self.signature, out); + } + + /// Get the length of the transaction when network encoded. + pub fn network_encoded_length(&self) -> usize { + self.tx.network_encoded_length(&self.signature) + } + + /// Network encode the signed transaction with a specified type flag. + pub fn network_encode_with_type(&self, ty: u8, out: &mut dyn BufMut) { + self.tx.network_encode_with_type(&self.signature, ty, out); + } + + /// Network encode the signed transaction. + pub fn network_encode(&self, out: &mut dyn BufMut) { + self.tx.network_encode(&self.signature, out); + } + + /// RLP decode the signed transaction. + pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result { + T::rlp_decode_signed(buf) + } + + /// EIP-2718 decode the signed transaction with a specified type flag. + pub fn eip2718_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result { + T::eip2718_decode_with_type(buf, ty) + } + + /// EIP-2718 decode the signed transaction. + pub fn eip2718_decode(buf: &mut &[u8]) -> Eip2718Result { + T::eip2718_decode(buf) + } + + /// Network decode the signed transaction with a specified type flag. + pub fn network_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result { + T::network_decode_with_type(buf, ty) + } + + /// Network decode the signed transaction. + pub fn network_decode(buf: &mut &[u8]) -> Eip2718Result { + T::network_decode(buf) + } +} + #[cfg(feature = "k256")] impl> Signed { /// Recover the signer of the transaction diff --git a/crates/consensus/src/transaction/eip1559.rs b/crates/consensus/src/transaction/eip1559.rs index 92563600134..e71ef3373be 100644 --- a/crates/consensus/src/transaction/eip1559.rs +++ b/crates/consensus/src/transaction/eip1559.rs @@ -1,8 +1,7 @@ -use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; -use alloc::vec::Vec; +use crate::{transaction::RlpEcdsaTx, SignableTransaction, Signed, Transaction, TxType}; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; -use alloy_primitives::{keccak256, Bytes, ChainId, Parity, Signature, TxKind, B256, U256}; -use alloy_rlp::{BufMut, Decodable, Encodable, Header}; +use alloy_primitives::{Bytes, ChainId, Signature, TxKind, B256, U256}; +use alloy_rlp::{BufMut, Decodable, Encodable}; use core::mem; /// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)). @@ -89,37 +88,33 @@ impl TxEip1559 { } } - /// Decodes the inner [TxEip1559] fields from RLP bytes. - /// - /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following - /// RLP fields in the following order: - /// - /// - `chain_id` - /// - `nonce` - /// - `max_priority_fee_per_gas` - /// - `max_fee_per_gas` - /// - `gas_limit` - /// - `to` - /// - `value` - /// - `data` (`input`) - /// - `access_list` - pub fn decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { - Ok(Self { - chain_id: Decodable::decode(buf)?, - nonce: Decodable::decode(buf)?, - max_priority_fee_per_gas: Decodable::decode(buf)?, - max_fee_per_gas: Decodable::decode(buf)?, - gas_limit: Decodable::decode(buf)?, - to: Decodable::decode(buf)?, - value: Decodable::decode(buf)?, - input: Decodable::decode(buf)?, - access_list: Decodable::decode(buf)?, - }) + /// Get the transaction type + #[doc(alias = "transaction_type")] + pub(crate) const fn tx_type() -> TxType { + TxType::Eip1559 } + /// Calculates a heuristic for the in-memory size of the [TxEip1559] + /// transaction. + #[inline] + pub fn size(&self) -> usize { + mem::size_of::() + // chain_id + mem::size_of::() + // nonce + mem::size_of::() + // gas_limit + mem::size_of::() + // max_fee_per_gas + mem::size_of::() + // max_priority_fee_per_gas + self.to.size() + // to + mem::size_of::() + // value + self.access_list.size() + // access_list + self.input.len() // input + } +} + +impl RlpEcdsaTx for TxEip1559 { + const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 }; + /// Outputs the length of the transaction's fields, without a RLP header. - #[doc(hidden)] - pub fn fields_len(&self) -> usize { + fn rlp_encoded_fields_length(&self) -> usize { let mut len = 0; len += self.chain_id.length(); len += self.nonce.length(); @@ -133,8 +128,9 @@ impl TxEip1559 { len } - /// Encodes only the transaction's fields into the desired buffer, without a RLP header. - pub(crate) fn encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + /// Encodes only the transaction's fields into the desired buffer, without + /// a RLP header. + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { self.chain_id.encode(out); self.nonce.encode(out); self.max_priority_fee_per_gas.encode(out); @@ -146,120 +142,32 @@ impl TxEip1559 { self.access_list.encode(out); } - /// Returns what the encoded length should be, if the transaction were RLP encoded with the - /// given signature, depending on the value of `with_header`. - /// - /// If `with_header` is `true`, the payload length will include the RLP header length. - /// If `with_header` is `false`, the payload length will not include the RLP header length. - pub fn encoded_len_with_signature(&self, signature: &S, with_header: bool) -> usize - where - S: EncodableSignature, - { - // this counts the tx fields and signature fields - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - - // this counts: - // * tx type byte - // * inner header length - // * inner payload length - let inner_payload_length = - 1 + Header { list: true, payload_length }.length() + payload_length; - - if with_header { - // header length plus length of the above, wrapped with a string header - Header { list: false, payload_length: inner_payload_length }.length() - + inner_payload_length - } else { - inner_payload_length - } - } - - /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating - /// hash that for eip2718 does not require a rlp header. - #[doc(hidden)] - pub fn encode_with_signature(&self, signature: &S, out: &mut dyn BufMut, with_header: bool) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - if with_header { - Header { - list: false, - payload_length: 1 + Header { list: true, payload_length }.length() + payload_length, - } - .encode(out); - } - out.put_u8(self.tx_type() as u8); - self.encode_with_signature_fields(signature, out); - } - - /// Decodes the transaction from RLP bytes, including the signature. - /// - /// This __does not__ expect the bytes to start with a transaction type byte or string - /// header. + /// Decodes the inner [TxEip1559] fields from RLP bytes. /// - /// This __does__ expect the bytes to start with a list header and include a signature. - #[doc(hidden)] - pub fn decode_signed_fields(buf: &mut &[u8]) -> alloy_rlp::Result> { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - // record original length so we can check encoding - let original_len = buf.len(); - - let tx = Self::decode_fields(buf)?; - let signature = Signature::decode_rlp_vrs(buf)?; - - if !matches!(signature.v(), Parity::Parity(_)) { - return Err(alloy_rlp::Error::Custom("invalid parity for typed transaction")); - } - - let signed = tx.into_signed(signature); - if buf.len() + header.payload_length != original_len { - return Err(alloy_rlp::Error::ListLengthMismatch { - expected: header.payload_length, - got: original_len - buf.len(), - }); - } - - Ok(signed) - } - - /// Encodes the transaction from RLP bytes, including the signature. This __does not__ encode a - /// tx type byte or string header. + /// NOTE: This assumes a RLP header has already been decoded, and _just_ + /// decodes the following RLP fields in the following order: /// - /// This __does__ encode a list header and include a signature. - pub fn encode_with_signature_fields(&self, signature: &S, out: &mut dyn BufMut) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - let header = Header { list: true, payload_length }; - header.encode(out); - self.encode_fields(out); - signature.write_rlp_vrs(out); - } - - /// Get transaction type - #[doc(alias = "transaction_type")] - pub(crate) const fn tx_type(&self) -> TxType { - TxType::Eip1559 - } - - /// Calculates a heuristic for the in-memory size of the [TxEip1559] transaction. - #[inline] - pub fn size(&self) -> usize { - mem::size_of::() + // chain_id - mem::size_of::() + // nonce - mem::size_of::() + // gas_limit - mem::size_of::() + // max_fee_per_gas - mem::size_of::() + // max_priority_fee_per_gas - self.to.size() + // to - mem::size_of::() + // value - self.access_list.size() + // access_list - self.input.len() // input + /// - `chain_id` + /// - `nonce` + /// - `max_priority_fee_per_gas` + /// - `max_fee_per_gas` + /// - `gas_limit` + /// - `to` + /// - `value` + /// - `data` (`input`) + /// - `access_list` + fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self { + chain_id: Decodable::decode(buf)?, + nonce: Decodable::decode(buf)?, + max_priority_fee_per_gas: Decodable::decode(buf)?, + max_fee_per_gas: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + to: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + access_list: Decodable::decode(buf)?, + }) } } @@ -331,7 +239,7 @@ impl SignableTransaction for TxEip1559 { } fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { - out.put_u8(self.tx_type() as u8); + out.put_u8(Self::tx_type() as u8); self.encode(out) } @@ -344,37 +252,24 @@ impl SignableTransaction for TxEip1559 { // combination for an EIP-1559 transaction. V should indicate the y-parity of the // signature. let signature = signature.with_parity_bool(); - - let mut buf = Vec::with_capacity(self.encoded_len_with_signature(&signature, false)); - self.encode_with_signature(&signature, &mut buf, false); - let hash = keccak256(&buf); - - Signed::new_unchecked(self, signature, hash) + let tx_hash = self.tx_hash(&signature); + Signed::new_unchecked(self, signature, tx_hash) } } impl Encodable for TxEip1559 { fn encode(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); + self.rlp_encode(out); } fn length(&self) -> usize { - let payload_length = self.fields_len(); - Header { list: true, payload_length }.length() + payload_length + self.rlp_encoded_length() } } impl Decodable for TxEip1559 { - fn decode(data: &mut &[u8]) -> alloy_rlp::Result { - let header = Header::decode(data)?; - let remaining_len = data.len(); - - if header.payload_length > remaining_len { - return Err(alloy_rlp::Error::InputTooShort); - } - - Self::decode_fields(data) + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::rlp_decode(buf) } } @@ -501,7 +396,7 @@ pub(super) mod serde_bincode_compat { #[cfg(all(test, feature = "k256"))] mod tests { use super::TxEip1559; - use crate::SignableTransaction; + use crate::{transaction::RlpEcdsaTx, SignableTransaction}; use alloy_eips::eip2930::AccessList; use alloy_primitives::{address, b256, hex, Address, Signature, B256, U256}; @@ -511,16 +406,16 @@ mod tests { let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0"); let tx = TxEip1559 { - chain_id: 1, - nonce: 0x42, - gas_limit: 44386, - to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(), - value: U256::from(0_u64), - input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(), - max_fee_per_gas: 0x4a817c800, - max_priority_fee_per_gas: 0x3b9aca00, - access_list: AccessList::default(), - }; + chain_id: 1, + nonce: 0x42, + gas_limit: 44386, + to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(), + value: U256::from(0_u64), + input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(), + max_fee_per_gas: 0x4a817c800, + max_priority_fee_per_gas: 0x3b9aca00, + access_list: AccessList::default(), + }; let sig = Signature::from_scalars_and_parity( b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"), @@ -544,16 +439,16 @@ mod tests { let hash: B256 = b256!("0ec0b6a2df4d87424e5f6ad2a654e27aaeb7dac20ae9e8385cc09087ad532ee0"); let tx = TxEip1559 { - chain_id: 1, - nonce: 0x42, - gas_limit: 44386, - to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(), - value: U256::from(0_u64), - input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(), - max_fee_per_gas: 0x4a817c800, - max_priority_fee_per_gas: 0x3b9aca00, - access_list: AccessList::default(), - }; + chain_id: 1, + nonce: 0x42, + gas_limit: 44386, + to: address!("6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").into(), + value: U256::from(0_u64), + input: hex!("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").into(), + max_fee_per_gas: 0x4a817c800, + max_priority_fee_per_gas: 0x3b9aca00, + access_list: AccessList::default(), + }; let sig = Signature::from_scalars_and_parity( b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"), @@ -563,8 +458,8 @@ mod tests { .unwrap(); let mut buf = vec![]; - tx.encode_with_signature_fields(&sig, &mut buf); - let decoded = TxEip1559::decode_signed_fields(&mut &buf[..]).unwrap(); + tx.rlp_encode_signed(&sig, &mut buf); + let decoded = TxEip1559::rlp_decode_signed(&mut &buf[..]).unwrap(); assert_eq!(decoded, tx.into_signed(sig)); assert_eq!(*decoded.hash(), hash); } diff --git a/crates/consensus/src/transaction/eip2930.rs b/crates/consensus/src/transaction/eip2930.rs index 3f3b6030f58..67dbbbeeefc 100644 --- a/crates/consensus/src/transaction/eip2930.rs +++ b/crates/consensus/src/transaction/eip2930.rs @@ -1,10 +1,11 @@ -use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; -use alloc::vec::Vec; +use crate::{SignableTransaction, Signed, Transaction, TxType}; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; -use alloy_primitives::{keccak256, Bytes, ChainId, Parity, Signature, TxKind, B256, U256}; -use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header}; +use alloy_primitives::{Bytes, ChainId, Signature, TxKind, B256, U256}; +use alloy_rlp::{BufMut, Decodable, Encodable}; use core::mem; +use super::RlpEcdsaTx; + /// Transaction with an [`AccessList`] ([EIP-2930](https://eips.ethereum.org/EIPS/eip-2930)). #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] @@ -58,6 +59,12 @@ pub struct TxEip2930 { } impl TxEip2930 { + /// Get the transaction type. + #[doc(alias = "transaction_type")] + pub const fn tx_type() -> TxType { + TxType::Eip2930 + } + /// Calculates a heuristic for the in-memory size of the [TxEip2930] transaction. #[inline] pub fn size(&self) -> usize { @@ -70,36 +77,13 @@ impl TxEip2930 { self.access_list.size() + // access_list self.input.len() // input } +} - /// Decodes the inner [TxEip2930] fields from RLP bytes. - /// - /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following - /// RLP fields in the following order: - /// - /// - `chain_id` - /// - `nonce` - /// - `gas_price` - /// - `gas_limit` - /// - `to` - /// - `value` - /// - `data` (`input`) - /// - `access_list` - pub fn decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { - Ok(Self { - chain_id: Decodable::decode(buf)?, - nonce: Decodable::decode(buf)?, - gas_price: Decodable::decode(buf)?, - gas_limit: Decodable::decode(buf)?, - to: Decodable::decode(buf)?, - value: Decodable::decode(buf)?, - input: Decodable::decode(buf)?, - access_list: Decodable::decode(buf)?, - }) - } +impl RlpEcdsaTx for TxEip2930 { + const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 }; /// Outputs the length of the transaction's fields, without a RLP header. - #[doc(hidden)] - pub fn fields_len(&self) -> usize { + fn rlp_encoded_fields_length(&self) -> usize { let mut len = 0; len += self.chain_id.length(); len += self.nonce.length(); @@ -112,8 +96,7 @@ impl TxEip2930 { len } - /// Encodes only the transaction's fields into the desired buffer, without a RLP header. - pub(crate) fn encode_fields(&self, out: &mut dyn BufMut) { + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { self.chain_id.encode(out); self.nonce.encode(out); self.gas_price.encode(out); @@ -124,106 +107,17 @@ impl TxEip2930 { self.access_list.encode(out); } - /// Returns what the encoded length should be, if the transaction were RLP encoded with the - /// given signature, depending on the value of `with_header`. - /// - /// If `with_header` is `true`, the payload length will include the RLP header length. - /// If `with_header` is `false`, the payload length will not include the RLP header length. - pub fn encoded_len_with_signature(&self, signature: &S, with_header: bool) -> usize - where - S: EncodableSignature, - { - // this counts the tx fields and signature fields - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - - // this counts: - // * tx type byte - // * inner header length - // * inner payload length - let inner_payload_length = - 1 + Header { list: true, payload_length }.length() + payload_length; - - if with_header { - // header length plus length of the above, wrapped with a string header - Header { list: false, payload_length: inner_payload_length }.length() - + inner_payload_length - } else { - inner_payload_length - } - } - - /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating - /// hash that for eip2718 does not require a rlp header - #[doc(hidden)] - pub fn encode_with_signature(&self, signature: &S, out: &mut dyn BufMut, with_header: bool) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - if with_header { - Header { - list: false, - payload_length: 1 + Header { list: true, payload_length }.length() + payload_length, - } - .encode(out); - } - out.put_u8(self.tx_type() as u8); - self.encode_with_signature_fields(signature, out); - } - - /// Encodes the transaction from RLP bytes, including the signature. This __does not__ encode a - /// tx type byte or string header. - /// - /// This __does__ encode a list header and include a signature. - pub fn encode_with_signature_fields(&self, signature: &S, out: &mut dyn BufMut) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - let header = Header { list: true, payload_length }; - header.encode(out); - self.encode_fields(out); - signature.write_rlp_vrs(out); - } - - /// Decodes the transaction from RLP bytes, including the signature. - /// - /// This __does not__ expect the bytes to start with a transaction type byte or string - /// header. - /// - /// This __does__ expect the bytes to start with a list header and include a signature. - #[doc(hidden)] - pub fn decode_signed_fields(buf: &mut &[u8]) -> alloy_rlp::Result> { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - // record original length so we can check encoding - let original_len = buf.len(); - - let tx = Self::decode_fields(buf)?; - let signature = Signature::decode_rlp_vrs(buf)?; - - if !matches!(signature.v(), Parity::Parity(_)) { - return Err(alloy_rlp::Error::Custom("invalid parity for typed transaction")); - } - - let signed = tx.into_signed(signature); - if buf.len() + header.payload_length != original_len { - return Err(alloy_rlp::Error::ListLengthMismatch { - expected: header.payload_length, - got: original_len - buf.len(), - }); - } - - Ok(signed) - } - - /// Get transaction type. - #[doc(alias = "transaction_type")] - pub const fn tx_type(&self) -> TxType { - TxType::Eip2930 + fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self { + chain_id: Decodable::decode(buf)?, + nonce: Decodable::decode(buf)?, + gas_price: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + to: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + access_list: Decodable::decode(buf)?, + }) } } @@ -295,60 +189,44 @@ impl SignableTransaction for TxEip2930 { } fn encode_for_signing(&self, out: &mut dyn BufMut) { - out.put_u8(self.tx_type() as u8); - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); + out.put_u8(Self::tx_type() as u8); + self.encode(out); } fn payload_len_for_signature(&self) -> usize { - let payload_length = self.fields_len(); - // 'transaction type byte length' + 'header length' + 'payload length' - 1 + Header { list: true, payload_length }.length() + payload_length + self.length() + 1 } fn into_signed(self, signature: Signature) -> Signed { - // Drop any v chain id value to ensure the signature format is correct at the time of - // combination for an EIP-2930 transaction. V should indicate the y-parity of the - // signature. + // Drop any v chain id value to ensure the signature format is correct + // at the time of combination for an EIP-2930 transaction. V should + // indicate the y-parity of the signature. let signature = signature.with_parity_bool(); - - let mut buf = Vec::with_capacity(self.encoded_len_with_signature(&signature, false)); - self.encode_with_signature(&signature, &mut buf, false); - let hash = keccak256(&buf); - - Signed::new_unchecked(self, signature, hash) + let tx_hash = self.tx_hash(&signature); + Signed::new_unchecked(self, signature, tx_hash) } } impl Encodable for TxEip2930 { fn encode(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); + self.rlp_encode(out); } fn length(&self) -> usize { - let payload_length = self.fields_len(); - length_of_length(payload_length) + payload_length + self.rlp_encoded_length() } } impl Decodable for TxEip2930 { - fn decode(data: &mut &[u8]) -> alloy_rlp::Result { - let header = Header::decode(data)?; - let remaining_len = data.len(); - - if header.payload_length > remaining_len { - return Err(alloy_rlp::Error::InputTooShort); - } - - Self::decode_fields(data) + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::rlp_decode(buf) } } #[cfg(test)] mod tests { use super::TxEip2930; - use crate::{SignableTransaction, TxEnvelope}; + use crate::{transaction::RlpEcdsaTx, SignableTransaction, TxEnvelope}; use alloy_primitives::{Address, Signature, TxKind, U256}; use alloy_rlp::{Decodable, Encodable}; @@ -368,9 +246,9 @@ mod tests { let signature = Signature::test_signature(); let mut encoded = Vec::new(); - tx.encode_with_signature_fields(&signature, &mut encoded); + tx.rlp_encode_signed(&signature, &mut encoded); - let decoded = TxEip2930::decode_signed_fields(&mut &*encoded).unwrap(); + let decoded = TxEip2930::rlp_decode_signed(&mut &*encoded).unwrap(); assert_eq!(decoded, tx.into_signed(signature)); } diff --git a/crates/consensus/src/transaction/eip4844.rs b/crates/consensus/src/transaction/eip4844.rs index a3ad4dbceaf..9bbfba7d631 100644 --- a/crates/consensus/src/transaction/eip4844.rs +++ b/crates/consensus/src/transaction/eip4844.rs @@ -1,9 +1,9 @@ -use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; +use crate::{SignableTransaction, Signed, Transaction, TxType}; use alloc::vec::Vec; use alloy_eips::{eip2930::AccessList, eip4844::DATA_GAS_PER_BLOB, eip7702::SignedAuthorization}; -use alloy_primitives::{keccak256, Address, Bytes, ChainId, Parity, Signature, TxKind, B256, U256}; -use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header}; +use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256}; +use alloy_rlp::{Buf, BufMut, Decodable, Encodable, Header}; use core::mem; #[doc(inline)] @@ -13,6 +13,8 @@ pub use alloy_eips::eip4844::BlobTransactionSidecar; #[doc(inline)] pub use alloy_eips::eip4844::BlobTransactionValidationError; +use super::RlpEcdsaTx; + /// [EIP-4844 Blob Transaction](https://eips.ethereum.org/EIPS/eip-4844#blob-transaction) /// /// A transaction with blob hashes and max blob fee. @@ -56,6 +58,20 @@ impl<'de> serde::Deserialize<'de> for TxEip4844Variant { } } +impl From> for Signed { + fn from(value: Signed) -> Self { + let (tx, signature, hash) = value.into_parts(); + Self::new_unchecked(TxEip4844Variant::TxEip4844(tx), signature, hash) + } +} + +impl From> for Signed { + fn from(value: Signed) -> Self { + let (tx, signature, hash) = value.into_parts(); + Self::new_unchecked(TxEip4844Variant::TxEip4844WithSidecar(tx), signature, hash) + } +} + impl From for TxEip4844Variant { fn from(tx: TxEip4844WithSidecar) -> Self { Self::TxEip4844WithSidecar(tx) @@ -100,7 +116,7 @@ impl TxEip4844Variant { /// Get the transaction type. #[doc(alias = "transaction_type")] - pub const fn tx_type(&self) -> TxType { + pub const fn tx_type() -> TxType { TxType::Eip4844 } @@ -112,89 +128,6 @@ impl TxEip4844Variant { Self::TxEip4844WithSidecar(tx) => tx.tx(), } } - - /// Outputs the length of the transaction's fields, without a RLP header. - #[doc(hidden)] - pub fn fields_len(&self) -> usize { - match self { - Self::TxEip4844(tx) => tx.fields_len(), - Self::TxEip4844WithSidecar(tx) => tx.tx().fields_len(), - } - } - - /// Encodes the [TxEip4844Variant] fields as RLP, with a tx type. If `with_header` is `false`, - /// the following will be encoded: - /// `tx_type (0x03) || rlp([transaction_payload_body, blobs, commitments, proofs])` - /// - /// If `with_header` is `true`, the following will be encoded: - /// `rlp(tx_type (0x03) || rlp([transaction_payload_body, blobs, commitments, proofs]))` - #[doc(hidden)] - pub fn encode_with_signature(&self, signature: &S, out: &mut dyn BufMut, with_header: bool) - where - S: EncodableSignature, - { - let payload_length = match self { - Self::TxEip4844(tx) => tx.fields_len() + signature.rlp_vrs_len(), - Self::TxEip4844WithSidecar(tx) => { - let payload_length = tx.tx().fields_len() + signature.rlp_vrs_len(); - let inner_header = Header { list: true, payload_length }; - inner_header.length() + payload_length + tx.sidecar().rlp_encoded_fields_length() - } - }; - - if with_header { - Header { - list: false, - payload_length: 1 - + Header { list: false, payload_length }.length() - + payload_length, - } - .encode(out); - } - out.put_u8(self.tx_type() as u8); - - match self { - Self::TxEip4844(tx) => { - tx.encode_with_signature_fields(signature, out); - } - Self::TxEip4844WithSidecar(tx) => { - tx.encode_with_signature_fields(signature, out); - } - } - } - - /// Decodes the transaction from RLP bytes, including the signature. - /// - /// This __does not__ expect the bytes to start with a transaction type byte or string - /// header. - /// - /// This __does__ expect the bytes to start with a list header and include a signature. - #[doc(hidden)] - pub fn decode_signed_fields(buf: &mut &[u8]) -> alloy_rlp::Result> { - let mut current_buf = *buf; - let _header = Header::decode(&mut current_buf)?; - - // There are two possibilities when decoding a signed EIP-4844 transaction: - // If it's a historical transaction, it will only have the transaction fields, and no - // sidecar. If it's a transaction received during the gossip stage or sent through - // eth_sendRawTransaction, it will have the transaction fields and a sidecar. - // - // To disambiguate, we try to decode two list headers. If there is only one list header, we - // assume it's a historical transaction. If there are two, we know the transaction contains - // a sidecar. - let header = Header::decode(&mut current_buf)?; - if header.list { - let tx = TxEip4844WithSidecar::decode_signed_fields(buf)?; - let (tx, signature, hash) = tx.into_parts(); - return Ok(Signed::new_unchecked(tx.into(), signature, hash)); - } - - // Since there is not a second list header, this is a historical 4844 transaction without a - // sidecar. - let tx = TxEip4844::decode_signed_fields(buf)?; - let (tx, signature, hash) = tx.into_parts(); - Ok(Signed::new_unchecked(tx.into(), signature, hash)) - } } impl Transaction for TxEip4844Variant { @@ -296,14 +229,129 @@ impl Transaction for TxEip4844Variant { } } +impl RlpEcdsaTx for TxEip4844Variant { + const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 }; + + fn rlp_encoded_fields_length(&self) -> usize { + match self { + Self::TxEip4844(inner) => inner.rlp_encoded_fields_length(), + Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_fields_length(), + } + } + + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + match self { + Self::TxEip4844(inner) => inner.rlp_encode_fields(out), + Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_fields(out), + } + } + + fn rlp_encoded_length(&self) -> usize { + match self { + Self::TxEip4844(inner) => inner.rlp_encoded_length(), + Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_length(), + } + } + + fn rlp_encoded_length_with_signature(&self, signature: &Signature) -> usize { + match self { + Self::TxEip4844(inner) => inner.rlp_encoded_length_with_signature(signature), + Self::TxEip4844WithSidecar(inner) => inner.rlp_encoded_length_with_signature(signature), + } + } + + fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) { + match self { + Self::TxEip4844(inner) => inner.rlp_encode_signed(signature, out), + Self::TxEip4844WithSidecar(inner) => inner.rlp_encode_signed(signature, out), + } + } + + fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + let needle = &mut &**buf; + + // We also need to do a trial decoding of WithSidecar to see if it + // works. The trial ref is consumed to look for a WithSidecar. + let trial = &mut &**buf; + + // If the next bytes are a header, one of 3 things is true: + // - If the header is a list, this is a WithSidecar tx + // - If there is no header, this is a non-sidecar tx with a single-byte chain ID. + // - If there is a string header, this is a non-sidecar tx with a multi-byte chain ID. + // To check these, we first try to decode the header. If it fails or is + // not a list, we lmow that it is a non-sidecar transaction. + if Header::decode(needle).map_or(false, |h| h.list) { + if let Ok(tx) = TxEip4844WithSidecar::rlp_decode_fields(trial) { + *buf = *trial; + return Ok(tx.into()); + } + } + TxEip4844::rlp_decode_fields(buf).map(Into::into) + } + + fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let needle = &mut &**buf; + let header = Header::decode(needle)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let remaining_len = needle.len(); + if header.payload_length > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); + } + + let chunk = &mut &buf[..header.length_with_payload()]; + let res = Self::rlp_decode_fields(chunk)?; + if !chunk.is_empty() { + return Err(alloy_rlp::Error::UnexpectedLength); + } + buf.advance(header.length_with_payload()); + Ok(res) + } + + fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> { + // We need to determine if this has a sidecar tx or not. The needle ref + // is consumed to look for headers. + let needle = &mut &**buf; + + // We also need to do a trial decoding of WithSidecar to see if it + // works. The original ref is consumed to look for a WithSidecar. + let trial = &mut &**buf; + + // First we decode the outer header + Header::decode(needle)?; + + // If the next bytes are a header, one of 3 things is true: + // - If the header is a list, this is a WithSidecar tx + // - If there is no header, this is a non-sidecar tx with a single-byte chain ID. + // - If there is a string header, this is a non-sidecar tx with a multi-byte chain ID. + // To check these, we first try to decode the header. If it fails or is + // not a list, we lmow that it is a non-sidecar transaction. + if Header::decode(needle).map_or(false, |h| h.list) { + if let Ok((tx, signature)) = TxEip4844WithSidecar::rlp_decode_with_signature(trial) { + // If succesful, we need to consume the trial buffer up to + // the same point. + *buf = *trial; + return Ok((tx.into(), signature)); + } + } + TxEip4844::rlp_decode_with_signature(buf).map(|(tx, signature)| (tx.into(), signature)) + } + + fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash { + // eip4844 tx_hash is always based on the non-sidecar encoding + self.tx().tx_hash_with_type(signature, ty) + } +} + impl SignableTransaction for TxEip4844Variant { fn set_chain_id(&mut self, chain_id: ChainId) { match self { - Self::TxEip4844(ref mut inner) => { - inner.chain_id = chain_id; + Self::TxEip4844(inner) => { + inner.set_chain_id(chain_id); } - Self::TxEip4844WithSidecar(ref mut inner) => { - inner.tx.chain_id = chain_id; + Self::TxEip4844WithSidecar(inner) => { + inner.set_chain_id(chain_id); } } } @@ -318,9 +366,7 @@ impl SignableTransaction for TxEip4844Variant { } fn payload_len_for_signature(&self) -> usize { - let payload_length = self.fields_len(); - // 'transaction type byte length' + 'header length' + 'payload length' - 1 + length_of_length(payload_length) + payload_length + self.tx().payload_len_for_signature() } fn into_signed(self, signature: Signature) -> Signed { @@ -329,11 +375,7 @@ impl SignableTransaction for TxEip4844Variant { // signature. let signature = signature.with_parity_bool(); - let payload_length = 1 + self.fields_len() + signature.rlp_vrs_len(); - let mut buf = Vec::with_capacity(payload_length); - // we use the inner tx to encode the fields - self.tx().encode_with_signature(&signature, &mut buf, false); - let hash = keccak256(&buf); + let hash = self.tx_hash(&signature); Signed::new_unchecked(self, signature, hash) } @@ -462,41 +504,33 @@ impl TxEip4844 { sidecar.validate(&self.blob_versioned_hashes, proof_settings) } - /// Decodes the inner [TxEip4844Variant] fields from RLP bytes. - /// - /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following - /// RLP fields in the following order: - /// - /// - `chain_id` - /// - `nonce` - /// - `max_priority_fee_per_gas` - /// - `max_fee_per_gas` - /// - `gas_limit` - /// - `to` - /// - `value` - /// - `data` (`input`) - /// - `access_list` - /// - `max_fee_per_blob_gas` - /// - `blob_versioned_hashes` - pub fn decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { - Ok(Self { - chain_id: Decodable::decode(buf)?, - nonce: Decodable::decode(buf)?, - max_priority_fee_per_gas: Decodable::decode(buf)?, - max_fee_per_gas: Decodable::decode(buf)?, - gas_limit: Decodable::decode(buf)?, - to: Decodable::decode(buf)?, - value: Decodable::decode(buf)?, - input: Decodable::decode(buf)?, - access_list: Decodable::decode(buf)?, - max_fee_per_blob_gas: Decodable::decode(buf)?, - blob_versioned_hashes: Decodable::decode(buf)?, - }) + /// Get transaction type. + #[doc(alias = "transaction_type")] + pub const fn tx_type() -> TxType { + TxType::Eip4844 } - /// Outputs the length of the transaction's fields, without a RLP header. - #[doc(hidden)] - pub fn fields_len(&self) -> usize { + /// Calculates a heuristic for the in-memory size of the [TxEip4844Variant] transaction. + #[inline] + pub fn size(&self) -> usize { + mem::size_of::() + // chain_id + mem::size_of::() + // nonce + mem::size_of::() + // gas_limit + mem::size_of::() + // max_fee_per_gas + mem::size_of::() + // max_priority_fee_per_gas + mem::size_of::
() + // to + mem::size_of::() + // value + self.access_list.size() + // access_list + self.input.len() + // input + self.blob_versioned_hashes.capacity() * mem::size_of::() + // blob hashes size + mem::size_of::() // max_fee_per_data_gas + } +} + +impl RlpEcdsaTx for TxEip4844 { + const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 }; + + fn rlp_encoded_fields_length(&self) -> usize { let mut len = 0; len += self.chain_id.length(); len += self.nonce.length(); @@ -512,8 +546,7 @@ impl TxEip4844 { len } - /// Encodes only the transaction's fields into the desired buffer, without a RLP header. - pub(crate) fn encode_fields(&self, out: &mut dyn BufMut) { + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { self.chain_id.encode(out); self.nonce.encode(out); self.max_priority_fee_per_gas.encode(out); @@ -527,142 +560,20 @@ impl TxEip4844 { self.blob_versioned_hashes.encode(out); } - /// Calculates a heuristic for the in-memory size of the [TxEip4844Variant] transaction. - #[inline] - pub fn size(&self) -> usize { - mem::size_of::() + // chain_id - mem::size_of::() + // nonce - mem::size_of::() + // gas_limit - mem::size_of::() + // max_fee_per_gas - mem::size_of::() + // max_priority_fee_per_gas - mem::size_of::
() + // to - mem::size_of::() + // value - self.access_list.size() + // access_list - self.input.len() + // input - self.blob_versioned_hashes.capacity() * mem::size_of::() + // blob hashes size - mem::size_of::() // max_fee_per_data_gas - } - - /// Returns what the encoded length should be, if the transaction were RLP encoded with the - /// given signature, depending on the value of `with_header`. - /// - /// If `with_header` is `true`, the payload length will include the RLP header length. - /// If `with_header` is `false`, the payload length will not include the RLP header length. - pub fn encoded_len_with_signature(&self, signature: &S, with_header: bool) -> usize - where - S: EncodableSignature, - { - // this counts the tx fields and signature fields - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - - // this counts: - // * tx type byte - // * inner header length - // * inner payload length - let inner_payload_length = - 1 + Header { list: true, payload_length }.length() + payload_length; - - if with_header { - // header length plus length of the above, wrapped with a string header - Header { list: false, payload_length: inner_payload_length }.length() - + inner_payload_length - } else { - inner_payload_length - } - } - - /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating - /// hash that for eip2718 does not require a rlp header - #[doc(hidden)] - pub fn encode_with_signature(&self, signature: &S, out: &mut dyn BufMut, with_header: bool) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - if with_header { - Header { - list: false, - payload_length: 1 + Header { list: true, payload_length }.length() + payload_length, - } - .encode(out); - } - out.put_u8(self.tx_type() as u8); - self.encode_with_signature_fields(signature, out); - } - - /// Encodes the transaction from RLP bytes, including the signature. This __does not__ encode a - /// tx type byte or string header. - /// - /// This __does__ encode a list header and include a signature. - pub fn encode_with_signature_fields(&self, signature: &S, out: &mut dyn BufMut) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - let header = Header { list: true, payload_length }; - header.encode(out); - self.encode_fields(out); - signature.write_rlp_vrs(out); - } - - /// Decodes the transaction from RLP bytes, including the signature. - /// - /// This __does not__ expect the bytes to start with a transaction type byte or string - /// header. - /// - /// This __does__ expect the bytes to start with a list header and include a signature. - #[doc(hidden)] - pub fn decode_signed_fields(buf: &mut &[u8]) -> alloy_rlp::Result> { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - // record original length so we can check encoding - let original_len = buf.len(); - - let tx = Self::decode_fields(buf)?; - let signature = Signature::decode_rlp_vrs(buf)?; - - if !matches!(signature.v(), Parity::Parity(_)) { - return Err(alloy_rlp::Error::Custom("invalid parity for typed transaction")); - } - - let signed = tx.into_signed(signature); - if buf.len() + header.payload_length != original_len { - return Err(alloy_rlp::Error::ListLengthMismatch { - expected: header.payload_length, - got: original_len - buf.len(), - }); - } - - Ok(signed) - } - - /// Get transaction type. - #[doc(alias = "transaction_type")] - pub const fn tx_type(&self) -> TxType { - TxType::Eip4844 - } - - /// Encodes the EIP-4844 transaction in RLP for signing. - /// - /// This encodes the transaction as: - /// `tx_type || rlp(chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, - /// value, input, access_list, max_fee_per_blob_gas, blob_versioned_hashes)` - /// - /// Note that there is no rlp header before the transaction type byte. - pub fn encode_for_signing(&self, out: &mut dyn BufMut) { - out.put_u8(self.tx_type() as u8); - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); - } - - /// Outputs the length of the signature RLP encoding for the transaction. - pub fn payload_len_for_signature(&self) -> usize { - let payload_length = self.fields_len(); - // 'transaction type byte length' + 'header length' + 'payload length' - 1 + Header { list: true, payload_length }.length() + payload_length + fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self { + chain_id: Decodable::decode(buf)?, + nonce: Decodable::decode(buf)?, + max_priority_fee_per_gas: Decodable::decode(buf)?, + max_fee_per_gas: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + to: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + access_list: Decodable::decode(buf)?, + max_fee_per_blob_gas: Decodable::decode(buf)?, + blob_versioned_hashes: Decodable::decode(buf)?, + }) } } @@ -672,11 +583,12 @@ impl SignableTransaction for TxEip4844 { } fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { - self.encode_for_signing(out); + out.put_u8(Self::tx_type() as u8); + self.encode(out); } fn payload_len_for_signature(&self) -> usize { - self.payload_len_for_signature() + self.length() + 1 } fn into_signed(self, signature: Signature) -> Signed { @@ -684,11 +596,7 @@ impl SignableTransaction for TxEip4844 { // combination for an EIP-4844 transaction. V should indicate the y-parity of the // signature. let signature = signature.with_parity_bool(); - - let mut buf = Vec::with_capacity(self.encoded_len_with_signature(&signature, false)); - self.encode_with_signature(&signature, &mut buf, false); - let hash = keccak256(&buf); - + let hash = self.tx_hash(&signature); Signed::new_unchecked(self, signature, hash) } } @@ -757,26 +665,17 @@ impl Transaction for TxEip4844 { impl Encodable for TxEip4844 { fn encode(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); + self.rlp_encode(out); } fn length(&self) -> usize { - let payload_length = self.fields_len(); - length_of_length(payload_length) + payload_length + self.rlp_encoded_length() } } impl Decodable for TxEip4844 { - fn decode(data: &mut &[u8]) -> alloy_rlp::Result { - let header = Header::decode(data)?; - let remaining_len = data.len(); - - if header.payload_length > remaining_len { - return Err(alloy_rlp::Error::InputTooShort); - } - - Self::decode_fields(data) + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::rlp_decode(buf) } } @@ -831,8 +730,8 @@ impl TxEip4844WithSidecar { /// Get the transaction type. #[doc(alias = "transaction_type")] - pub const fn tx_type(&self) -> TxType { - self.tx.tx_type() + pub const fn tx_type() -> TxType { + TxEip4844::tx_type() } /// Get access to the inner tx [TxEip4844]. @@ -856,75 +755,6 @@ impl TxEip4844WithSidecar { pub fn into_parts(self) -> (TxEip4844, BlobTransactionSidecar) { (self.tx, self.sidecar) } - - /// Encodes the transaction from RLP bytes, including the signature. This __does not__ encode a - /// tx type byte or string header. - /// - /// This __does__ encode a list header and include a signature. - /// - /// This encodes the following: - /// `rlp([tx_payload, blobs, commitments, proofs])` - /// - /// where `tx_payload` is the RLP encoding of the [TxEip4844] transaction fields: - /// `rlp([chain_id, nonce, max_priority_fee_per_gas, ..., v, r, s])` - pub fn encode_with_signature_fields(&self, signature: &S, out: &mut dyn BufMut) - where - S: EncodableSignature, - { - let inner_payload_length = self.tx.fields_len() + signature.rlp_vrs_len(); - let inner_header = Header { list: true, payload_length: inner_payload_length }; - - let outer_payload_length = - inner_header.length() + inner_payload_length + self.sidecar.rlp_encoded_fields_length(); - let outer_header = Header { list: true, payload_length: outer_payload_length }; - - // write the two headers - outer_header.encode(out); - inner_header.encode(out); - - // now write the fields - self.tx.encode_fields(out); - signature.write_rlp_vrs(out); - self.sidecar.rlp_encode_fields(out); - } - - /// Decodes the transaction from RLP bytes, including the signature. - /// - /// This __does not__ expect the bytes to start with a transaction type byte or string - /// header. - /// - /// This __does__ expect the bytes to start with a list header and include a signature. - /// - /// This is the inverse of [TxEip4844WithSidecar::encode_with_signature_fields]. - #[doc(hidden)] - pub fn decode_signed_fields(buf: &mut &[u8]) -> alloy_rlp::Result> { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - // record original length so we can check encoding - let original_len = buf.len(); - - // decode the inner tx - let inner_tx = TxEip4844::decode_signed_fields(buf)?; - - // decode the sidecar - let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?; - - if buf.len() + header.payload_length != original_len { - return Err(alloy_rlp::Error::ListLengthMismatch { - expected: header.payload_length, - got: original_len - buf.len(), - }); - } - - let (tx, signature, hash) = inner_tx.into_parts(); - - // create unchecked signed tx because these checks should have happened during construction - // of `Signed` in `TxEip4844::decode_signed_fields` - Ok(Signed::new_unchecked(Self::from_tx_and_sidecar(tx, sidecar), signature, hash)) - } } impl SignableTransaction for TxEip4844WithSidecar { @@ -953,14 +783,8 @@ impl SignableTransaction for TxEip4844WithSidecar { // signature. let signature = signature.with_parity_bool(); - let mut buf = Vec::with_capacity(self.tx.encoded_len_with_signature(&signature, false)); - // The sidecar is NOT included in the signed payload, only the transaction fields and the - // type byte. Include the type byte. - // - // Include the transaction fields, making sure to __not__ use the sidecar, and __not__ - // encode a header. - self.tx.encode_with_signature(&signature, &mut buf, false); - let hash = keccak256(&buf); + // important: must hash the tx WITHOUT the sidecar + let hash = self.tx.tx_hash(&signature); Signed::new_unchecked(self, signature, hash) } @@ -1028,6 +852,58 @@ impl Transaction for TxEip4844WithSidecar { } } +impl RlpEcdsaTx for TxEip4844WithSidecar { + const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 }; + + fn rlp_encoded_fields_length(&self) -> usize { + self.sidecar.rlp_encoded_fields_length() + self.tx.rlp_encoded_length() + } + + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + self.tx.rlp_encode(out); + self.sidecar.rlp_encode_fields(out); + } + + fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) { + self.rlp_header_signed(signature).encode(out); + self.tx.rlp_encode_signed(signature, out); + self.sidecar.rlp_encode_fields(out); + } + + fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + let tx = TxEip4844::rlp_decode(buf)?; + let sidecar = BlobTransactionSidecar::rlp_decode_fields(buf)?; + Ok(Self { tx, sidecar }) + } + + fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let remaining_len = buf.len(); + if header.payload_length > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); + } + + let chunk = &mut &buf[..remaining_len]; + let (tx, signature) = TxEip4844::rlp_decode_with_signature(chunk)?; + let sidecar = BlobTransactionSidecar::rlp_decode_fields(chunk)?; + + // Decoding did not consume the entire payload specified by the header + if !chunk.is_empty() { + return Err(alloy_rlp::Error::UnexpectedLength); + } + buf.advance(header.payload_length); + + Ok((Self { tx, sidecar }, signature)) + } + + fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> alloy_primitives::TxHash { + self.tx.tx_hash_with_type(signature, ty) + } +} + #[cfg(test)] mod tests { use super::{BlobTransactionSidecar, TxEip4844, TxEip4844WithSidecar}; diff --git a/crates/consensus/src/transaction/eip7702.rs b/crates/consensus/src/transaction/eip7702.rs index 50ead9215b9..d428166b116 100644 --- a/crates/consensus/src/transaction/eip7702.rs +++ b/crates/consensus/src/transaction/eip7702.rs @@ -1,13 +1,15 @@ -use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; +use crate::{SignableTransaction, Signed, Transaction, TxType}; use alloc::vec::Vec; use alloy_eips::{ eip2930::AccessList, eip7702::{constants::EIP7702_TX_TYPE_ID, SignedAuthorization}, }; -use alloy_primitives::{keccak256, Address, Bytes, ChainId, Parity, Signature, TxKind, B256, U256}; -use alloy_rlp::{BufMut, Decodable, Encodable, Header}; +use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256}; +use alloy_rlp::{BufMut, Decodable, Encodable}; use core::mem; +use super::RlpEcdsaTx; + /// A transaction with a priority fee ([EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)). #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] @@ -94,39 +96,34 @@ impl TxEip7702 { } } - /// Decodes the inner [TxEip7702] fields from RLP bytes. - /// - /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following - /// RLP fields in the following order: - /// - /// - `chain_id` - /// - `nonce` - /// - `max_priority_fee_per_gas` - /// - `max_fee_per_gas` - /// - `gas_limit` - /// - `to` - /// - `value` - /// - `data` (`input`) - /// - `access_list` - /// - `authorization_list` - pub fn decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { - Ok(Self { - chain_id: Decodable::decode(buf)?, - nonce: Decodable::decode(buf)?, - max_priority_fee_per_gas: Decodable::decode(buf)?, - max_fee_per_gas: Decodable::decode(buf)?, - gas_limit: Decodable::decode(buf)?, - to: Decodable::decode(buf)?, - value: Decodable::decode(buf)?, - input: Decodable::decode(buf)?, - access_list: Decodable::decode(buf)?, - authorization_list: Decodable::decode(buf)?, - }) + /// Get the transaction type. + #[doc(alias = "transaction_type")] + pub const fn tx_type() -> TxType { + TxType::Eip7702 + } + + /// Calculates a heuristic for the in-memory size of the [TxEip7702] transaction. + #[inline] + pub fn size(&self) -> usize { + mem::size_of::() + // chain_id + mem::size_of::() + // nonce + mem::size_of::() + // gas_limit + mem::size_of::() + // max_fee_per_gas + mem::size_of::() + // max_priority_fee_per_gas + mem::size_of::
() + // to + mem::size_of::() + // value + self.access_list.size() + // access_list + self.input.len() + // input + self.authorization_list.capacity() * mem::size_of::() // authorization_list } +} + +impl RlpEcdsaTx for TxEip7702 { + const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 }; /// Outputs the length of the transaction's fields, without a RLP header. #[doc(hidden)] - pub fn fields_len(&self) -> usize { + fn rlp_encoded_fields_length(&self) -> usize { let mut len = 0; len += self.chain_id.length(); len += self.nonce.length(); @@ -141,8 +138,7 @@ impl TxEip7702 { len } - /// Encodes only the transaction's fields into the desired buffer, without a RLP header. - pub(crate) fn encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { self.chain_id.encode(out); self.nonce.encode(out); self.max_priority_fee_per_gas.encode(out); @@ -155,121 +151,19 @@ impl TxEip7702 { self.authorization_list.encode(out); } - /// Returns what the encoded length should be, if the transaction were RLP encoded with the - /// given signature, depending on the value of `with_header`. - /// - /// If `with_header` is `true`, the payload length will include the RLP header length. - /// If `with_header` is `false`, the payload length will not include the RLP header length. - pub fn encoded_len_with_signature(&self, signature: &S, with_header: bool) -> usize - where - S: EncodableSignature, - { - // this counts the tx fields and signature fields - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - - // this counts: - // * tx type byte - // * inner header length - // * inner payload length - let inner_payload_length = - 1 + Header { list: true, payload_length }.length() + payload_length; - - if with_header { - // header length plus length of the above, wrapped with a string header - Header { list: false, payload_length: inner_payload_length }.length() - + inner_payload_length - } else { - inner_payload_length - } - } - - /// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating - /// hash that for eip2718 does not require a rlp header. - #[doc(hidden)] - pub fn encode_with_signature(&self, signature: &S, out: &mut dyn BufMut, with_header: bool) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - if with_header { - Header { - list: false, - payload_length: 1 + Header { list: true, payload_length }.length() + payload_length, - } - .encode(out); - } - out.put_u8(EIP7702_TX_TYPE_ID); - self.encode_with_signature_fields(signature, out); - } - - /// Decodes the transaction from RLP bytes, including the signature. - /// - /// This __does not__ expect the bytes to start with a transaction type byte or string - /// header. - /// - /// This __does__ expect the bytes to start with a list header and include a signature. - #[doc(hidden)] - pub fn decode_signed_fields(buf: &mut &[u8]) -> alloy_rlp::Result> { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - // record original length so we can check encoding - let original_len = buf.len(); - - let tx = Self::decode_fields(buf)?; - let signature = Signature::decode_rlp_vrs(buf)?; - - if !matches!(signature.v(), Parity::Parity(_)) { - return Err(alloy_rlp::Error::Custom("invalid parity for typed transaction")); - } - - let signed = tx.into_signed(signature); - if buf.len() + header.payload_length != original_len { - return Err(alloy_rlp::Error::ListLengthMismatch { - expected: header.payload_length, - got: original_len - buf.len(), - }); - } - - Ok(signed) - } - - /// Encodes the transaction from RLP bytes, including the signature. This __does not__ encode a - /// tx type byte or string header. - /// - /// This __does__ encode a list header and include a signature. - pub fn encode_with_signature_fields(&self, signature: &S, out: &mut dyn BufMut) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - let header = Header { list: true, payload_length }; - header.encode(out); - self.encode_fields(out); - signature.write_rlp_vrs(out); - } - - /// Get transaction type - #[doc(alias = "transaction_type")] - pub const fn tx_type(&self) -> TxType { - TxType::Eip7702 - } - - /// Calculates a heuristic for the in-memory size of the [TxEip7702] transaction. - #[inline] - pub fn size(&self) -> usize { - mem::size_of::() + // chain_id - mem::size_of::() + // nonce - mem::size_of::() + // gas_limit - mem::size_of::() + // max_fee_per_gas - mem::size_of::() + // max_priority_fee_per_gas - mem::size_of::
() + // to - mem::size_of::() + // value - self.access_list.size() + // access_list - self.input.len() + // input - self.authorization_list.capacity() * mem::size_of::() // authorization_list + fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self { + chain_id: Decodable::decode(buf)?, + nonce: Decodable::decode(buf)?, + max_priority_fee_per_gas: Decodable::decode(buf)?, + max_fee_per_gas: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + to: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + access_list: Decodable::decode(buf)?, + authorization_list: Decodable::decode(buf)?, + }) } } @@ -354,37 +248,25 @@ impl SignableTransaction for TxEip7702 { // combination for an EIP-7702 transaction. V should indicate the y-parity of the // signature. let signature = signature.with_parity_bool(); + let tx_hash = self.tx_hash(&signature); - let mut buf = Vec::with_capacity(self.encoded_len_with_signature(&signature, false)); - self.encode_with_signature(&signature, &mut buf, false); - let hash = keccak256(&buf); - - Signed::new_unchecked(self, signature, hash) + Signed::new_unchecked(self, signature, tx_hash) } } impl Encodable for TxEip7702 { fn encode(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() }.encode(out); - self.encode_fields(out); + self.rlp_encode(out); } fn length(&self) -> usize { - let payload_length = self.fields_len(); - Header { list: true, payload_length }.length() + payload_length + self.rlp_encoded_length() } } impl Decodable for TxEip7702 { fn decode(data: &mut &[u8]) -> alloy_rlp::Result { - let header = Header::decode(data)?; - let remaining_len = data.len(); - - if header.payload_length > remaining_len { - return Err(alloy_rlp::Error::InputTooShort); - } - - Self::decode_fields(data) + Self::rlp_decode(data) } } @@ -512,7 +394,7 @@ pub(super) mod serde_bincode_compat { #[cfg(all(test, feature = "k256"))] mod tests { - use super::TxEip7702; + use super::*; use crate::SignableTransaction; use alloy_eips::eip2930::AccessList; use alloy_primitives::{address, b256, hex, Address, Signature, U256}; @@ -540,8 +422,8 @@ mod tests { .unwrap(); let mut buf = vec![]; - tx.encode_with_signature_fields(&sig, &mut buf); - let decoded = TxEip7702::decode_signed_fields(&mut &buf[..]).unwrap(); + tx.rlp_encode_signed(&sig, &mut buf); + let decoded = TxEip7702::rlp_decode_signed(&mut &buf[..]).unwrap(); assert_eq!(decoded, tx.into_signed(sig)); } @@ -567,8 +449,8 @@ mod tests { ) .unwrap(); let mut buf = vec![]; - tx.encode_with_signature_fields(&sig, &mut buf); - let decoded = TxEip7702::decode_signed_fields(&mut &buf[..]).unwrap(); + tx.rlp_encode_signed(&sig, &mut buf); + let decoded = TxEip7702::rlp_decode_signed(&mut &buf[..]).unwrap(); assert_eq!(decoded, tx.into_signed(sig)); } @@ -595,8 +477,8 @@ mod tests { .unwrap(); let mut buf = vec![]; - tx.encode_with_signature_fields(&sig, &mut buf); - let decoded = TxEip7702::decode_signed_fields(&mut &buf[..]).unwrap(); + tx.rlp_encode_signed(&sig, &mut buf); + let decoded = TxEip7702::rlp_decode_signed(&mut &buf[..]).unwrap(); assert_eq!(decoded, tx.into_signed(sig)); } } diff --git a/crates/consensus/src/transaction/envelope.rs b/crates/consensus/src/transaction/envelope.rs index 14768f55a83..196f6d86adf 100644 --- a/crates/consensus/src/transaction/envelope.rs +++ b/crates/consensus/src/transaction/envelope.rs @@ -1,14 +1,17 @@ -use core::fmt; - -use crate::{Signed, Transaction, TxEip1559, TxEip2930, TxEip7702, TxLegacy}; +use crate::{ + transaction::{ + eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, + RlpEcdsaTx, + }, + Signed, Transaction, TxEip1559, TxEip2930, TxEip7702, TxLegacy, +}; use alloy_eips::{ eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}, eip2930::AccessList, }; use alloy_primitives::{Bytes, TxKind, B256}; -use alloy_rlp::{Decodable, Encodable, Header}; - -use crate::transaction::eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}; +use alloy_rlp::{Decodable, Encodable}; +use core::fmt; /// Ethereum `TransactionType` flags as specified in EIPs [2718], [1559], [2930], /// [4844], and [7702]. @@ -277,25 +280,14 @@ impl TxEnvelope { } /// Return the length of the inner txn, including type byte length - pub fn rlp_payload_length(&self) -> usize { - let payload_length = match self { - Self::Legacy(t) => t.tx().fields_len() + t.signature().rlp_vrs_len(), - Self::Eip2930(t) => t.tx().fields_len() + t.signature().rlp_vrs_len(), - Self::Eip1559(t) => t.tx().fields_len() + t.signature().rlp_vrs_len(), - Self::Eip4844(t) => match t.tx() { - TxEip4844Variant::TxEip4844(tx) => tx.fields_len() + t.signature().rlp_vrs_len(), - TxEip4844Variant::TxEip4844WithSidecar(tx) => { - let inner_payload_length = tx.tx().fields_len() + t.signature().rlp_vrs_len(); - let inner_header = Header { list: true, payload_length: inner_payload_length }; - - inner_header.length() - + inner_payload_length - + tx.sidecar.rlp_encoded_fields_length() - } - }, - Self::Eip7702(t) => t.tx().fields_len() + t.signature().rlp_vrs_len(), - }; - Header { list: true, payload_length }.length() + payload_length + !self.is_legacy() as usize + pub fn eip2718_encoded_length(&self) -> usize { + match self { + Self::Legacy(t) => t.eip2718_encoded_length(), + Self::Eip2930(t) => t.eip2718_encoded_length(), + Self::Eip1559(t) => t.eip2718_encoded_length(), + Self::Eip4844(t) => t.eip2718_encoded_length(), + Self::Eip7702(t) => t.eip2718_encoded_length(), + } } } @@ -318,16 +310,16 @@ impl Decodable for TxEnvelope { impl Decodable2718 for TxEnvelope { fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result { match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("unexpected tx type"))? { - TxType::Eip2930 => Ok(TxEip2930::decode_signed_fields(buf)?.into()), - TxType::Eip1559 => Ok(TxEip1559::decode_signed_fields(buf)?.into()), - TxType::Eip4844 => Ok(TxEip4844Variant::decode_signed_fields(buf)?.into()), - TxType::Eip7702 => Ok(TxEip7702::decode_signed_fields(buf)?.into()), + TxType::Eip2930 => Ok(TxEip2930::rlp_decode_signed(buf)?.into()), + TxType::Eip1559 => Ok(TxEip1559::rlp_decode_signed(buf)?.into()), + TxType::Eip4844 => Ok(TxEip4844Variant::rlp_decode_signed(buf)?.into()), + TxType::Eip7702 => Ok(TxEip7702::rlp_decode_signed(buf)?.into()), TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)), } } fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result { - Ok(TxLegacy::decode_signed_fields(buf)?.into()) + TxLegacy::rlp_decode_signed(buf).map(Into::into).map_err(Into::into) } } @@ -343,24 +335,24 @@ impl Encodable2718 for TxEnvelope { } fn encode_2718_len(&self) -> usize { - self.rlp_payload_length() + self.eip2718_encoded_length() } fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { match self { // Legacy transactions have no difference between network and 2718 - Self::Legacy(tx) => tx.tx().encode_with_signature_fields(tx.signature(), out), + Self::Legacy(tx) => tx.eip2718_encode(out), Self::Eip2930(tx) => { - tx.tx().encode_with_signature(tx.signature(), out, false); + tx.eip2718_encode(out); } Self::Eip1559(tx) => { - tx.tx().encode_with_signature(tx.signature(), out, false); + tx.eip2718_encode(out); } Self::Eip4844(tx) => { - tx.tx().encode_with_signature(tx.signature(), out, false); + tx.eip2718_encode(out); } Self::Eip7702(tx) => { - tx.tx().encode_with_signature(tx.signature(), out, false); + tx.eip2718_encode(out); } } } @@ -631,8 +623,8 @@ mod tests { fn test_decode_live_legacy_tx() { use alloy_primitives::address; - let raw_tx = alloy_primitives::hex::decode("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8").unwrap(); - let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); + let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8"); + let res = TxEnvelope::decode_2718(&mut raw_tx.as_ref()).unwrap(); assert_eq!(res.tx_type(), TxType::Legacy); let tx = match res { @@ -640,6 +632,8 @@ mod tests { _ => unreachable!(), }; + assert_eq!(tx.tx().chain_id(), Some(1)); + assert_eq!(tx.tx().to, TxKind::Call(address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D"))); assert_eq!( tx.hash().to_string(), @@ -659,7 +653,8 @@ mod tests { // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap(); - let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap(); + + let res = TxEnvelope::decode_2718(&mut raw_tx.as_slice()).unwrap(); assert_eq!(res.tx_type(), TxType::Eip4844); let tx = match res { @@ -754,6 +749,7 @@ mod tests { access_list: Default::default(), }; let signature = Signature::test_signature().with_parity(Parity::Eip155(42)); + test_encode_decode_roundtrip(tx, Some(signature)); } @@ -820,7 +816,17 @@ mod tests { }; let tx = TxEip4844WithSidecar { tx, sidecar }; let signature = Signature::test_signature().with_parity(Parity::Eip155(42)); - test_encode_decode_roundtrip(tx, Some(signature)); + + let tx_signed = tx.into_signed(signature); + let tx_envelope: TxEnvelope = tx_signed.into(); + + let mut out = Vec::new(); + tx_envelope.network_encode(&mut out); + let mut slice = out.as_slice(); + let decoded = TxEnvelope::network_decode(&mut slice).unwrap(); + assert_eq!(slice.len(), 0); + assert_eq!(out.len(), tx_envelope.network_len()); + assert_eq!(decoded, tx_envelope); } #[test] diff --git a/crates/consensus/src/transaction/legacy.rs b/crates/consensus/src/transaction/legacy.rs index 7377095e6b1..518e697dc19 100644 --- a/crates/consensus/src/transaction/legacy.rs +++ b/crates/consensus/src/transaction/legacy.rs @@ -1,11 +1,20 @@ -use core::mem; - +use crate::{transaction::RlpEcdsaTx, SignableTransaction, Signed, Transaction, TxType}; use alloc::vec::Vec; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; use alloy_primitives::{keccak256, Bytes, ChainId, Parity, Signature, TxKind, B256, U256}; use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable, Header, Result}; +use core::mem; -use crate::{EncodableSignature, SignableTransaction, Signed, Transaction, TxType}; +/// Enforce correct parity for legacy transactions (EIP-155, 27 or 28). +macro_rules! legacy_sig { + ($signature:expr) => { + if let Parity::Parity(parity) = $signature.v() { + &$signature.with_parity(Parity::NonEip155(parity)) + } else { + $signature + } + }; +} /// Legacy transaction. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] @@ -76,10 +85,39 @@ impl TxLegacy { self.input.len() // input } - /// Outputs the length of the transaction's fields, without a RLP header or length of the - /// eip155 fields. - #[doc(hidden)] - pub fn fields_len(&self) -> usize { + /// Outputs the length of EIP-155 fields. Only outputs a non-zero value for EIP-155 legacy + /// transactions. + pub(crate) fn eip155_fields_len(&self) -> usize { + self.chain_id.map_or( + // this is either a pre-EIP-155 legacy transaction or a typed transaction + 0, + // EIP-155 encodes the chain ID and two zeroes, so we add 2 to the length of the chain + // ID to get the length of all 3 fields + // len(chain_id) + (0x00) + (0x00) + |id| id.length() + 2, + ) + } + + /// Encodes EIP-155 arguments into the desired buffer. Only encodes values + /// for legacy transactions. + pub(crate) fn encode_eip155_signing_fields(&self, out: &mut dyn BufMut) { + // if this is a legacy transaction without a chain ID, it must be pre-EIP-155 + // and does not need to encode the chain ID for the signature hash encoding + if let Some(id) = self.chain_id { + // EIP-155 encodes the chain ID and two zeroes + id.encode(out); + 0x00u8.encode(out); + 0x00u8.encode(out); + } + } +} + +// Legacy transaction network and 2718 encodings are identical to the RLP +// encoding. +impl RlpEcdsaTx for TxLegacy { + const DEFAULT_TX_TYPE: u8 = { Self::TX_TYPE as u8 }; + + fn rlp_encoded_fields_length(&self) -> usize { let mut len = 0; len += self.nonce.length(); len += self.gas_price.length(); @@ -90,9 +128,7 @@ impl TxLegacy { len } - /// Encodes only the transaction's fields into the desired buffer, without a RLP header or - /// eip155 fields. - pub(crate) fn encode_fields(&self, out: &mut dyn BufMut) { + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { self.nonce.encode(out); self.gas_price.encode(out); self.gas_limit.encode(out); @@ -101,107 +137,100 @@ impl TxLegacy { self.input.0.encode(out); } - /// Encodes the transaction from RLP bytes, including the signature. This __does not__ encode a - /// tx type byte or string header. - /// - /// This __does__ encode a list header and include a signature. - pub fn encode_with_signature_fields(&self, signature: &S, out: &mut dyn alloy_rlp::BufMut) - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - let header = Header { list: true, payload_length }; - header.encode(out); - self.encode_fields(out); + fn rlp_encoded_length_with_signature(&self, signature: &Signature) -> usize { + // Enforce correct parity for legacy transactions (EIP-155, 27 or 28). + let signature = legacy_sig!(signature); + self.rlp_header_signed(signature).length_with_payload() + } + + fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) { + // Enforce correct parity for legacy transactions (EIP-155, 27 or 28). + let signature = legacy_sig!(signature); + self.rlp_header_signed(signature).encode(out); + self.rlp_encode_fields(out); signature.write_rlp_vrs(out); } - /// Returns what the encoded length should be, if the transaction were RLP encoded with the - /// given signature. - pub fn encoded_len_with_signature(&self, signature: &S) -> usize - where - S: EncodableSignature, - { - let payload_length = self.fields_len() + signature.rlp_vrs_len(); - Header { list: true, payload_length }.length() + payload_length + fn eip2718_encoded_length(&self, signature: &Signature) -> usize { + self.rlp_encoded_length_with_signature(signature) } - /// Encodes EIP-155 arguments into the desired buffer. Only encodes values - /// for legacy transactions. - pub(crate) fn encode_eip155_signing_fields(&self, out: &mut dyn BufMut) { - // if this is a legacy transaction without a chain ID, it must be pre-EIP-155 - // and does not need to encode the chain ID for the signature hash encoding - if let Some(id) = self.chain_id { - // EIP-155 encodes the chain ID and two zeroes - id.encode(out); - 0x00u8.encode(out); - 0x00u8.encode(out); - } + fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) { + self.rlp_encode_signed(signature, out); } - /// Outputs the length of EIP-155 fields. Only outputs a non-zero value for EIP-155 legacy - /// transactions. - pub(crate) fn eip155_fields_len(&self) -> usize { - self.chain_id.map_or( - // this is either a pre-EIP-155 legacy transaction or a typed transaction - 0, - // EIP-155 encodes the chain ID and two zeroes, so we add 2 to the length of the chain - // ID to get the length of all 3 fields - // len(chain_id) + (0x00) + (0x00) - |id| id.length() + 2, - ) + fn network_encoded_length(&self, signature: &Signature) -> usize { + self.rlp_encoded_length_with_signature(signature) } - /// Decodes the transaction from RLP bytes, including the signature. - /// - /// This __does not__ expect the bytes to start with a transaction type byte or string - /// header. - /// - /// This __does__ expect the bytes to start with a list header and include a signature. - #[doc(hidden)] - pub fn decode_signed_fields(buf: &mut &[u8]) -> alloy_rlp::Result> { + fn network_header(&self, signature: &Signature) -> Header { + self.rlp_header_signed(signature) + } + + fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) { + self.rlp_encode_signed(signature, out); + } + + fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self { + nonce: Decodable::decode(buf)?, + gas_price: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + to: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + chain_id: None, + }) + } + + fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> { let header = Header::decode(buf)?; if !header.list { return Err(alloy_rlp::Error::UnexpectedString); } - // record original length so we can check encoding - let original_len = buf.len(); - - let mut tx = Self::decode_fields(buf)?; + let remaining = buf.len(); + let mut tx = Self::rlp_decode_fields(buf)?; let signature = Signature::decode_rlp_vrs(buf)?; if !matches!(signature.v(), Parity::Eip155(_) | Parity::NonEip155(_)) { return Err(alloy_rlp::Error::Custom("invalid parity for legacy transaction")); } - // extract chain id from signature - let v = signature.v(); - tx.chain_id = v.chain_id(); + tx.chain_id = signature.v().chain_id(); - let signed = tx.into_signed(signature); - if buf.len() + header.payload_length != original_len { + if buf.len() + header.payload_length != remaining { return Err(alloy_rlp::Error::ListLengthMismatch { expected: header.payload_length, - got: original_len - buf.len(), + got: remaining - buf.len(), }); } - Ok(signed) + Ok((tx, signature)) } - /// Decode the RLP fields of the transaction, without decoding an RLP - /// header. - pub(crate) fn decode_fields(data: &mut &[u8]) -> Result { - Ok(Self { - nonce: Decodable::decode(data)?, - gas_price: Decodable::decode(data)?, - gas_limit: Decodable::decode(data)?, - to: Decodable::decode(data)?, - value: Decodable::decode(data)?, - input: Decodable::decode(data)?, - chain_id: None, - }) + fn eip2718_decode_with_type( + buf: &mut &[u8], + _ty: u8, + ) -> alloy_eips::eip2718::Eip2718Result> { + Self::rlp_decode_signed(buf).map_err(Into::into) + } + + fn eip2718_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result> { + Self::rlp_decode_signed(buf).map_err(Into::into) + } + + fn network_decode_with_type( + buf: &mut &[u8], + _ty: u8, + ) -> alloy_eips::eip2718::Eip2718Result> { + Self::rlp_decode_signed(buf).map_err(Into::into) + } + + fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> alloy_primitives::TxHash { + let mut buf = Vec::with_capacity(self.rlp_encoded_length_with_signature(signature)); + self.rlp_encode_signed(signature, &mut buf); + keccak256(&buf) } } @@ -277,16 +306,19 @@ impl SignableTransaction for TxLegacy { } fn encode_for_signing(&self, out: &mut dyn BufMut) { - Header { list: true, payload_length: self.fields_len() + self.eip155_fields_len() } - .encode(out); - self.encode_fields(out); + Header { + list: true, + payload_length: self.rlp_encoded_fields_length() + self.eip155_fields_len(), + } + .encode(out); + self.rlp_encode_fields(out); self.encode_eip155_signing_fields(out); } fn payload_len_for_signature(&self) -> usize { - let payload_length = self.fields_len() + self.eip155_fields_len(); + let payload_length = self.rlp_encoded_fields_length() + self.eip155_fields_len(); // 'header length' + 'payload length' - Header { list: true, payload_length }.length() + payload_length + Header { list: true, payload_length }.length_with_payload() } fn into_signed(self, signature: Signature) -> Signed { @@ -296,9 +328,8 @@ impl SignableTransaction for TxLegacy { } else { signature }; - let mut buf = Vec::with_capacity(self.encoded_len_with_signature(&signature)); - self.encode_with_signature_fields(&signature, &mut buf); - let hash = keccak256(&buf); + + let hash = self.tx_hash(&signature); Signed::new_unchecked(self, signature, hash) } } @@ -309,7 +340,7 @@ impl Encodable for TxLegacy { } fn length(&self) -> usize { - let payload_length = self.fields_len() + self.eip155_fields_len(); + let payload_length = self.rlp_encoded_fields_length() + self.eip155_fields_len(); // 'header length' + 'payload length' length_of_length(payload_length) + payload_length } @@ -326,7 +357,7 @@ impl Decodable for TxLegacy { return Err(alloy_rlp::Error::InputTooShort); } - let mut transaction = Self::decode_fields(data)?; + let mut transaction = Self::rlp_decode_fields(data)?; // If we still have data, it should be an eip-155 encoded chain_id if !data.is_empty() { @@ -495,12 +526,10 @@ mod tests { #[test] // Test vector from https://github.com/alloy-rs/alloy/issues/125 fn decode_legacy_and_recover_signer() { - let raw_tx = "f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8"; + use crate::transaction::RlpEcdsaTx; + let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8"); - let tx = TxLegacy::decode_signed_fields( - &mut alloy_primitives::hex::decode(raw_tx).unwrap().as_slice(), - ) - .unwrap(); + let tx = TxLegacy::rlp_decode_signed(&mut raw_tx.as_ref()).unwrap(); let recovered = tx.recover_signer().unwrap(); let expected = address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"); diff --git a/crates/consensus/src/transaction/mod.rs b/crates/consensus/src/transaction/mod.rs index 495e89780fe..a9a5c491f8a 100644 --- a/crates/consensus/src/transaction/mod.rs +++ b/crates/consensus/src/transaction/mod.rs @@ -32,6 +32,10 @@ pub use envelope::{TxEnvelope, TxType}; mod legacy; pub use legacy::TxLegacy; +mod rlp; +#[doc(hidden)] +pub use rlp::RlpEcdsaTx; + mod typed; pub use typed::TypedTransaction; diff --git a/crates/consensus/src/transaction/rlp.rs b/crates/consensus/src/transaction/rlp.rs new file mode 100644 index 00000000000..4c3a651f958 --- /dev/null +++ b/crates/consensus/src/transaction/rlp.rs @@ -0,0 +1,208 @@ +use crate::{SignableTransaction, Signed}; +use alloc::vec::Vec; +use alloy_eips::eip2718::{Eip2718Error, Eip2718Result}; +use alloy_primitives::{keccak256, Parity, Signature, TxHash}; +use alloy_rlp::{Buf, BufMut, Header}; + +/// Helper trait for managing RLP encoding of transactions inside 2718 +/// envelopes. +#[doc(hidden)] +pub trait RlpEcdsaTx: SignableTransaction + Sized { + /// The default transaction type for this transaction. + const DEFAULT_TX_TYPE: u8; + + /// Calculate the encoded length of the transaction's fields, without a RLP + /// header. + fn rlp_encoded_fields_length(&self) -> usize; + + /// Encodes only the transaction's fields into the desired buffer, without + /// a RLP header. + fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut); + + /// Create an list rlp header for the unsigned transaction. + fn rlp_header(&self) -> Header { + Header { list: true, payload_length: self.rlp_encoded_fields_length() } + } + + /// Get the length of the transaction when RLP encoded. + fn rlp_encoded_length(&self) -> usize { + self.rlp_header().length_with_payload() + } + + /// RLP encodes the transaction. + fn rlp_encode(&self, out: &mut dyn BufMut) { + self.rlp_header().encode(out); + self.rlp_encode_fields(out); + } + + /// Create an rlp list header for the signed transaction. + fn rlp_header_signed(&self, signature: &Signature) -> Header { + let payload_length = self.rlp_encoded_fields_length() + signature.rlp_vrs_len(); + Header { list: true, payload_length } + } + + /// Get the length of the transaction when RLP encoded with the given + /// signature. + fn rlp_encoded_length_with_signature(&self, signature: &Signature) -> usize { + self.rlp_header_signed(signature).length_with_payload() + } + + /// RLP encodes the transaction with the given signature. + fn rlp_encode_signed(&self, signature: &Signature, out: &mut dyn BufMut) { + self.rlp_header_signed(signature).encode(out); + self.rlp_encode_fields(out); + signature.write_rlp_vrs(out); + } + + /// Get the length of the transaction when EIP-2718 encoded. This is the + /// 1 byte type flag + the length of the RLP encoded transaction. + fn eip2718_encoded_length(&self, signature: &Signature) -> usize { + self.rlp_encoded_length_with_signature(signature) + 1 + } + + /// EIP-2718 encode the transaction with the given signature and type flag. + fn eip2718_encode_with_type(&self, signature: &Signature, ty: u8, out: &mut dyn BufMut) { + out.put_u8(ty); + self.rlp_encode_signed(signature, out); + } + + /// EIP-2718 encode the transaction with the given signature and the default + /// type flag. + fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) { + self.eip2718_encode_with_type(signature, Self::DEFAULT_TX_TYPE, out); + } + + /// Create an rlp header for the network encoded transaction. This will + /// usually be a string header, however, legacy transactions' network + /// encoding is a list. + fn network_header(&self, signature: &Signature) -> Header { + let payload_length = self.eip2718_encoded_length(signature); + Header { list: false, payload_length } + } + + /// Get the length of the transaction when network encoded. This is the + /// EIP-2718 encoded length with an outer RLP header. + fn network_encoded_length(&self, signature: &Signature) -> usize { + self.network_header(signature).length_with_payload() + } + + /// Network encode the transaction with the given signature. + fn network_encode_with_type(&self, signature: &Signature, ty: u8, out: &mut dyn BufMut) { + self.network_header(signature).encode(out); + self.eip2718_encode_with_type(signature, ty, out); + } + + /// Network encode the transaction with the given signature and the default + /// type flag. + fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) { + self.network_encode_with_type(signature, Self::DEFAULT_TX_TYPE, out); + } + + /// Decodes the fields of the transaction from RLP bytes. Do not decode a + /// header. You may assume the buffer is long enough to contain the + /// transaction. + fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result; + + /// Decodes the transaction from RLP bytes. + fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let remaining_len = buf.len(); + + if header.payload_length > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); + } + + Self::rlp_decode_fields(buf) + } + + /// Decodes the transaction from RLP bytes, including the signature. + fn rlp_decode_with_signature(buf: &mut &[u8]) -> alloy_rlp::Result<(Self, Signature)> { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + + let remaining = buf.len(); + let tx = Self::rlp_decode_fields(buf)?; + let signature = Signature::decode_rlp_vrs(buf)?; + + if !matches!(signature.v(), Parity::Parity(_)) { + return Err(alloy_rlp::Error::Custom( + "invalid parity for non-legacy typed transaction", + )); + } + + if buf.len() + header.payload_length != remaining { + return Err(alloy_rlp::Error::ListLengthMismatch { + expected: header.payload_length, + got: remaining - buf.len(), + }); + } + + Ok((tx, signature)) + } + + /// Decodes the transaction from RLP bytes, including the signature + /// Produces a [`Signed`]. + fn rlp_decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> { + Self::rlp_decode_with_signature(buf).map(|(tx, signature)| tx.into_signed(signature)) + } + + /// Decodes the transaction from eip2718 bytes, expecting the given type + /// flag. + fn eip2718_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result> { + let original_buf = *buf; + + if buf.remaining() < 1 { + return Err(alloy_rlp::Error::InputTooShort.into()); + } + let actual = buf.get_u8(); + if actual != ty { + return Err(Eip2718Error::UnexpectedType(actual)); + } + + // OPT: We avoid re-serializing by calculating the hash directly + // from the original buffer contents. + let (tx, signature) = Self::rlp_decode_with_signature(buf)?; + let total_len = tx.eip2718_encoded_length(&signature); + let hash = keccak256(&original_buf[..total_len]); + + Ok(Signed::new_unchecked(tx, signature, hash)) + } + + /// Decodes the transaction from eip2718 bytes, expecting the default type + /// flag. + fn eip2718_decode(buf: &mut &[u8]) -> Eip2718Result> { + Self::eip2718_decode_with_type(buf, Self::DEFAULT_TX_TYPE) + } + + /// Decodes the transaction from network bytes. + fn network_decode_with_type(buf: &mut &[u8], ty: u8) -> Eip2718Result> { + let header = Header::decode(buf)?; + if header.list { + return Err(alloy_rlp::Error::UnexpectedList.into()); + } + Self::eip2718_decode_with_type(buf, ty) + } + + /// Decodes the transaction from network bytes, expecting the default type + /// flag. + fn network_decode(buf: &mut &[u8]) -> Eip2718Result> { + Self::network_decode_with_type(buf, Self::DEFAULT_TX_TYPE) + } + + /// Calculate the transaction hash for the given signature and type. + fn tx_hash_with_type(&self, signature: &Signature, ty: u8) -> TxHash { + let mut buf = Vec::with_capacity(self.eip2718_encoded_length(signature)); + self.eip2718_encode_with_type(signature, ty, &mut buf); + keccak256(&buf) + } + + /// Calculate the transaction hash for the given signature. + fn tx_hash(&self, signature: &Signature) -> TxHash { + self.tx_hash_with_type(signature, Self::DEFAULT_TX_TYPE) + } +} diff --git a/crates/eips/src/eip2718.rs b/crates/eips/src/eip2718.rs index 82c11a1f759..2bab1d06c3f 100644 --- a/crates/eips/src/eip2718.rs +++ b/crates/eips/src/eip2718.rs @@ -138,13 +138,11 @@ pub trait Decodable2718: Sized { *buf = h_decode; let remaining_len = buf.len(); - if remaining_len == 0 || remaining_len < h.payload_length { return Err(alloy_rlp::Error::InputTooShort.into()); } - let ty = buf[0]; - buf.advance(1); + let ty = buf.get_u8(); let tx = Self::typed_decode(ty, buf)?; let bytes_consumed = remaining_len - buf.len(); diff --git a/crates/eips/src/eip4844/sidecar.rs b/crates/eips/src/eip4844/sidecar.rs index d39b9535dc6..a06d71d2b89 100644 --- a/crates/eips/src/eip4844/sidecar.rs +++ b/crates/eips/src/eip4844/sidecar.rs @@ -3,15 +3,13 @@ use crate::eip4844::{ kzg_to_versioned_hash, Blob, Bytes48, BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF, }; -use alloc::boxed::Box; +use alloc::{boxed::Box, vec::Vec}; use alloy_primitives::{bytes::BufMut, B256}; use alloy_rlp::{Decodable, Encodable, Header}; #[cfg(any(test, feature = "arbitrary"))] use crate::eip4844::MAX_BLOBS_PER_BLOCK; -use alloc::vec::Vec; - /// The versioned hash version for KZG. #[cfg(feature = "kzg")] pub(crate) const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;