From cf2ec22b60b02e7dcefff5267e7e32ee5522c93e Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sun, 27 Oct 2024 05:19:31 -0700 Subject: [PATCH 01/19] Update eip7685.rs --- crates/eips/src/eip7685.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index 0ffe72e1b90..95d350fafe5 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -76,7 +76,29 @@ impl Requests { self.0.extend(other.take()); } } +/// A list of requests or a precomputed requests hash. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PragueRequests { + /// Stores a list of requests for dynamic requests hash calculation. + Requests(Requests), + /// Stores a precomputed requests hash. + Hash(B256), +} +impl PragueRequests { + /// Returns the requests hash for the enum instance. + /// + /// - If the instance contains a list of requests, this function calculates the hash using + /// `requests_hash` of the `Requests` struct. + /// - If it contains a precomputed hash, it returns that hash directly. + #[cfg(feature = "sha2")] + pub fn requests_hash(&self) -> B256 { + match self { + Self::Requests(requests) => requests.requests_hash(), + Self::Hash(precomputed_hash) => *precomputed_hash, + } + } +} #[cfg(test)] mod tests { use super::*; From acd2f135e28e2649829853e273b364e26d095681 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:31:33 -0700 Subject: [PATCH 02/19] Update crates/eips/src/eip7685.rs Co-authored-by: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> --- crates/eips/src/eip7685.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index 95d350fafe5..aeb9ad35423 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -76,6 +76,7 @@ impl Requests { self.0.extend(other.take()); } } + /// A list of requests or a precomputed requests hash. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum PragueRequests { From 96fb47ce9b92d88d2e61798e3566d8da5883f94e Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:31:39 -0700 Subject: [PATCH 03/19] Update crates/eips/src/eip7685.rs Co-authored-by: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> --- crates/eips/src/eip7685.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index aeb9ad35423..d965f10066c 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -90,7 +90,7 @@ impl PragueRequests { /// Returns the requests hash for the enum instance. /// /// - If the instance contains a list of requests, this function calculates the hash using - /// `requests_hash` of the `Requests` struct. + /// `requests_hash` of the [`Requests`] struct. /// - If it contains a precomputed hash, it returns that hash directly. #[cfg(feature = "sha2")] pub fn requests_hash(&self) -> B256 { From 752dfce2372f877a1c2604af4c307bc499e53dbe Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:42:58 -0700 Subject: [PATCH 04/19] Update sidecar.rs --- crates/eips/src/eip4844/sidecar.rs | 534 +++-------------------------- 1 file changed, 39 insertions(+), 495 deletions(-) diff --git a/crates/eips/src/eip4844/sidecar.rs b/crates/eips/src/eip4844/sidecar.rs index 4aa98d19462..28f4d9253c3 100644 --- a/crates/eips/src/eip4844/sidecar.rs +++ b/crates/eips/src/eip4844/sidecar.rs @@ -1,516 +1,60 @@ -//! EIP-4844 sidecar type - -use crate::eip4844::{ - kzg_to_versioned_hash, Blob, Bytes48, BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF, -}; -use alloc::boxed::Box; -use alloy_primitives::{bytes::BufMut, B256}; -use alloy_rlp::{Decodable, Encodable, Header}; - -#[cfg(any(test, feature = "arbitrary"))] -use crate::eip4844::MAX_BLOBS_PER_BLOCK; +//! Contains helpers for dealing with additional parameters of `newPayload` requests. +use crate::{CancunPayloadFields, MaybeCancunPayloadFields}; use alloc::vec::Vec; +use alloy_eips::eip7685::{PragueRequests, Requests}; +use alloy_primitives::B256; -/// The versioned hash version for KZG. -#[cfg(feature = "kzg")] -pub(crate) const VERSIONED_HASH_VERSION_KZG: u8 = 0x01; - -/// A Blob hash -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct IndexedBlobHash { - /// The index of the blob - pub index: u64, - /// The hash of the blob - pub hash: B256, -} - -/// This represents a set of blobs, and its corresponding commitments and proofs. -/// -/// This type encodes and decodes the fields without an rlp header. -#[derive(Clone, Default, PartialEq, Eq, Hash)] -#[repr(C)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[doc(alias = "BlobTxSidecar")] -pub struct BlobTransactionSidecar { - /// The blob data. - #[cfg_attr( - all(debug_assertions, feature = "serde"), - serde(deserialize_with = "deserialize_blobs") - )] - pub blobs: Vec, - /// The blob commitments. - pub commitments: Vec, - /// The blob proofs. - pub proofs: Vec, -} - -impl core::fmt::Debug for BlobTransactionSidecar { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("BlobTransactionSidecar") - .field("blobs", &self.blobs.len()) - .field("commitments", &self.commitments) - .field("proofs", &self.proofs) - .finish() - } -} - -impl IntoIterator for BlobTransactionSidecar { - type Item = BlobTransactionSidecarItem; - type IntoIter = alloc::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.blobs - .into_iter() - .zip(self.commitments) - .zip(self.proofs) - .enumerate() - .map(|(index, ((blob, commitment), proof))| BlobTransactionSidecarItem { - index: index as u64, - blob: Box::new(blob), - kzg_commitment: commitment, - kzg_proof: proof, - }) - .collect::>() - .into_iter() - } -} - -/// A single blob sidecar. -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -#[repr(C)] +/// Container type for all available additional `newPayload` request parameters that are not present +/// in the `ExecutionPayload` object itself. +#[derive(Debug, Clone, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BlobTransactionSidecarItem { - /// The index of this item within the [BlobTransactionSidecar]. - #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] - pub index: u64, - /// The blob in this sidecar item. - #[cfg_attr(feature = "serde", serde(deserialize_with = "super::deserialize_blob"))] - pub blob: Box, - /// The KZG commitment. - pub kzg_commitment: Bytes48, - /// The KZG proof. - pub kzg_proof: Bytes48, +pub struct ExecutionPayloadSidecar { + /// Cancun request params introduced in `engine_newPayloadV3` that are not present in the + /// `ExecutionPayload`. + cancun: MaybeCancunPayloadFields, + /// The EIP-7685 requests provided as additional request params to `engine_newPayloadV4` that + /// are not present in the `ExecutionPayload`. + prague: Option, } -#[cfg(feature = "kzg")] -impl BlobTransactionSidecarItem { - /// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]` - pub fn to_kzg_versioned_hash(&self) -> [u8; 32] { - use sha2::Digest; - let commitment = self.kzg_commitment.as_slice(); - let mut hash: [u8; 32] = sha2::Sha256::digest(commitment).into(); - hash[0] = VERSIONED_HASH_VERSION_KZG; - hash +impl ExecutionPayloadSidecar { + /// Returns a new empty instance (pre-cancun, v1, v2) + pub const fn none() -> Self { + Self { cancun: MaybeCancunPayloadFields::none(), prague: None } } - /// Verifies the KZG proof of a blob to ensure its integrity and correctness. - pub fn verify_blob_kzg_proof(&self) -> Result<(), BlobTransactionValidationError> { - let binding = crate::eip4844::env_settings::EnvKzgSettings::Default; - let settings = binding.get(); - - let blob = c_kzg::Blob::from_bytes(self.blob.as_slice()) - .map_err(BlobTransactionValidationError::KZGError)?; - - let commitment = c_kzg::Bytes48::from_bytes(self.kzg_commitment.as_slice()) - .map_err(BlobTransactionValidationError::KZGError)?; - - let proof = c_kzg::Bytes48::from_bytes(self.kzg_proof.as_slice()) - .map_err(BlobTransactionValidationError::KZGError)?; - - let result = c_kzg::KzgProof::verify_blob_kzg_proof(&blob, &commitment, &proof, settings) - .map_err(BlobTransactionValidationError::KZGError)?; - - result.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof) + /// Creates a new instance for cancun with the cancun fields for `engine_newPayloadV3` + pub fn v3(cancun: CancunPayloadFields) -> Self { + Self { cancun: cancun.into(), prague: None } } - /// Verify the blob sidecar against its [IndexedBlobHash]. - pub fn verify_blob( - &self, - hash: &IndexedBlobHash, - ) -> Result<(), BlobTransactionValidationError> { - if self.index != hash.index { - let blob_hash_part = B256::from_slice(&self.blob[0..32]); - return Err(BlobTransactionValidationError::WrongVersionedHash { - have: blob_hash_part, - expected: hash.hash, - }); - } - - let computed_hash = self.to_kzg_versioned_hash(); - if computed_hash != hash.hash { - return Err(BlobTransactionValidationError::WrongVersionedHash { - have: computed_hash.into(), - expected: hash.hash, - }); - } - - self.verify_blob_kzg_proof() + /// Creates a new instance post prague for `engine_newPayloadV4` + pub fn v4(cancun: CancunPayloadFields, requests: PragueRequests) -> Self { + Self { cancun: cancun.into(), prague: Some(requests) } } -} -#[cfg(any(test, feature = "arbitrary"))] -impl<'a> arbitrary::Arbitrary<'a> for BlobTransactionSidecar { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let num_blobs = u.int_in_range(1..=MAX_BLOBS_PER_BLOCK)?; - let mut blobs = Vec::with_capacity(num_blobs); - for _ in 0..num_blobs { - blobs.push(Blob::arbitrary(u)?); - } - - let mut commitments = Vec::with_capacity(num_blobs); - let mut proofs = Vec::with_capacity(num_blobs); - for _ in 0..num_blobs { - commitments.push(Bytes48::arbitrary(u)?); - proofs.push(Bytes48::arbitrary(u)?); - } - - Ok(Self { blobs, commitments, proofs }) + /// Returns a reference to the [`CancunPayloadFields`]. + pub const fn cancun(&self) -> Option<&CancunPayloadFields> { + self.cancun.as_ref() } -} -impl BlobTransactionSidecar { - /// Constructs a new [BlobTransactionSidecar] from a set of blobs, commitments, and proofs. - pub const fn new(blobs: Vec, commitments: Vec, proofs: Vec) -> Self { - Self { blobs, commitments, proofs } + /// Returns the parent beacon block root, if any. + pub fn parent_beacon_block_root(&self) -> Option { + self.cancun.parent_beacon_block_root() } - /// Creates a new instance from the given KZG types. - #[cfg(feature = "kzg")] - pub fn from_kzg( - blobs: Vec, - commitments: Vec, - proofs: Vec, - ) -> Self { - // transmutes the vec of items, see also [core::mem::transmute](https://doc.rust-lang.org/std/mem/fn.transmute.html) - unsafe fn transmute_vec(input: Vec) -> Vec { - let mut v = core::mem::ManuallyDrop::new(input); - Vec::from_raw_parts(v.as_mut_ptr() as *mut U, v.len(), v.capacity()) - } - - // SAFETY: all types have the same size and alignment - unsafe { - let blobs = transmute_vec::(blobs); - let commitments = transmute_vec::(commitments); - let proofs = transmute_vec::(proofs); - Self { blobs, commitments, proofs } - } + /// Returns the blob versioned hashes, if any. + pub fn versioned_hashes(&self) -> Option<&Vec> { + self.cancun.versioned_hashes() } - /// Verifies that the versioned hashes are valid for this sidecar's blob data, commitments, and - /// proofs. - /// - /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters - /// derived from the KZG trusted setup. - /// - /// This ensures that the blob transaction payload has the same number of blob data elements, - /// commitments, and proofs. Each blob data element is verified against its commitment and - /// proof. - /// - /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response - /// fails to verify, or if the versioned hashes in the transaction do not match the actual - /// commitment versioned hashes. - #[cfg(feature = "kzg")] - pub fn validate( - &self, - blob_versioned_hashes: &[B256], - proof_settings: &c_kzg::KzgSettings, - ) -> Result<(), BlobTransactionValidationError> { - // Ensure the versioned hashes and commitments have the same length. - if blob_versioned_hashes.len() != self.commitments.len() { - return Err(c_kzg::Error::MismatchLength(format!( - "There are {} versioned commitment hashes and {} commitments", - blob_versioned_hashes.len(), - self.commitments.len() - )) - .into()); - } - - // calculate versioned hashes by zipping & iterating - for (versioned_hash, commitment) in - blob_versioned_hashes.iter().zip(self.commitments.iter()) - { - let commitment = c_kzg::KzgCommitment::from(commitment.0); - - // calculate & verify versioned hash - let calculated_versioned_hash = kzg_to_versioned_hash(commitment.as_slice()); - if *versioned_hash != calculated_versioned_hash { - return Err(BlobTransactionValidationError::WrongVersionedHash { - have: *versioned_hash, - expected: calculated_versioned_hash, - }); - } - } - - // SAFETY: ALL types have the same size - let res = unsafe { - c_kzg::KzgProof::verify_blob_kzg_proof_batch( - // blobs - core::mem::transmute::<&[Blob], &[c_kzg::Blob]>(self.blobs.as_slice()), - // commitments - core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.commitments.as_slice()), - // proofs - core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.proofs.as_slice()), - proof_settings, - ) + /// Returns the EIP-7685 requests + pub const fn requests(&self) -> Option<&Requests> { + if let Some(PragueRequests::Requests(ref requests)) = self.prague { + Some(requests) + } else { + None } - .map_err(BlobTransactionValidationError::KZGError)?; - - res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof) - } - - /// Returns an iterator over the versioned hashes of the commitments. - pub fn versioned_hashes(&self) -> impl Iterator + '_ { - self.commitments.iter().map(|c| kzg_to_versioned_hash(c.as_slice())) - } - - /// Returns the versioned hash for the blob at the given index, if it - /// exists. - pub fn versioned_hash_for_blob(&self, blob_index: usize) -> Option { - self.commitments.get(blob_index).map(|c| kzg_to_versioned_hash(c.as_slice())) - } - - /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecar]. - #[inline] - pub fn size(&self) -> usize { - self.blobs.len() * BYTES_PER_BLOB + // blobs - self.commitments.len() * BYTES_PER_COMMITMENT + // commitments - self.proofs.len() * BYTES_PER_PROOF // proofs - } - - /// Tries to create a new [`BlobTransactionSidecar`] from the given blobs. - #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))] - pub fn try_from_blobs(blobs: Vec) -> Result { - use crate::eip4844::env_settings::EnvKzgSettings; - use c_kzg::{KzgCommitment, KzgProof}; - - let kzg_settings = EnvKzgSettings::Default; - - let commitments = blobs - .iter() - .map(|blob| { - KzgCommitment::blob_to_kzg_commitment(&blob.clone(), kzg_settings.get()) - .map(|blob| blob.to_bytes()) - }) - .collect::, _>>()?; - - let proofs = blobs - .iter() - .zip(commitments.iter()) - .map(|(blob, commitment)| { - KzgProof::compute_blob_kzg_proof(blob, commitment, kzg_settings.get()) - .map(|blob| blob.to_bytes()) - }) - .collect::, _>>()?; - - Ok(Self::from_kzg(blobs, commitments, proofs)) - } - - /// Outputs the RLP length of the [BlobTransactionSidecar] fields, without - /// a RLP header. - #[doc(hidden)] - pub fn rlp_encoded_fields_length(&self) -> usize { - self.blobs.length() + self.commitments.length() + self.proofs.length() - } - - /// Encodes the inner [BlobTransactionSidecar] fields as RLP bytes, __without__ a RLP header. - /// - /// This encodes the fields in the following order: - /// - `blobs` - /// - `commitments` - /// - `proofs` - #[inline] - #[doc(hidden)] - pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) { - // Encode the blobs, commitments, and proofs - self.blobs.encode(out); - self.commitments.encode(out); - self.proofs.encode(out); - } - - /// Creates an RLP header for the [BlobTransactionSidecar]. - fn rlp_header(&self) -> Header { - Header { list: true, payload_length: self.rlp_encoded_fields_length() } - } - - /// Calculates the length of the [BlobTransactionSidecar] when encoded as - /// RLP. - pub fn rlp_encoded_length(&self) -> usize { - self.rlp_header().length() + self.rlp_encoded_fields_length() - } - - /// Encodes the [BlobTransactionSidecar] as RLP bytes. - pub fn rlp_encode(&self, out: &mut dyn BufMut) { - self.rlp_header().encode(out); - self.rlp_encode_fields(out); - } - - /// RLP decode the fields of a [BlobTransactionSidecar]. - #[doc(hidden)] - pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { - Ok(Self { - blobs: Decodable::decode(buf)?, - commitments: Decodable::decode(buf)?, - proofs: Decodable::decode(buf)?, - }) - } - - /// Decodes the [BlobTransactionSidecar] from RLP bytes. - pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let header = Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - if buf.len() < header.payload_length { - return Err(alloy_rlp::Error::InputTooShort); - } - Self::rlp_decode_fields(buf) - } -} - -impl Encodable for BlobTransactionSidecar { - /// Encodes the inner [BlobTransactionSidecar] fields as RLP bytes, without a RLP header. - fn encode(&self, out: &mut dyn BufMut) { - self.rlp_encode(out); - } - - fn length(&self) -> usize { - self.rlp_encoded_length() - } -} - -impl Decodable for BlobTransactionSidecar { - /// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header. - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - Self::rlp_decode(buf) - } -} - -// Helper function to deserialize boxed blobs -#[cfg(all(debug_assertions, feature = "serde"))] -fn deserialize_blobs<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::de::Deserializer<'de>, -{ - use serde::Deserialize; - - let raw_blobs = Vec::::deserialize(deserializer)?; - let mut blobs = Vec::with_capacity(raw_blobs.len()); - for blob in raw_blobs { - blobs.push(Blob::try_from(blob.as_ref()).map_err(serde::de::Error::custom)?); - } - Ok(blobs) -} - -/// An error that can occur when validating a [BlobTransactionSidecar::validate]. -#[derive(Debug)] -#[cfg(feature = "kzg")] -pub enum BlobTransactionValidationError { - /// Proof validation failed. - InvalidProof, - /// An error returned by [`c_kzg`]. - KZGError(c_kzg::Error), - /// The inner transaction is not a blob transaction. - NotBlobTransaction(u8), - /// Error variant for thrown by EIP-4844 tx variants without a sidecar. - MissingSidecar, - /// The versioned hash is incorrect. - WrongVersionedHash { - /// The versioned hash we got - have: B256, - /// The versioned hash we expected - expected: B256, - }, -} - -#[cfg(all(feature = "kzg", feature = "std"))] -impl std::error::Error for BlobTransactionValidationError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::KZGError(source) => Some(source), - Self::InvalidProof { .. } - | Self::NotBlobTransaction { .. } - | Self::MissingSidecar { .. } - | Self::WrongVersionedHash { .. } => None, - } - } -} - -#[cfg(feature = "kzg")] -impl core::fmt::Display for BlobTransactionValidationError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::InvalidProof => f.write_str("invalid KZG proof"), - Self::KZGError(err) => { - write!(f, "KZG error: {:?}", err) - } - Self::NotBlobTransaction(err) => { - write!(f, "unable to verify proof for non blob transaction: {}", err) - } - Self::MissingSidecar => { - f.write_str("eip4844 tx variant without sidecar being used for verification.") - } - Self::WrongVersionedHash { have, expected } => { - write!(f, "wrong versioned hash: have {}, expected {}", have, expected) - } - } - } -} - -#[cfg(feature = "kzg")] -impl From for BlobTransactionValidationError { - fn from(source: c_kzg::Error) -> Self { - Self::KZGError(source) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use arbitrary::Arbitrary; - - #[test] - #[cfg(feature = "serde")] - fn deserialize_blob() { - let blob = BlobTransactionSidecar { - blobs: vec![Blob::default(), Blob::default(), Blob::default(), Blob::default()], - commitments: vec![ - Bytes48::default(), - Bytes48::default(), - Bytes48::default(), - Bytes48::default(), - ], - proofs: vec![ - Bytes48::default(), - Bytes48::default(), - Bytes48::default(), - Bytes48::default(), - ], - }; - - let s = serde_json::to_string(&blob).unwrap(); - let deserialized: BlobTransactionSidecar = serde_json::from_str(&s).unwrap(); - assert_eq!(blob, deserialized); - } - - #[test] - fn test_arbitrary_blob() { - let mut unstructured = arbitrary::Unstructured::new(b"unstructured blob"); - let _blob = BlobTransactionSidecar::arbitrary(&mut unstructured).unwrap(); - } - - #[test] - fn test_blob_item_serde_roundtrip() { - let blob_item = BlobTransactionSidecarItem { - index: 0, - blob: Box::new(Blob::default()), - kzg_commitment: Bytes48::default(), - kzg_proof: Bytes48::default(), - }; - - let s = serde_json::to_string(&blob_item).unwrap(); - let deserialized: BlobTransactionSidecarItem = serde_json::from_str(&s).unwrap(); - assert_eq!(blob_item, deserialized); } } From eb88a9cbbc0539011c6603a026ed6c6c81d186b9 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:43:44 -0700 Subject: [PATCH 05/19] Update sidecar.rs --- crates/eips/src/eip4844/sidecar.rs | 534 ++++++++++++++++++++++++++--- 1 file changed, 495 insertions(+), 39 deletions(-) diff --git a/crates/eips/src/eip4844/sidecar.rs b/crates/eips/src/eip4844/sidecar.rs index 28f4d9253c3..4aa98d19462 100644 --- a/crates/eips/src/eip4844/sidecar.rs +++ b/crates/eips/src/eip4844/sidecar.rs @@ -1,60 +1,516 @@ -//! Contains helpers for dealing with additional parameters of `newPayload` requests. +//! EIP-4844 sidecar type + +use crate::eip4844::{ + kzg_to_versioned_hash, Blob, Bytes48, BYTES_PER_BLOB, BYTES_PER_COMMITMENT, BYTES_PER_PROOF, +}; +use alloc::boxed::Box; +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 crate::{CancunPayloadFields, MaybeCancunPayloadFields}; use alloc::vec::Vec; -use alloy_eips::eip7685::{PragueRequests, Requests}; -use alloy_primitives::B256; -/// Container type for all available additional `newPayload` request parameters that are not present -/// in the `ExecutionPayload` object itself. -#[derive(Debug, Clone, Default)] +/// The versioned hash version for KZG. +#[cfg(feature = "kzg")] +pub(crate) const VERSIONED_HASH_VERSION_KZG: u8 = 0x01; + +/// A Blob hash +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct IndexedBlobHash { + /// The index of the blob + pub index: u64, + /// The hash of the blob + pub hash: B256, +} + +/// This represents a set of blobs, and its corresponding commitments and proofs. +/// +/// This type encodes and decodes the fields without an rlp header. +#[derive(Clone, Default, PartialEq, Eq, Hash)] +#[repr(C)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[doc(alias = "BlobTxSidecar")] +pub struct BlobTransactionSidecar { + /// The blob data. + #[cfg_attr( + all(debug_assertions, feature = "serde"), + serde(deserialize_with = "deserialize_blobs") + )] + pub blobs: Vec, + /// The blob commitments. + pub commitments: Vec, + /// The blob proofs. + pub proofs: Vec, +} + +impl core::fmt::Debug for BlobTransactionSidecar { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("BlobTransactionSidecar") + .field("blobs", &self.blobs.len()) + .field("commitments", &self.commitments) + .field("proofs", &self.proofs) + .finish() + } +} + +impl IntoIterator for BlobTransactionSidecar { + type Item = BlobTransactionSidecarItem; + type IntoIter = alloc::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.blobs + .into_iter() + .zip(self.commitments) + .zip(self.proofs) + .enumerate() + .map(|(index, ((blob, commitment), proof))| BlobTransactionSidecarItem { + index: index as u64, + blob: Box::new(blob), + kzg_commitment: commitment, + kzg_proof: proof, + }) + .collect::>() + .into_iter() + } +} + +/// A single blob sidecar. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[repr(C)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ExecutionPayloadSidecar { - /// Cancun request params introduced in `engine_newPayloadV3` that are not present in the - /// `ExecutionPayload`. - cancun: MaybeCancunPayloadFields, - /// The EIP-7685 requests provided as additional request params to `engine_newPayloadV4` that - /// are not present in the `ExecutionPayload`. - prague: Option, +pub struct BlobTransactionSidecarItem { + /// The index of this item within the [BlobTransactionSidecar]. + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] + pub index: u64, + /// The blob in this sidecar item. + #[cfg_attr(feature = "serde", serde(deserialize_with = "super::deserialize_blob"))] + pub blob: Box, + /// The KZG commitment. + pub kzg_commitment: Bytes48, + /// The KZG proof. + pub kzg_proof: Bytes48, } -impl ExecutionPayloadSidecar { - /// Returns a new empty instance (pre-cancun, v1, v2) - pub const fn none() -> Self { - Self { cancun: MaybeCancunPayloadFields::none(), prague: None } +#[cfg(feature = "kzg")] +impl BlobTransactionSidecarItem { + /// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]` + pub fn to_kzg_versioned_hash(&self) -> [u8; 32] { + use sha2::Digest; + let commitment = self.kzg_commitment.as_slice(); + let mut hash: [u8; 32] = sha2::Sha256::digest(commitment).into(); + hash[0] = VERSIONED_HASH_VERSION_KZG; + hash } - /// Creates a new instance for cancun with the cancun fields for `engine_newPayloadV3` - pub fn v3(cancun: CancunPayloadFields) -> Self { - Self { cancun: cancun.into(), prague: None } + /// Verifies the KZG proof of a blob to ensure its integrity and correctness. + pub fn verify_blob_kzg_proof(&self) -> Result<(), BlobTransactionValidationError> { + let binding = crate::eip4844::env_settings::EnvKzgSettings::Default; + let settings = binding.get(); + + let blob = c_kzg::Blob::from_bytes(self.blob.as_slice()) + .map_err(BlobTransactionValidationError::KZGError)?; + + let commitment = c_kzg::Bytes48::from_bytes(self.kzg_commitment.as_slice()) + .map_err(BlobTransactionValidationError::KZGError)?; + + let proof = c_kzg::Bytes48::from_bytes(self.kzg_proof.as_slice()) + .map_err(BlobTransactionValidationError::KZGError)?; + + let result = c_kzg::KzgProof::verify_blob_kzg_proof(&blob, &commitment, &proof, settings) + .map_err(BlobTransactionValidationError::KZGError)?; + + result.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof) } - /// Creates a new instance post prague for `engine_newPayloadV4` - pub fn v4(cancun: CancunPayloadFields, requests: PragueRequests) -> Self { - Self { cancun: cancun.into(), prague: Some(requests) } + /// Verify the blob sidecar against its [IndexedBlobHash]. + pub fn verify_blob( + &self, + hash: &IndexedBlobHash, + ) -> Result<(), BlobTransactionValidationError> { + if self.index != hash.index { + let blob_hash_part = B256::from_slice(&self.blob[0..32]); + return Err(BlobTransactionValidationError::WrongVersionedHash { + have: blob_hash_part, + expected: hash.hash, + }); + } + + let computed_hash = self.to_kzg_versioned_hash(); + if computed_hash != hash.hash { + return Err(BlobTransactionValidationError::WrongVersionedHash { + have: computed_hash.into(), + expected: hash.hash, + }); + } + + self.verify_blob_kzg_proof() } +} - /// Returns a reference to the [`CancunPayloadFields`]. - pub const fn cancun(&self) -> Option<&CancunPayloadFields> { - self.cancun.as_ref() +#[cfg(any(test, feature = "arbitrary"))] +impl<'a> arbitrary::Arbitrary<'a> for BlobTransactionSidecar { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let num_blobs = u.int_in_range(1..=MAX_BLOBS_PER_BLOCK)?; + let mut blobs = Vec::with_capacity(num_blobs); + for _ in 0..num_blobs { + blobs.push(Blob::arbitrary(u)?); + } + + let mut commitments = Vec::with_capacity(num_blobs); + let mut proofs = Vec::with_capacity(num_blobs); + for _ in 0..num_blobs { + commitments.push(Bytes48::arbitrary(u)?); + proofs.push(Bytes48::arbitrary(u)?); + } + + Ok(Self { blobs, commitments, proofs }) } +} - /// Returns the parent beacon block root, if any. - pub fn parent_beacon_block_root(&self) -> Option { - self.cancun.parent_beacon_block_root() +impl BlobTransactionSidecar { + /// Constructs a new [BlobTransactionSidecar] from a set of blobs, commitments, and proofs. + pub const fn new(blobs: Vec, commitments: Vec, proofs: Vec) -> Self { + Self { blobs, commitments, proofs } } - /// Returns the blob versioned hashes, if any. - pub fn versioned_hashes(&self) -> Option<&Vec> { - self.cancun.versioned_hashes() + /// Creates a new instance from the given KZG types. + #[cfg(feature = "kzg")] + pub fn from_kzg( + blobs: Vec, + commitments: Vec, + proofs: Vec, + ) -> Self { + // transmutes the vec of items, see also [core::mem::transmute](https://doc.rust-lang.org/std/mem/fn.transmute.html) + unsafe fn transmute_vec(input: Vec) -> Vec { + let mut v = core::mem::ManuallyDrop::new(input); + Vec::from_raw_parts(v.as_mut_ptr() as *mut U, v.len(), v.capacity()) + } + + // SAFETY: all types have the same size and alignment + unsafe { + let blobs = transmute_vec::(blobs); + let commitments = transmute_vec::(commitments); + let proofs = transmute_vec::(proofs); + Self { blobs, commitments, proofs } + } } - /// Returns the EIP-7685 requests - pub const fn requests(&self) -> Option<&Requests> { - if let Some(PragueRequests::Requests(ref requests)) = self.prague { - Some(requests) - } else { - None + /// Verifies that the versioned hashes are valid for this sidecar's blob data, commitments, and + /// proofs. + /// + /// Takes as input the [KzgSettings](c_kzg::KzgSettings), which should contain the parameters + /// derived from the KZG trusted setup. + /// + /// This ensures that the blob transaction payload has the same number of blob data elements, + /// commitments, and proofs. Each blob data element is verified against its commitment and + /// proof. + /// + /// Returns [BlobTransactionValidationError::InvalidProof] if any blob KZG proof in the response + /// fails to verify, or if the versioned hashes in the transaction do not match the actual + /// commitment versioned hashes. + #[cfg(feature = "kzg")] + pub fn validate( + &self, + blob_versioned_hashes: &[B256], + proof_settings: &c_kzg::KzgSettings, + ) -> Result<(), BlobTransactionValidationError> { + // Ensure the versioned hashes and commitments have the same length. + if blob_versioned_hashes.len() != self.commitments.len() { + return Err(c_kzg::Error::MismatchLength(format!( + "There are {} versioned commitment hashes and {} commitments", + blob_versioned_hashes.len(), + self.commitments.len() + )) + .into()); + } + + // calculate versioned hashes by zipping & iterating + for (versioned_hash, commitment) in + blob_versioned_hashes.iter().zip(self.commitments.iter()) + { + let commitment = c_kzg::KzgCommitment::from(commitment.0); + + // calculate & verify versioned hash + let calculated_versioned_hash = kzg_to_versioned_hash(commitment.as_slice()); + if *versioned_hash != calculated_versioned_hash { + return Err(BlobTransactionValidationError::WrongVersionedHash { + have: *versioned_hash, + expected: calculated_versioned_hash, + }); + } + } + + // SAFETY: ALL types have the same size + let res = unsafe { + c_kzg::KzgProof::verify_blob_kzg_proof_batch( + // blobs + core::mem::transmute::<&[Blob], &[c_kzg::Blob]>(self.blobs.as_slice()), + // commitments + core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.commitments.as_slice()), + // proofs + core::mem::transmute::<&[Bytes48], &[c_kzg::Bytes48]>(self.proofs.as_slice()), + proof_settings, + ) } + .map_err(BlobTransactionValidationError::KZGError)?; + + res.then_some(()).ok_or(BlobTransactionValidationError::InvalidProof) + } + + /// Returns an iterator over the versioned hashes of the commitments. + pub fn versioned_hashes(&self) -> impl Iterator + '_ { + self.commitments.iter().map(|c| kzg_to_versioned_hash(c.as_slice())) + } + + /// Returns the versioned hash for the blob at the given index, if it + /// exists. + pub fn versioned_hash_for_blob(&self, blob_index: usize) -> Option { + self.commitments.get(blob_index).map(|c| kzg_to_versioned_hash(c.as_slice())) + } + + /// Calculates a size heuristic for the in-memory size of the [BlobTransactionSidecar]. + #[inline] + pub fn size(&self) -> usize { + self.blobs.len() * BYTES_PER_BLOB + // blobs + self.commitments.len() * BYTES_PER_COMMITMENT + // commitments + self.proofs.len() * BYTES_PER_PROOF // proofs + } + + /// Tries to create a new [`BlobTransactionSidecar`] from the given blobs. + #[cfg(all(feature = "kzg", any(test, feature = "arbitrary")))] + pub fn try_from_blobs(blobs: Vec) -> Result { + use crate::eip4844::env_settings::EnvKzgSettings; + use c_kzg::{KzgCommitment, KzgProof}; + + let kzg_settings = EnvKzgSettings::Default; + + let commitments = blobs + .iter() + .map(|blob| { + KzgCommitment::blob_to_kzg_commitment(&blob.clone(), kzg_settings.get()) + .map(|blob| blob.to_bytes()) + }) + .collect::, _>>()?; + + let proofs = blobs + .iter() + .zip(commitments.iter()) + .map(|(blob, commitment)| { + KzgProof::compute_blob_kzg_proof(blob, commitment, kzg_settings.get()) + .map(|blob| blob.to_bytes()) + }) + .collect::, _>>()?; + + Ok(Self::from_kzg(blobs, commitments, proofs)) + } + + /// Outputs the RLP length of the [BlobTransactionSidecar] fields, without + /// a RLP header. + #[doc(hidden)] + pub fn rlp_encoded_fields_length(&self) -> usize { + self.blobs.length() + self.commitments.length() + self.proofs.length() + } + + /// Encodes the inner [BlobTransactionSidecar] fields as RLP bytes, __without__ a RLP header. + /// + /// This encodes the fields in the following order: + /// - `blobs` + /// - `commitments` + /// - `proofs` + #[inline] + #[doc(hidden)] + pub fn rlp_encode_fields(&self, out: &mut dyn BufMut) { + // Encode the blobs, commitments, and proofs + self.blobs.encode(out); + self.commitments.encode(out); + self.proofs.encode(out); + } + + /// Creates an RLP header for the [BlobTransactionSidecar]. + fn rlp_header(&self) -> Header { + Header { list: true, payload_length: self.rlp_encoded_fields_length() } + } + + /// Calculates the length of the [BlobTransactionSidecar] when encoded as + /// RLP. + pub fn rlp_encoded_length(&self) -> usize { + self.rlp_header().length() + self.rlp_encoded_fields_length() + } + + /// Encodes the [BlobTransactionSidecar] as RLP bytes. + pub fn rlp_encode(&self, out: &mut dyn BufMut) { + self.rlp_header().encode(out); + self.rlp_encode_fields(out); + } + + /// RLP decode the fields of a [BlobTransactionSidecar]. + #[doc(hidden)] + pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self { + blobs: Decodable::decode(buf)?, + commitments: Decodable::decode(buf)?, + proofs: Decodable::decode(buf)?, + }) + } + + /// Decodes the [BlobTransactionSidecar] from RLP bytes. + pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + if buf.len() < header.payload_length { + return Err(alloy_rlp::Error::InputTooShort); + } + Self::rlp_decode_fields(buf) + } +} + +impl Encodable for BlobTransactionSidecar { + /// Encodes the inner [BlobTransactionSidecar] fields as RLP bytes, without a RLP header. + fn encode(&self, out: &mut dyn BufMut) { + self.rlp_encode(out); + } + + fn length(&self) -> usize { + self.rlp_encoded_length() + } +} + +impl Decodable for BlobTransactionSidecar { + /// Decodes the inner [BlobTransactionSidecar] fields from RLP bytes, without a RLP header. + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::rlp_decode(buf) + } +} + +// Helper function to deserialize boxed blobs +#[cfg(all(debug_assertions, feature = "serde"))] +fn deserialize_blobs<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + use serde::Deserialize; + + let raw_blobs = Vec::::deserialize(deserializer)?; + let mut blobs = Vec::with_capacity(raw_blobs.len()); + for blob in raw_blobs { + blobs.push(Blob::try_from(blob.as_ref()).map_err(serde::de::Error::custom)?); + } + Ok(blobs) +} + +/// An error that can occur when validating a [BlobTransactionSidecar::validate]. +#[derive(Debug)] +#[cfg(feature = "kzg")] +pub enum BlobTransactionValidationError { + /// Proof validation failed. + InvalidProof, + /// An error returned by [`c_kzg`]. + KZGError(c_kzg::Error), + /// The inner transaction is not a blob transaction. + NotBlobTransaction(u8), + /// Error variant for thrown by EIP-4844 tx variants without a sidecar. + MissingSidecar, + /// The versioned hash is incorrect. + WrongVersionedHash { + /// The versioned hash we got + have: B256, + /// The versioned hash we expected + expected: B256, + }, +} + +#[cfg(all(feature = "kzg", feature = "std"))] +impl std::error::Error for BlobTransactionValidationError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::KZGError(source) => Some(source), + Self::InvalidProof { .. } + | Self::NotBlobTransaction { .. } + | Self::MissingSidecar { .. } + | Self::WrongVersionedHash { .. } => None, + } + } +} + +#[cfg(feature = "kzg")] +impl core::fmt::Display for BlobTransactionValidationError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::InvalidProof => f.write_str("invalid KZG proof"), + Self::KZGError(err) => { + write!(f, "KZG error: {:?}", err) + } + Self::NotBlobTransaction(err) => { + write!(f, "unable to verify proof for non blob transaction: {}", err) + } + Self::MissingSidecar => { + f.write_str("eip4844 tx variant without sidecar being used for verification.") + } + Self::WrongVersionedHash { have, expected } => { + write!(f, "wrong versioned hash: have {}, expected {}", have, expected) + } + } + } +} + +#[cfg(feature = "kzg")] +impl From for BlobTransactionValidationError { + fn from(source: c_kzg::Error) -> Self { + Self::KZGError(source) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use arbitrary::Arbitrary; + + #[test] + #[cfg(feature = "serde")] + fn deserialize_blob() { + let blob = BlobTransactionSidecar { + blobs: vec![Blob::default(), Blob::default(), Blob::default(), Blob::default()], + commitments: vec![ + Bytes48::default(), + Bytes48::default(), + Bytes48::default(), + Bytes48::default(), + ], + proofs: vec![ + Bytes48::default(), + Bytes48::default(), + Bytes48::default(), + Bytes48::default(), + ], + }; + + let s = serde_json::to_string(&blob).unwrap(); + let deserialized: BlobTransactionSidecar = serde_json::from_str(&s).unwrap(); + assert_eq!(blob, deserialized); + } + + #[test] + fn test_arbitrary_blob() { + let mut unstructured = arbitrary::Unstructured::new(b"unstructured blob"); + let _blob = BlobTransactionSidecar::arbitrary(&mut unstructured).unwrap(); + } + + #[test] + fn test_blob_item_serde_roundtrip() { + let blob_item = BlobTransactionSidecarItem { + index: 0, + blob: Box::new(Blob::default()), + kzg_commitment: Bytes48::default(), + kzg_proof: Bytes48::default(), + }; + + let s = serde_json::to_string(&blob_item).unwrap(); + let deserialized: BlobTransactionSidecarItem = serde_json::from_str(&s).unwrap(); + assert_eq!(blob_item, deserialized); } } From 7cea94d747116fbe424b0985113a9f15bb20d440 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:44:27 -0700 Subject: [PATCH 06/19] Update sidecar.rs --- crates/rpc-types-engine/src/sidecar.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/rpc-types-engine/src/sidecar.rs b/crates/rpc-types-engine/src/sidecar.rs index a640508bcd8..28f4d9253c3 100644 --- a/crates/rpc-types-engine/src/sidecar.rs +++ b/crates/rpc-types-engine/src/sidecar.rs @@ -2,7 +2,7 @@ use crate::{CancunPayloadFields, MaybeCancunPayloadFields}; use alloc::vec::Vec; -use alloy_eips::eip7685::Requests; +use alloy_eips::eip7685::{PragueRequests, Requests}; use alloy_primitives::B256; /// Container type for all available additional `newPayload` request parameters that are not present @@ -15,7 +15,7 @@ pub struct ExecutionPayloadSidecar { cancun: MaybeCancunPayloadFields, /// The EIP-7685 requests provided as additional request params to `engine_newPayloadV4` that /// are not present in the `ExecutionPayload`. - prague: Option, + prague: Option, } impl ExecutionPayloadSidecar { @@ -30,7 +30,7 @@ impl ExecutionPayloadSidecar { } /// Creates a new instance post prague for `engine_newPayloadV4` - pub fn v4(cancun: CancunPayloadFields, requests: Requests) -> Self { + pub fn v4(cancun: CancunPayloadFields, requests: PragueRequests) -> Self { Self { cancun: cancun.into(), prague: Some(requests) } } @@ -51,6 +51,10 @@ impl ExecutionPayloadSidecar { /// Returns the EIP-7685 requests pub const fn requests(&self) -> Option<&Requests> { - self.prague.as_ref() + if let Some(PragueRequests::Requests(ref requests)) = self.prague { + Some(requests) + } else { + None + } } } From 5028a99838f551afed7b132a2dcf86548b2db596 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:46:53 -0700 Subject: [PATCH 07/19] Update eip7685.rs --- crates/eips/src/eip7685.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index d965f10066c..2c0030df913 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -5,6 +5,7 @@ use alloc::vec::Vec; use alloy_primitives::{b256, Bytes, B256}; use derive_more::{Deref, DerefMut, From, IntoIterator}; +use serde::{Deserialize, Serialize}; /// The empty requests hash. /// @@ -78,7 +79,7 @@ impl Requests { } /// A list of requests or a precomputed requests hash. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum PragueRequests { /// Stores a list of requests for dynamic requests hash calculation. Requests(Requests), From 0086673fb1b0a582532c44fbe755c349b6a1300d Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:50:24 -0700 Subject: [PATCH 08/19] Update eip7685.rs --- crates/eips/src/eip7685.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index 2c0030df913..e750445b467 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -79,7 +79,8 @@ impl Requests { } /// A list of requests or a precomputed requests hash. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum PragueRequests { /// Stores a list of requests for dynamic requests hash calculation. Requests(Requests), From c508d83ff90312acb57e48ecbaea6ea46f745731 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:53:26 -0700 Subject: [PATCH 09/19] Update eip7685.rs --- crates/eips/src/eip7685.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index e750445b467..de49344dea5 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use alloy_primitives::{b256, Bytes, B256}; use derive_more::{Deref, DerefMut, From, IntoIterator}; -use serde::{Deserialize, Serialize}; + /// The empty requests hash. /// @@ -80,7 +80,7 @@ impl Requests { /// A list of requests or a precomputed requests hash. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum PragueRequests { /// Stores a list of requests for dynamic requests hash calculation. Requests(Requests), From 57bc2ab27efa6c360902573a38bc40271d79ec21 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:55:21 -0700 Subject: [PATCH 10/19] Update eip7685.rs --- crates/eips/src/eip7685.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index de49344dea5..18c9d9f0871 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -6,7 +6,6 @@ use alloc::vec::Vec; use alloy_primitives::{b256, Bytes, B256}; use derive_more::{Deref, DerefMut, From, IntoIterator}; - /// The empty requests hash. /// /// This is equivalent to `sha256(sha256(0) ++ sha256(1) ++ sha256(2))` From 7e555bcd09a9a58ef54761a823b4ef2aecaf41ca Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sat, 2 Nov 2024 07:38:25 -0700 Subject: [PATCH 11/19] Update eip7685.rs --- crates/eips/src/eip7685.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index 18c9d9f0871..9523158905b 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -78,16 +78,21 @@ impl Requests { } /// A list of requests or a precomputed requests hash. +/// +/// For testing purposes, the `Hash` variant stores a precomputed requests hash. This can be useful +/// when the exact contents of the requests are unnecessary, and only a consistent hash value is +/// needed to simulate the presence of requests without holding actual data. + #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum PragueRequests { - /// Stores a list of requests for dynamic requests hash calculation. +pub enum RequestsOrHash { + /// Stores a list of requests, allowing for dynamic requests hash calculation. Requests(Requests), - /// Stores a precomputed requests hash. + /// Stores a precomputed requests hash, used primarily for testing. Hash(B256), } -impl PragueRequests { +impl RequestsOrHash { /// Returns the requests hash for the enum instance. /// /// - If the instance contains a list of requests, this function calculates the hash using From d2d3cc84242f3a7cf89a0adf2f851ce41a35c879 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sat, 2 Nov 2024 07:38:54 -0700 Subject: [PATCH 12/19] Update sidecar.rs --- crates/rpc-types-engine/src/sidecar.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/rpc-types-engine/src/sidecar.rs b/crates/rpc-types-engine/src/sidecar.rs index 28f4d9253c3..44a4331b462 100644 --- a/crates/rpc-types-engine/src/sidecar.rs +++ b/crates/rpc-types-engine/src/sidecar.rs @@ -2,7 +2,7 @@ use crate::{CancunPayloadFields, MaybeCancunPayloadFields}; use alloc::vec::Vec; -use alloy_eips::eip7685::{PragueRequests, Requests}; +use alloy_eips::eip7685::{Requests, RequestsOrHash}; use alloy_primitives::B256; /// Container type for all available additional `newPayload` request parameters that are not present @@ -15,7 +15,7 @@ pub struct ExecutionPayloadSidecar { cancun: MaybeCancunPayloadFields, /// The EIP-7685 requests provided as additional request params to `engine_newPayloadV4` that /// are not present in the `ExecutionPayload`. - prague: Option, + prague: Option, } impl ExecutionPayloadSidecar { @@ -30,7 +30,7 @@ impl ExecutionPayloadSidecar { } /// Creates a new instance post prague for `engine_newPayloadV4` - pub fn v4(cancun: CancunPayloadFields, requests: PragueRequests) -> Self { + pub fn v4(cancun: CancunPayloadFields, requests: RequestsOrHash) -> Self { Self { cancun: cancun.into(), prague: Some(requests) } } @@ -51,10 +51,20 @@ impl ExecutionPayloadSidecar { /// Returns the EIP-7685 requests pub const fn requests(&self) -> Option<&Requests> { - if let Some(PragueRequests::Requests(ref requests)) = self.prague { + if let Some(RequestsOrHash::Requests(ref requests)) = self.prague { Some(requests) } else { None } } + + /// Calculates or retrieves the requests hash. + /// + /// - If the `prague` field contains a list of requests, it calculates the requests hash + /// dynamically. + /// - If it contains a precomputed hash (used for testing), it returns that hash directly. + + pub fn requests_hash(&self) -> B256 { + self.prague.as_ref().unwrap().requests_hash() + } } From a4a008e292a517e8ebc491eb39d83b9c68080d0f Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sun, 17 Nov 2024 07:12:11 -0800 Subject: [PATCH 13/19] Update crates/eips/src/eip7685.rs Co-authored-by: Oliver --- crates/eips/src/eip7685.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index 9523158905b..3eb712b9e63 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -82,7 +82,6 @@ impl Requests { /// For testing purposes, the `Hash` variant stores a precomputed requests hash. This can be useful /// when the exact contents of the requests are unnecessary, and only a consistent hash value is /// needed to simulate the presence of requests without holding actual data. - #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RequestsOrHash { From 9d6a24300e711d1bed7b6e9cd134b021745c0518 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sun, 17 Nov 2024 07:17:11 -0800 Subject: [PATCH 14/19] Update sidecar.rs --- crates/rpc-types-engine/src/sidecar.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/rpc-types-engine/src/sidecar.rs b/crates/rpc-types-engine/src/sidecar.rs index 44a4331b462..5dcaf72ffe5 100644 --- a/crates/rpc-types-engine/src/sidecar.rs +++ b/crates/rpc-types-engine/src/sidecar.rs @@ -64,7 +64,10 @@ impl ExecutionPayloadSidecar { /// dynamically. /// - If it contains a precomputed hash (used for testing), it returns that hash directly. - pub fn requests_hash(&self) -> B256 { - self.prague.as_ref().unwrap().requests_hash() + pub fn requests_hash(&self) -> Option { + match self.prague.as_ref() { + Some(hash) => Some(hash.requests_hash()), + None => None, + } } } From 86c11a9ce8d13a3f58451cc699c12b590863d8b5 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sun, 17 Nov 2024 07:19:04 -0800 Subject: [PATCH 15/19] Update eip7685.rs --- crates/eips/src/eip7685.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index 3eb712b9e63..6b33a048411 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -80,8 +80,9 @@ impl Requests { /// A list of requests or a precomputed requests hash. /// /// For testing purposes, the `Hash` variant stores a precomputed requests hash. This can be useful -/// when the exact contents of the requests are unnecessary, and only a consistent hash value is +/// when the exact contents of the requests are unnecessary, and only a consistent hash value is /// needed to simulate the presence of requests without holding actual data. + #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RequestsOrHash { From 1b4082a2cffd759169cc433f0413dfbefc8ba86f Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sun, 17 Nov 2024 07:19:50 -0800 Subject: [PATCH 16/19] Update crates/rpc-types-engine/src/sidecar.rs Co-authored-by: Oliver --- crates/rpc-types-engine/src/sidecar.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/rpc-types-engine/src/sidecar.rs b/crates/rpc-types-engine/src/sidecar.rs index 5dcaf72ffe5..56dba04bf63 100644 --- a/crates/rpc-types-engine/src/sidecar.rs +++ b/crates/rpc-types-engine/src/sidecar.rs @@ -63,7 +63,6 @@ impl ExecutionPayloadSidecar { /// - If the `prague` field contains a list of requests, it calculates the requests hash /// dynamically. /// - If it contains a precomputed hash (used for testing), it returns that hash directly. - pub fn requests_hash(&self) -> Option { match self.prague.as_ref() { Some(hash) => Some(hash.requests_hash()), From fac623bfa809ce0e463b98d255c275551350539c Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sun, 17 Nov 2024 07:23:47 -0800 Subject: [PATCH 17/19] Update sidecar.rs --- crates/rpc-types-engine/src/sidecar.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/rpc-types-engine/src/sidecar.rs b/crates/rpc-types-engine/src/sidecar.rs index 56dba04bf63..9c6a148d580 100644 --- a/crates/rpc-types-engine/src/sidecar.rs +++ b/crates/rpc-types-engine/src/sidecar.rs @@ -64,9 +64,6 @@ impl ExecutionPayloadSidecar { /// dynamically. /// - If it contains a precomputed hash (used for testing), it returns that hash directly. pub fn requests_hash(&self) -> Option { - match self.prague.as_ref() { - Some(hash) => Some(hash.requests_hash()), - None => None, - } + self.prague.as_ref().map_or(None, |hash| Some(hash.requests_hash())) } } From 20e03d5ac512a8f55e808ca55ad106c9d02eefb0 Mon Sep 17 00:00:00 2001 From: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Date: Sun, 17 Nov 2024 07:27:34 -0800 Subject: [PATCH 18/19] Update sidecar.rs --- crates/rpc-types-engine/src/sidecar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc-types-engine/src/sidecar.rs b/crates/rpc-types-engine/src/sidecar.rs index 9c6a148d580..387579b801c 100644 --- a/crates/rpc-types-engine/src/sidecar.rs +++ b/crates/rpc-types-engine/src/sidecar.rs @@ -64,6 +64,6 @@ impl ExecutionPayloadSidecar { /// dynamically. /// - If it contains a precomputed hash (used for testing), it returns that hash directly. pub fn requests_hash(&self) -> Option { - self.prague.as_ref().map_or(None, |hash| Some(hash.requests_hash())) + self.prague.as_ref().map(|hash| hash.requests_hash()) } } From db00324af5b20eda5a95124257174a9a9c12f6d2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 27 Nov 2024 19:49:01 +0100 Subject: [PATCH 19/19] touchups --- crates/eips/src/eip7685.rs | 28 ++++++++++++++++++++++---- crates/rpc-types-engine/src/cancun.rs | 8 +++++++- crates/rpc-types-engine/src/prague.rs | 23 ++++++++++++++++++--- crates/rpc-types-engine/src/sidecar.rs | 15 +++++++------- 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/crates/eips/src/eip7685.rs b/crates/eips/src/eip7685.rs index 643eb8a973e..a1e334d46f6 100644 --- a/crates/eips/src/eip7685.rs +++ b/crates/eips/src/eip7685.rs @@ -79,14 +79,14 @@ impl Requests { /// /// For testing purposes, the `Hash` variant stores a precomputed requests hash. This can be useful /// when the exact contents of the requests are unnecessary, and only a consistent hash value is -/// needed to simulate the presence of requests without holding actual data. - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +/// needed to simulate the presence of requests without holding actual data. +#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum RequestsOrHash { /// Stores a list of requests, allowing for dynamic requests hash calculation. Requests(Requests), - /// Stores a precomputed requests hash, used primarily for testing. + /// Stores a precomputed requests hash, used primarily for testing or mocking because the + /// header only contains the hash. Hash(B256), } @@ -103,7 +103,27 @@ impl RequestsOrHash { Self::Hash(precomputed_hash) => *precomputed_hash, } } + + /// Returns an instance with the [`EMPTY_REQUESTS_HASH`]. + pub const fn empty() -> Self { + Self::Hash(EMPTY_REQUESTS_HASH) + } + + /// Returns the requests, if any. + pub const fn requests(&self) -> Option<&Requests> { + match self { + Self::Requests(requests) => Some(requests), + Self::Hash(_) => None, + } + } } + +impl Default for RequestsOrHash { + fn default() -> Self { + Self::Requests(Requests::default()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc-types-engine/src/cancun.rs b/crates/rpc-types-engine/src/cancun.rs index eddef715d67..d0d2a48b29e 100644 --- a/crates/rpc-types-engine/src/cancun.rs +++ b/crates/rpc-types-engine/src/cancun.rs @@ -2,7 +2,6 @@ //! beacon consensus engine. use alloc::vec::Vec; - use alloy_primitives::B256; /// Fields introduced in `engine_newPayloadV3` that are not present in the `ExecutionPayload` RPC @@ -20,6 +19,13 @@ pub struct CancunPayloadFields { pub versioned_hashes: Vec, } +impl CancunPayloadFields { + /// Returns a new [`CancunPayloadFields`] instance. + pub const fn new(parent_beacon_block_root: B256, versioned_hashes: Vec) -> Self { + Self { parent_beacon_block_root, versioned_hashes } + } +} + /// A container type for [CancunPayloadFields] that may or may not be present. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/rpc-types-engine/src/prague.rs b/crates/rpc-types-engine/src/prague.rs index fc1f119b425..36ae0a08135 100644 --- a/crates/rpc-types-engine/src/prague.rs +++ b/crates/rpc-types-engine/src/prague.rs @@ -1,7 +1,8 @@ //! Contains types related to the Prague hardfork that will be used by RPC to communicate with the //! beacon consensus engine. -use alloy_eips::eip7685::Requests; +use alloy_eips::eip7685::{Requests, RequestsOrHash}; +use alloy_primitives::B256; /// Fields introduced in `engine_newPayloadV4` that are not present in the `ExecutionPayload` RPC /// object. @@ -9,11 +10,18 @@ use alloy_eips::eip7685::Requests; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PraguePayloadFields { /// EIP-7685 requests. - pub requests: Requests, + pub requests: RequestsOrHash, /// EIP-7742 target number of blobs in the block. pub target_blobs_per_block: u64, } +impl PraguePayloadFields { + /// Returns a new [`PraguePayloadFields`] instance. + pub fn new(requests: impl Into, target_blobs_per_block: u64) -> Self { + Self { requests: requests.into(), target_blobs_per_block } + } +} + /// A container type for [PraguePayloadFields] that may or may not be present. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -34,7 +42,7 @@ impl MaybePraguePayloadFields { /// Returns the requests, if any. pub fn requests(&self) -> Option<&Requests> { - self.fields.as_ref().map(|fields| &fields.requests) + self.fields.as_ref().and_then(|fields| fields.requests.requests()) } /// Returns the target blobs per block, if any. @@ -42,6 +50,15 @@ impl MaybePraguePayloadFields { self.fields.as_ref().map(|fields| fields.target_blobs_per_block) } + /// Calculates or retrieves the requests hash. + /// + /// - If the `prague` field contains a list of requests, it calculates the requests hash + /// dynamically. + /// - If it contains a precomputed hash (used for testing), it returns that hash directly. + pub fn requests_hash(&self) -> Option { + self.fields.as_ref().map(|fields| fields.requests.requests_hash()) + } + /// Returns a reference to the inner fields. pub const fn as_ref(&self) -> Option<&PraguePayloadFields> { self.fields.as_ref() diff --git a/crates/rpc-types-engine/src/sidecar.rs b/crates/rpc-types-engine/src/sidecar.rs index d27bf4e045c..f2e262fabd3 100644 --- a/crates/rpc-types-engine/src/sidecar.rs +++ b/crates/rpc-types-engine/src/sidecar.rs @@ -4,7 +4,7 @@ use crate::{ CancunPayloadFields, MaybeCancunPayloadFields, MaybePraguePayloadFields, PraguePayloadFields, }; use alloc::vec::Vec; -use alloy_eips::eip7685::{Requests, RequestsOrHash}; +use alloy_eips::eip7685::Requests; use alloy_primitives::B256; /// Container type for all available additional `newPayload` request parameters that are not present @@ -57,12 +57,11 @@ impl ExecutionPayloadSidecar { } /// Returns the EIP-7685 requests - pub const fn requests(&self) -> Option<&Requests> { - if let Some(RequestsOrHash::Requests(ref requests)) = self.prague { - Some(requests) - } else { - None - } + /// + /// Note: if the [`PraguePayloadFields`] only contains the requests hash this will return + /// `None`. + pub fn requests(&self) -> Option<&Requests> { + self.prague.requests() } /// Calculates or retrieves the requests hash. @@ -71,7 +70,7 @@ impl ExecutionPayloadSidecar { /// dynamically. /// - If it contains a precomputed hash (used for testing), it returns that hash directly. pub fn requests_hash(&self) -> Option { - self.prague.as_ref().map(|hash| hash.requests_hash()) + self.prague.requests_hash() } /// Returns the target blobs per block