From f5872e12c4b10b4918114cb7cc08883bed7ad2dc Mon Sep 17 00:00:00 2001 From: Mark Mackey Date: Tue, 3 Sep 2024 13:54:39 -0500 Subject: [PATCH] BeaconBlock & PayloadStatus Finished --- consensus/types/src/beacon_block.rs | 13 +- consensus/types/src/beacon_block_body.rs | 6 + consensus/types/src/chain_spec.rs | 12 ++ consensus/types/src/eth_spec.rs | 13 +- consensus/types/src/lib.rs | 2 +- consensus/types/src/payload_attestation.rs | 136 ++++++++++++++++++++- 6 files changed, 173 insertions(+), 9 deletions(-) diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index e4aa4faf31..f9703430fc 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -678,7 +678,7 @@ impl> BeaconBlockElectra } impl> BeaconBlockEIP7732 { - /// Return a Electra block where the block has maximum size. + /// Return a EIP7732 block where the block has maximum size. pub fn full(spec: &ChainSpec) -> Self { let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); let indexed_attestation: IndexedAttestationElectra = IndexedAttestationElectra { @@ -722,6 +722,15 @@ impl> BeaconBlockEIP7732 sync_committee_signature: AggregateSignature::empty(), sync_committee_bits: BitVector::default(), }; + let payload_attestations = vec![ + PayloadAttestation:: { + aggregation_bits: BitList::with_capacity(E::PTCSize::to_usize()).unwrap(), + slot: Slot::new(0), + payload_status: PayloadStatus::PayloadPresent, + }; + E::max_payload_attestations() + ] + .into(); BeaconBlockEIP7732 { slot: spec.genesis_slot, proposer_index: 0, @@ -735,6 +744,7 @@ impl> BeaconBlockEIP7732 voluntary_exits: base_block.body.voluntary_exits, bls_to_execution_changes, signed_execution_bid: SignedExecutionBid::empty(), + payload_attestations, sync_aggregate, randao_reveal: Signature::empty(), eth1_data: Eth1Data { @@ -804,6 +814,7 @@ impl> EmptyBlock for BeaconBlockEIP7 sync_aggregate: SyncAggregate::empty(), bls_to_execution_changes: VariableList::empty(), signed_execution_bid: SignedExecutionBid::empty(), + payload_attestations: VariableList::empty(), _phantom: PhantomData, }, } diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index de938f5f8e..dc193c00e9 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -125,6 +125,8 @@ pub struct BeaconBlockBody = FullPay pub execution_requests: ExecutionRequests, #[superstruct(only(EIP7732))] pub signed_execution_bid: SignedExecutionBid, + #[superstruct(only(EIP7732))] + pub payload_attestations: VariableList, E::MaxPayloadAttestations>, #[superstruct(only(Base, Altair, EIP7732))] #[metastruct(exclude_from(fields))] #[ssz(skip_serializing, skip_deserializing)] @@ -748,6 +750,7 @@ impl From>> sync_aggregate, bls_to_execution_changes, signed_execution_bid, + payload_attestations, _phantom, } = body; @@ -763,6 +766,7 @@ impl From>> sync_aggregate, bls_to_execution_changes, signed_execution_bid, + payload_attestations, _phantom: PhantomData, } } @@ -787,6 +791,7 @@ impl From>> sync_aggregate, bls_to_execution_changes, signed_execution_bid, + payload_attestations, _phantom, } = body; @@ -803,6 +808,7 @@ impl From>> sync_aggregate, bls_to_execution_changes, signed_execution_bid, + payload_attestations, _phantom: PhantomData, }, None, diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index 607e9d06b3..1d91f08e23 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -26,6 +26,8 @@ pub enum Domain { SyncCommittee, ContributionAndProof, SyncCommitteeSelectionProof, + BeaconBuilder, + PTCAttester, ApplicationMask(ApplicationDomain), } @@ -110,6 +112,8 @@ pub struct ChainSpec { pub(crate) domain_voluntary_exit: u32, pub(crate) domain_selection_proof: u32, pub(crate) domain_aggregate_and_proof: u32, + pub(crate) domain_beacon_builder: u32, + pub(crate) domain_ptc_attester: u32, /* * Fork choice @@ -483,6 +487,8 @@ impl ChainSpec { Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(), Domain::BlsToExecutionChange => self.domain_bls_to_execution_change, + Domain::BeaconBuilder => self.domain_beacon_builder, + Domain::PTCAttester => self.domain_ptc_attester, } } @@ -707,6 +713,8 @@ impl ChainSpec { domain_voluntary_exit: 4, domain_selection_proof: 5, domain_aggregate_and_proof: 6, + domain_beacon_builder: 0x1B, + domain_ptc_attester: 0x0C, /* * Fork choice @@ -1030,6 +1038,8 @@ impl ChainSpec { domain_voluntary_exit: 4, domain_selection_proof: 5, domain_aggregate_and_proof: 6, + domain_beacon_builder: 0x1B, + domain_ptc_attester: 0x0C, /* * Fork choice @@ -1945,6 +1955,8 @@ mod tests { &spec, ); test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec); + test_domain(Domain::BeaconBuilder, spec.domain_beacon_builder, &spec); + test_domain(Domain::PTCAttester, spec.domain_ptc_attester, &spec); // The builder domain index is zero let builder_domain_pre_mask = [0; 4]; diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index b03094243b..ef7e14013e 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -161,9 +161,10 @@ pub trait EthSpec: type MaxWithdrawalRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; /* - * New in EIP-7736 + * New in EIP-7732 */ type PTCSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; fn default_spec() -> ChainSpec; @@ -376,6 +377,11 @@ pub trait EthSpec: Self::MaxWithdrawalRequestsPerPayload::to_usize() } + /// Returns the `MaxPayloadAttestations` constant for this specification. + fn max_payload_attestations() -> usize { + Self::MaxPayloadAttestations::to_usize() + } + fn kzg_commitments_inclusion_proof_depth() -> usize { Self::KzgCommitmentsInclusionProofDepth::to_usize() } @@ -444,6 +450,7 @@ impl EthSpec for MainnetEthSpec { type MaxAttestationsElectra = U8; type MaxWithdrawalRequestsPerPayload = U16; type PTCSize = U512; + type MaxPayloadAttestations = U4; fn default_spec() -> ChainSpec { ChainSpec::mainnet() @@ -510,7 +517,8 @@ impl EthSpec for MinimalEthSpec { MaxConsolidationRequestsPerPayload, MaxAttesterSlashingsElectra, MaxAttestationsElectra, - PTCSize + PTCSize, + MaxPayloadAttestations }); fn default_spec() -> ChainSpec { @@ -577,6 +585,7 @@ impl EthSpec for GnosisEthSpec { type BytesPerCell = U2048; type KzgCommitmentsInclusionProofDepth = U4; type PTCSize = U512; + type MaxPayloadAttestations = U4; fn default_spec() -> ChainSpec { ChainSpec::gnosis() diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 423f3effb5..9671619752 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -224,7 +224,7 @@ pub use crate::payload::{ FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, FullPayloadRef, OwnedExecPayload, }; -pub use crate::payload_attestation::PayloadAttestation; +pub use crate::payload_attestation::{PayloadAttestation, PayloadStatus}; pub use crate::payload_attestation_data::PayloadAttestationData; pub use crate::payload_attestation_message::PayloadAttestationMessage; pub use crate::pending_attestation::PendingAttestation; diff --git a/consensus/types/src/payload_attestation.rs b/consensus/types/src/payload_attestation.rs index 67224fd8bd..07525657bb 100644 --- a/consensus/types/src/payload_attestation.rs +++ b/consensus/types/src/payload_attestation.rs @@ -1,8 +1,12 @@ use crate::test_utils::TestRandom; use crate::*; -use serde::{Deserialize, Serialize}; +use derivative::Derivative; +use rand::Rng; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; +use tree_hash::TreeHash; use tree_hash_derive::TreeHash; #[derive( @@ -11,24 +15,146 @@ use tree_hash_derive::TreeHash; TreeHash, Debug, Clone, - PartialEq, - Eq, Encode, Decode, Serialize, Deserialize, + Derivative, )] #[serde(bound = "E: EthSpec", deny_unknown_fields)] #[arbitrary(bound = "E: EthSpec")] +#[derivative(PartialEq, Hash)] pub struct PayloadAttestation { pub aggregation_bits: BitList, pub slot: Slot, - pub payload_status: u8, + pub payload_status: PayloadStatus, +} + +#[repr(u8)] +#[derive(arbitrary::Arbitrary, Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum PayloadStatus { + PayloadAbsent = 0, + PayloadPresent = 1, + PayloadWithheld = 2, + PayloadInvalidStatus = 3, +} + +impl TryFrom for PayloadStatus { + type Error = String; + + fn try_from(byte: u8) -> Result { + match byte { + 0 => Ok(PayloadStatus::PayloadAbsent), + 1 => Ok(PayloadStatus::PayloadPresent), + 2 => Ok(PayloadStatus::PayloadWithheld), + 3 => Ok(PayloadStatus::PayloadInvalidStatus), + _ => Err(format!("Invalid byte for PayloadStatus: {}", byte)), + } + } +} + +impl TestRandom for PayloadStatus { + fn random_for_test(rng: &mut impl rand::RngCore) -> Self { + rng.gen_range(0u8..=3u8) + .try_into() + .expect("PayloadStatus: random byte is valid") + } +} + +impl TreeHash for PayloadStatus { + fn tree_hash_type() -> tree_hash::TreeHashType { + u8::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + (*self as u8).tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + u8::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> Hash256 { + (*self as u8).tree_hash_root() + } +} + +// ssz(enum_behaviour = "tag") would probably work but we want to ensure +// that the mapping between the variant and u8 matches the spec +impl Encode for PayloadStatus { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + ::ssz_bytes_len(&(*self as u8)) + } + + fn ssz_append(&self, buf: &mut Vec) { + ::ssz_append(&(*self as u8), buf) + } +} + +impl Decode for PayloadStatus { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + (*bytes + // the u8 is just the first byte of the slice + .get(0) + .ok_or(ssz::DecodeError::InvalidByteLength { + len: 0, + expected: 1, + })?) + .try_into() + .map_err(|s| ssz::DecodeError::BytesInvalid(s)) + } +} + +impl Serialize for PayloadStatus { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serde_utils::quoted_u8::Quoted::::serialize( + &serde_utils::quoted_u8::Quoted { + value: (*self as u8), + }, + serializer, + ) + } +} + +impl<'de> Deserialize<'de> for PayloadStatus { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let quoted = serde_utils::quoted_u8::Quoted::::deserialize(deserializer)?; + PayloadStatus::try_from(quoted.value).map_err(serde::de::Error::custom) + } } #[cfg(test)] -mod tests { +mod payload_attestation_tests { use super::*; ssz_and_tree_hash_tests!(PayloadAttestation); } + +#[cfg(test)] +mod payload_status_tests { + use super::*; + + ssz_and_tree_hash_tests!(PayloadStatus); +}