diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index da8566dcb..cbfbd250f 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -1,6 +1,6 @@ use crate::beacon_block_body::{ BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyMerge, BeaconBlockBodyRef, - BeaconBlockBodyRefMut, + BeaconBlockBodyRefMut, BeaconBlockBobyEip4844 }; use crate::test_utils::TestRandom; use crate::*; @@ -17,7 +17,7 @@ use tree_hash_derive::TreeHash; /// A block of the `BeaconChain`. #[superstruct( - variants(Base, Altair, Merge), + variants(Base, Altair, Merge, Eip4844), variant_attributes( derive( Debug, @@ -64,6 +64,8 @@ pub struct BeaconBlock = FullPayload> { pub body: BeaconBlockBodyAltair, #[superstruct(only(Merge), partial_getter(rename = "body_merge"))] pub body: BeaconBlockBodyMerge, + #[superstruct(only(Eip4844), partial_getter(rename = "body_eip4844"))] + pub body: BeaconBlockBodyEip4844, } impl> SignedRoot for BeaconBlock {} @@ -540,6 +542,7 @@ macro_rules! impl_from { impl_from!(BeaconBlockBase, >, >, |body: BeaconBlockBodyBase<_, _>| body.into()); impl_from!(BeaconBlockAltair, >, >, |body: BeaconBlockBodyAltair<_, _>| body.into()); impl_from!(BeaconBlockMerge, >, >, |body: BeaconBlockBodyMerge<_, _>| body.into()); +impl_from!(BeaconBlockEip4844, >, >, |body: BeaconBlockBodyEip4844<_, _>| body.into()); // We can clone blocks with payloads to blocks without payloads, without cloning the payload. macro_rules! impl_clone_as_blinded { diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 381a9bd43..a4fdbf689 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -13,7 +13,7 @@ use tree_hash_derive::TreeHash; /// /// This *superstruct* abstracts over the hard-fork. #[superstruct( - variants(Base, Altair, Merge), + variants(Base, Altair, Merge, Eip4844), variant_attributes( derive( Debug, @@ -47,14 +47,16 @@ pub struct BeaconBlockBody = FullPayload> pub attestations: VariableList, T::MaxAttestations>, pub deposits: VariableList, pub voluntary_exits: VariableList, - #[superstruct(only(Altair, Merge))] + #[superstruct(only(Altair, Merge, Eip4844))] pub sync_aggregate: SyncAggregate, // We flatten the execution payload so that serde can use the name of the inner type, // either `execution_payload` for full payloads, or `execution_payload_header` for blinded // payloads. - #[superstruct(only(Merge))] + #[superstruct(only(Merge, Eip4844))] #[serde(flatten)] pub execution_payload: Payload, + #[superstruct(only(Eip4844))] + pub blob_kzg_commitments: VariableList, #[superstruct(only(Base, Altair))] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index e61697602..aafb3d236 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -95,6 +95,10 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + type GasLimitDenominator: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MinGasLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxExtraDataBytes: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Eip4844 + */ + type MaxBlobsPerBlock: Unsigned + Clone + Sync + Send + Debug + PartialEq; /* * Derived values (set these CAREFULLY) */ @@ -222,6 +226,11 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + fn bytes_per_logs_bloom() -> usize { Self::BytesPerLogsBloom::to_usize() } + + /// Returns the `MAX_BLOBS_PER_BLOCK` constant for this specification. + fn max_blobs_per_block() -> usize { + Self::MaxBlobsPerBlock::to_usize() + } } /// Macro to inherit some type values from another EthSpec. @@ -265,6 +274,7 @@ impl EthSpec for MainnetEthSpec { type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch + type MaxBlobsPerBlock = U16; fn default_spec() -> ChainSpec { ChainSpec::mainnet() @@ -309,7 +319,8 @@ impl EthSpec for MinimalEthSpec { BytesPerLogsBloom, GasLimitDenominator, MinGasLimit, - MaxExtraDataBytes + MaxExtraDataBytes, + MaxBlobsPerBlock }); fn default_spec() -> ChainSpec { @@ -354,6 +365,7 @@ impl EthSpec for GnosisEthSpec { type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count type MaxPendingAttestations = U2048; // 128 max attestations * 16 slots per epoch type SlotsPerEth1VotingPeriod = U1024; // 64 epochs * 16 slots per epoch + type MaxBlobsPerBlock = U16; fn default_spec() -> ChainSpec { ChainSpec::gnosis() diff --git a/consensus/types/src/kzg_commitment.rs b/consensus/types/src/kzg_commitment.rs new file mode 100644 index 000000000..688f96e05 --- /dev/null +++ b/consensus/types/src/kzg_commitment.rs @@ -0,0 +1,112 @@ +use std::fmt; +use serde::{Deserialize, Deserializer, Serializer}; +use ssz::{Decode, DecodeError, Encode}; +use tree_hash::TreeHash; + +const KZG_COMMITMENT_BYTES_LEN: usize = 48; + +#[derive(Default, Debug, PartialEq, Hash, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct KzgCommitment(#[serde(with = "serde_kzg_commitment")] pub [u8; KZG_COMMITMENT_BYTES_LEN]); + +impl fmt::Display for KzgCommitment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", eth2_serde_utils::hex::encode(&self.0)) + } +} + +impl From<[u8; KZG_COMMITMENT_BYTES_LEN]> for KzgCommitment { + fn from(bytes: [u8; KZG_COMMITMENT_BYTES_LEN]) -> Self { + Self(bytes) + } +} + +impl Into<[u8; KZG_COMMITMENT_BYTES_LEN]> for KzgCommitment { + fn into(self) -> [u8; KZG_COMMITMENT_BYTES_LEN] { + self.0 + } +} + +pub mod serde_kzg_commitment { + use serde::de::Error; + use super::*; + + pub fn serialize(bytes: &[u8; KZG_COMMITMENT_BYTES_LEN], serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(ð2_serde_utils::hex::encode(bytes)) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; KZG_COMMITMENT_BYTES_LEN], D::Error> + where + D: Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + + let bytes = eth2_serde_utils::hex::decode(&s).map_err(D::Error::custom)?; + + if bytes.len() != KZG_COMMITMENT_BYTES_LEN { + return Err(D::Error::custom(format!( + "incorrect byte length {}, expected {}", + bytes.len(), + KZG_COMMITMENT_BYTES_LEN + ))); + } + + let mut array = [0; KZG_COMMITMENT_BYTES_LEN]; + array[..].copy_from_slice(&bytes); + + Ok(array) + } +} + +impl Encode for KzgCommitment { + fn is_ssz_fixed_len() -> bool { + <[u8; KZG_COMMITMENT_BYTES_LEN] as Encode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <[u8; KZG_COMMITMENT_BYTES_LEN] as Encode>::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.0.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } +} + +impl Decode for KzgCommitment { + fn is_ssz_fixed_len() -> bool { + <[u8; KZG_COMMITMENT_BYTES_LEN] as Decode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <[u8; KZG_COMMITMENT_BYTES_LEN] as Decode>::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + <[u8; KZG_COMMITMENT_BYTES_LEN]>::from_ssz_bytes(bytes).map(Self) + } +} + +impl TreeHash for KzgCommitment { + fn tree_hash_type() -> tree_hash::TreeHashType { + <[u8; KZG_COMMITMENT_BYTES_LEN]>::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> Vec { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + <[u8; KZG_COMMITMENT_BYTES_LEN]>::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.0.tree_hash_root() + } +} diff --git a/consensus/types/src/kzg_proof.rs b/consensus/types/src/kzg_proof.rs new file mode 100644 index 000000000..50ee1266d --- /dev/null +++ b/consensus/types/src/kzg_proof.rs @@ -0,0 +1,112 @@ +use std::fmt; +use serde::{Deserialize, Deserializer, Serializer}; +use ssz::{Decode, DecodeError, Encode}; +use tree_hash::TreeHash; + +const KZG_PROOF_BYTES_LEN: usize = 48; + +#[derive(Default, Debug, PartialEq, Hash, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +pub struct KzgProof(#[serde(with = "serde_kzg_proof")] pub [u8; KZG_PROOF_BYTES_LEN]); + +impl fmt::Display for KzgProof { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", eth2_serde_utils::hex::encode(&self.0)) + } +} + +impl From<[u8; KZG_PROOF_BYTES_LEN]> for KzgProof { + fn from(bytes: [u8; KZG_PROOF_BYTES_LEN]) -> Self { + Self(bytes) + } +} + +impl Into<[u8; KZG_PROOF_BYTES_LEN]> for KzgProof { + fn into(self) -> [u8; KZG_PROOF_BYTES_LEN] { + self.0 + } +} + +pub mod serde_kzg_proof { + use serde::de::Error; + use super::*; + + pub fn serialize(bytes: &[u8; KZG_PROOF_BYTES_LEN], serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(ð2_serde_utils::hex::encode(bytes)) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; KZG_PROOF_BYTES_LEN], D::Error> + where + D: Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + + let bytes = eth2_serde_utils::hex::decode(&s).map_err(D::Error::custom)?; + + if bytes.len() != KZG_PROOF_BYTES_LEN { + return Err(D::Error::custom(format!( + "incorrect byte length {}, expected {}", + bytes.len(), + KZG_PROOF_BYTES_LEN + ))); + } + + let mut array = [0; KZG_PROOF_BYTES_LEN]; + array[..].copy_from_slice(&bytes); + + Ok(array) + } +} + +impl Encode for KzgProof { + fn is_ssz_fixed_len() -> bool { + <[u8; KZG_PROOF_BYTES_LEN] as Encode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <[u8; KZG_PROOF_BYTES_LEN] as Encode>::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.0.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } +} + +impl Decode for KzgProof { + fn is_ssz_fixed_len() -> bool { + <[u8; KZG_PROOF_BYTES_LEN] as Decode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <[u8; KZG_PROOF_BYTES_LEN] as Decode>::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + <[u8; KZG_PROOF_BYTES_LEN]>::from_ssz_bytes(bytes).map(Self) + } +} + +impl TreeHash for KzgProof { + fn tree_hash_type() -> tree_hash::TreeHashType { + <[u8; KZG_PROOF_BYTES_LEN]>::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> Vec { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + <[u8; KZG_PROOF_BYTES_LEN]>::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.0.tree_hash_root() + } +} diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index f05012c0b..c5a0e6ba0 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -14,6 +14,8 @@ #[macro_use] extern crate lazy_static; +extern crate core; + #[macro_use] pub mod test_utils; @@ -90,6 +92,9 @@ pub mod slot_data; #[cfg(feature = "sqlite")] pub mod sqlite; +pub mod kzg_commitment; +pub mod kzg_proof; + use ethereum_types::{H160, H256}; pub use crate::aggregate_and_proof::AggregateAndProof;