diff --git a/Cargo.lock b/Cargo.lock index 5dbba204..87e2d165 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2667,7 +2667,6 @@ dependencies = [ "thiserror 1.0.69", "transition_functions", "tynm", - "typenum", "types", "unwrap_none", ] @@ -3544,7 +3543,6 @@ dependencies = [ "tokio-stream", "tower-http", "transition_functions", - "try_from_iterator", "typenum", "types", "unwrap_none", diff --git a/fork_choice_control/src/helpers.rs b/fork_choice_control/src/helpers.rs index d7ce9f8f..d15bcc7c 100644 --- a/fork_choice_control/src/helpers.rs +++ b/fork_choice_control/src/helpers.rs @@ -255,7 +255,8 @@ impl Context

{ } pub fn on_blob_sidecar(&mut self, blob_sidecar: BlobSidecar

) -> Option> { - let subnet_id = misc::compute_subnet_for_blob_sidecar(self.config(), &blob_sidecar); + let subnet_id = misc::compute_subnet_for_blob_sidecar(self.config(), &blob_sidecar) + .expect("cannot compute subnet_id for given blob_sidecar"); self.controller().on_gossip_blob_sidecar( Arc::new(blob_sidecar), diff --git a/fork_choice_store/Cargo.toml b/fork_choice_store/Cargo.toml index 2205b3cd..cd38ac34 100644 --- a/fork_choice_store/Cargo.toml +++ b/fork_choice_store/Cargo.toml @@ -34,6 +34,5 @@ tap = { workspace = true } thiserror = { workspace = true } transition_functions = { workspace = true } tynm = { workspace = true } -typenum = { workspace = true } types = { workspace = true } unwrap_none = { workspace = true } diff --git a/fork_choice_store/src/store.rs b/fork_choice_store/src/store.rs index ef147471..51d987af 100644 --- a/fork_choice_store/src/store.rs +++ b/fork_choice_store/src/store.rs @@ -30,7 +30,6 @@ use transition_functions::{ combined, unphased::{self, ProcessSlots, StateRootPolicy}, }; -use typenum::Unsigned as _; use types::{ combined::{ Attestation, AttesterSlashing, AttestingIndices, BeaconState, SignedAggregateAndProof, @@ -1699,12 +1698,11 @@ impl Store

{ let block_header = blob_sidecar.signed_block_header.message; // [REJECT] The sidecar's index is consistent with MAX_BLOBS_PER_BLOCK -- i.e. blob_sidecar.index < MAX_BLOBS_PER_BLOCK. - let max_blobs_per_block = - if self.chain_config().phase_at_slot::

(block_header.slot) >= Phase::Electra { - P::MaxBlobsPerBlockElectra::U64 - } else { - P::MaxBlobsPerBlock::U64 - }; + let max_blobs_per_block = self + .chain_config() + .phase_at_slot::

(block_header.slot) + .max_blobs_per_block::

() + .unwrap_or_default(); ensure!( blob_sidecar.index < max_blobs_per_block, @@ -1713,7 +1711,8 @@ impl Store

{ // [REJECT] The sidecar is for the correct subnet -- i.e. compute_subnet_for_blob_sidecar(blob_sidecar.index) == subnet_id. if let Some(actual) = origin.subnet_id() { - let expected = misc::compute_subnet_for_blob_sidecar(&self.chain_config, &blob_sidecar); + let expected = + misc::compute_subnet_for_blob_sidecar(&self.chain_config, &blob_sidecar)?; ensure!( actual == expected, diff --git a/grandine-snapshot-tests b/grandine-snapshot-tests index c063d4cd..c531cc15 160000 --- a/grandine-snapshot-tests +++ b/grandine-snapshot-tests @@ -1 +1 @@ -Subproject commit c063d4cdeb8102cd7bb1b1ab848d523a8d879098 +Subproject commit c531cc15dab36638b713a80432f7544a5424dd57 diff --git a/helper_functions/src/error.rs b/helper_functions/src/error.rs index be18ec86..395d5b06 100644 --- a/helper_functions/src/error.rs +++ b/helper_functions/src/error.rs @@ -10,6 +10,8 @@ pub(crate) enum Error { AttestationSourceMismatch, #[error("attesting indices are not sorted and unique")] AttestingIndicesNotSortedAndUnique, + #[error("cannot compute subnet for blob sidecar")] + BlobSidecarSubnetNotAvailable, #[error("committee index is out of bounds")] CommitteeIndexOutOfBounds, #[error("aggregation bitlist length {aggregation_bitlist_length} does not match committee length {committee_length}")] diff --git a/helper_functions/src/misc.rs b/helper_functions/src/misc.rs index e512514b..688ff0d9 100644 --- a/helper_functions/src/misc.rs +++ b/helper_functions/src/misc.rs @@ -23,7 +23,6 @@ use types::{ Blob, BlobCommitmentInclusionProof, BlobIndex, KzgCommitment, KzgProof, VersionedHash, }, }, - nonstandard::Phase, phase0::{ consts::{ AttestationSubnetCount, BLS_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX, @@ -279,16 +278,16 @@ pub fn compute_subnet_for_attestation( } /// [`compute_subnet_for_blob_sidecar`](https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.1/specs/deneb/validator.md#sidecar) -#[must_use] pub fn compute_subnet_for_blob_sidecar( config: &Config, blob_sidecar: &BlobSidecar

, -) -> SubnetId { - if config.phase_at_slot::

(blob_sidecar.signed_block_header.message.slot) >= Phase::Electra { - blob_sidecar.index % config.blob_sidecar_subnet_count_electra - } else { - blob_sidecar.index % config.blob_sidecar_subnet_count - } +) -> Result { + let phase = config.phase_at_slot::

(blob_sidecar.signed_block_header.message.slot); + + Ok(blob_sidecar.index + % config + .blob_sidecar_subnet_count(phase) + .ok_or(Error::BlobSidecarSubnetNotAvailable)?) } /// diff --git a/http_api/Cargo.toml b/http_api/Cargo.toml index 9aa407a2..9d9c1211 100644 --- a/http_api/Cargo.toml +++ b/http_api/Cargo.toml @@ -48,7 +48,6 @@ tokio = { workspace = true } tokio-stream = { workspace = true } tower-http = { workspace = true } transition_functions = { workspace = true } -try_from_iterator = { workspace = true } typenum = { workspace = true } types = { workspace = true } unwrap_none = { workspace = true } diff --git a/http_api/src/standard.rs b/http_api/src/standard.rs index 1f51fdf7..38bc4fa3 100644 --- a/http_api/src/standard.rs +++ b/http_api/src/standard.rs @@ -43,11 +43,10 @@ use prometheus_metrics::Metrics; use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_with::{As, DisplayFromStr}; -use ssz::{ContiguousList, SszHash as _}; +use ssz::{DynamicList, SszHash as _}; use std_ext::ArcExt as _; use tap::Pipe as _; use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}; -use try_from_iterator::TryFromIterator as _; use typenum::Unsigned as _; use types::{ altair::{ @@ -941,34 +940,38 @@ pub async fn blob_sidecars( EthPath(block_id): EthPath, EthQuery(query): EthQuery, headers: HeaderMap, -) -> Result< - EthResponse>, P::MaxBlobsPerBlock>, (), JsonOrSsz>, - Error, -> { - let block_root = - block_id::block_root(block_id, &controller, &anchor_checkpoint_provider)?.value; +) -> Result>>, (), JsonOrSsz>, Error> { + let WithStatus { + value: block, + optimistic, + finalized, + } = block_id::block(block_id, &controller, &anchor_checkpoint_provider)?; - // TODO(feature/electra): support P::MaxBlobsPerBlockElectra + let version = block.phase(); + let block_root = block.message().hash_tree_root(); + let max_blobs_per_block = version.max_blobs_per_block::

().unwrap_or_default(); let blob_identifiers = query .indices - .unwrap_or_else(|| (0..P::MaxBlobsPerBlock::U64).collect()) + .unwrap_or_else(|| (0..max_blobs_per_block).collect()) .into_iter() .map(|index| { - ensure!( - index < P::MaxBlobsPerBlock::U64, - Error::InvalidBlobIndex(index) - ); - + ensure!(index < max_blobs_per_block, Error::InvalidBlobIndex(index)); Ok(BlobIdentifier { block_root, index }) }) .collect::>>()?; let blob_sidecars = controller.blob_sidecars_by_ids(blob_identifiers)?; - let blob_sidecars = - ContiguousList::try_from_iter(blob_sidecars.into_iter()).map_err(AnyhowError::new)?; + let blob_sidecars = DynamicList::try_from_iter_with_maximum( + blob_sidecars.into_iter(), + usize::try_from(max_blobs_per_block).map_err(AnyhowError::new)?, + ) + .map_err(AnyhowError::new)?; - Ok(EthResponse::json_or_ssz(blob_sidecars, &headers)?) + Ok(EthResponse::json_or_ssz(blob_sidecars, &headers)? + .execution_optimistic(optimistic) + .finalized(finalized) + .version(version)) } /// `POST /eth/v1/beacon/blocks` diff --git a/p2p/src/network.rs b/p2p/src/network.rs index 8fc8cbe6..03d11ea0 100644 --- a/p2p/src/network.rs +++ b/p2p/src/network.rs @@ -392,7 +392,7 @@ impl Network

{ ValidatorToP2p::PublishContributionAndProof(contribution_and_proof) => { self.publish_contribution_and_proof(contribution_and_proof); } - } + }; }, message = self.channels.sync_to_p2p_rx.select_next_some() => { @@ -550,14 +550,19 @@ impl Network

{ let subnet_id = misc::compute_subnet_for_blob_sidecar(self.controller.chain_config(), &blob_sidecar); - let blob_identifier: BlobIdentifier = blob_sidecar.as_ref().into(); + match subnet_id { + Ok(subnet_id) => { + let blob_identifier: BlobIdentifier = blob_sidecar.as_ref().into(); - debug!("publishing blob sidecar: {blob_identifier:?}, subnet_id: {subnet_id}"); + debug!("publishing blob sidecar: {blob_identifier:?}, subnet_id: {subnet_id}"); - self.publish(PubsubMessage::BlobSidecar(Box::new(( - subnet_id, - blob_sidecar, - )))); + self.publish(PubsubMessage::BlobSidecar(Box::new(( + subnet_id, + blob_sidecar, + )))); + } + Err(error) => warn!("unable to publish blob sidecar: {error:?}"), + } } fn publish_singular_attestation(&self, attestation: Arc>, subnet_id: SubnetId) { diff --git a/ssz/src/dynamic_list.rs b/ssz/src/dynamic_list.rs index 22d98f41..9366b252 100644 --- a/ssz/src/dynamic_list.rs +++ b/ssz/src/dynamic_list.rs @@ -7,6 +7,7 @@ use core::fmt::Debug; use derivative::Derivative; use derive_more::{AsRef, Deref}; +use serde::{Serialize, Serializer}; use crate::{ error::{ReadError, WriteError}, @@ -55,6 +56,12 @@ impl<'list, T> IntoIterator for &'list DynamicList { } } +impl Serialize for DynamicList { + fn serialize(&self, serializer: S) -> Result { + serializer.collect_seq(self) + } +} + impl SszSize for DynamicList { const SIZE: Size = Size::Variable { minimum_size: 0 }; } diff --git a/types/src/config.rs b/types/src/config.rs index 3bb31a27..664fce53 100644 --- a/types/src/config.rs +++ b/types/src/config.rs @@ -737,6 +737,15 @@ impl Config { } } + #[must_use] + pub const fn blob_sidecar_subnet_count(&self, phase: Phase) -> Option { + match phase { + Phase::Phase0 | Phase::Altair | Phase::Bellatrix | Phase::Capella => None, + Phase::Deneb => Some(self.blob_sidecar_subnet_count), + Phase::Electra => Some(self.blob_sidecar_subnet_count_electra), + } + } + fn fork_slots(&self) -> impl Iterator)> + '_ { enum_iterator::all().map(|phase| (phase, self.fork_slot::

(phase))) } diff --git a/types/src/nonstandard.rs b/types/src/nonstandard.rs index 52d4ce63..8b90ab07 100644 --- a/types/src/nonstandard.rs +++ b/types/src/nonstandard.rs @@ -12,6 +12,7 @@ use smallvec::SmallVec; use ssz::ContiguousList; use static_assertions::assert_eq_size; use strum::{AsRefStr, Display, EnumString}; +use typenum::Unsigned as _; use crate::{ altair::{ @@ -62,6 +63,17 @@ pub enum Phase { Electra, } +impl Phase { + #[must_use] + pub const fn max_blobs_per_block(self) -> Option { + match self { + Self::Phase0 | Self::Altair | Self::Bellatrix | Self::Capella => None, + Self::Deneb => Some(P::MaxBlobsPerBlock::U64), + Self::Electra => Some(P::MaxBlobsPerBlockElectra::U64), + } + } +} + /// Like [`Option`], but with [`None`] greater than any [`Some`]. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(test, derive(Debug))]