Merge branch 'unstable' into refactor-mock-builder

This commit is contained in:
Pawan Dhananjay
2025-01-16 10:30:47 -08:00
107 changed files with 2016 additions and 1311 deletions

View File

@@ -12,8 +12,8 @@ use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
use super::{
AggregateSignature, AttestationData, BitList, ChainSpec, Domain, EthSpec, Fork, SecretKey,
Signature, SignedRoot,
AggregateSignature, AttestationData, BitList, ChainSpec, CommitteeIndex, Domain, EthSpec, Fork,
SecretKey, Signature, SignedRoot,
};
#[derive(Debug, PartialEq)]
@@ -24,6 +24,10 @@ pub enum Error {
IncorrectStateVariant,
InvalidCommitteeLength,
InvalidCommitteeIndex,
AttesterNotInCommittee(usize),
InvalidCommittee,
MissingCommittee,
NoCommitteeForSlotAndIndex { slot: Slot, index: CommitteeIndex },
}
impl From<ssz_types::Error> for Error {
@@ -231,6 +235,16 @@ impl<E: EthSpec> Attestation<E> {
Attestation::Electra(att) => att.aggregation_bits.get(index),
}
}
pub fn to_single_attestation_with_attester_index(
&self,
attester_index: usize,
) -> Result<SingleAttestation, Error> {
match self {
Self::Base(_) => Err(Error::IncorrectStateVariant),
Self::Electra(attn) => attn.to_single_attestation_with_attester_index(attester_index),
}
}
}
impl<E: EthSpec> AttestationRef<'_, E> {
@@ -287,6 +301,14 @@ impl<E: EthSpec> AttestationElectra<E> {
self.get_committee_indices().first().cloned()
}
pub fn get_aggregation_bits(&self) -> Vec<u64> {
self.aggregation_bits
.iter()
.enumerate()
.filter_map(|(index, bit)| if bit { Some(index as u64) } else { None })
.collect()
}
pub fn get_committee_indices(&self) -> Vec<u64> {
self.committee_bits
.iter()
@@ -350,6 +372,22 @@ impl<E: EthSpec> AttestationElectra<E> {
Ok(())
}
}
pub fn to_single_attestation_with_attester_index(
&self,
attester_index: usize,
) -> Result<SingleAttestation, Error> {
let Some(committee_index) = self.committee_index() else {
return Err(Error::InvalidCommitteeIndex);
};
Ok(SingleAttestation {
committee_index: committee_index as usize,
attester_index,
data: self.data.clone(),
signature: self.signature.clone(),
})
}
}
impl<E: EthSpec> AttestationBase<E> {
@@ -527,6 +565,58 @@ impl<E: EthSpec> ForkVersionDeserialize for Vec<Attestation<E>> {
}
}
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
Decode,
Encode,
TestRandom,
Derivative,
arbitrary::Arbitrary,
TreeHash,
PartialEq,
)]
pub struct SingleAttestation {
pub committee_index: usize,
pub attester_index: usize,
pub data: AttestationData,
pub signature: AggregateSignature,
}
impl SingleAttestation {
pub fn to_attestation<E: EthSpec>(&self, committee: &[usize]) -> Result<Attestation<E>, Error> {
let aggregation_bit = committee
.iter()
.enumerate()
.find_map(|(i, &validator_index)| {
if self.attester_index == validator_index {
return Some(i);
}
None
})
.ok_or(Error::AttesterNotInCommittee(self.attester_index))?;
let mut committee_bits: BitVector<E::MaxCommitteesPerSlot> = BitVector::default();
committee_bits
.set(self.committee_index, true)
.map_err(|_| Error::InvalidCommitteeIndex)?;
let mut aggregation_bits =
BitList::with_capacity(committee.len()).map_err(|_| Error::InvalidCommitteeLength)?;
aggregation_bits.set(aggregation_bit, true)?;
Ok(Attestation::Electra(AttestationElectra {
aggregation_bits,
committee_bits,
data: self.data.clone(),
signature: self.signature.clone(),
}))
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -12,7 +12,7 @@ use test_random_derive::TestRandom;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use self::indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectra};
use self::indexed_attestation::IndexedAttestationBase;
/// A block of the `BeaconChain`.
#[superstruct(
@@ -499,52 +499,6 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockBell
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockCapella<E, Payload> {
/// Return a Capella block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> Self {
let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec);
let bls_to_execution_changes = vec![
SignedBlsToExecutionChange {
message: BlsToExecutionChange {
validator_index: 0,
from_bls_pubkey: PublicKeyBytes::empty(),
to_execution_address: Address::ZERO,
},
signature: Signature::empty()
};
E::max_bls_to_execution_changes()
]
.into();
let sync_aggregate = SyncAggregate {
sync_committee_signature: AggregateSignature::empty(),
sync_committee_bits: BitVector::default(),
};
BeaconBlockCapella {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyCapella {
proposer_slashings: base_block.body.proposer_slashings,
attester_slashings: base_block.body.attester_slashings,
attestations: base_block.body.attestations,
deposits: base_block.body.deposits,
voluntary_exits: base_block.body.voluntary_exits,
bls_to_execution_changes,
sync_aggregate,
randao_reveal: Signature::empty(),
eth1_data: Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
deposit_count: 0,
},
graffiti: Graffiti::default(),
execution_payload: Payload::Capella::default(),
},
}
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockCapella<E, Payload> {
/// Returns an empty Capella block to be used during genesis.
fn empty(spec: &ChainSpec) -> Self {
@@ -604,79 +558,6 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockDene
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockElectra<E, Payload> {
/// Return a Electra 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<E> = IndexedAttestationElectra {
attesting_indices: VariableList::new(vec![0_u64; E::MaxValidatorsPerSlot::to_usize()])
.unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
};
let attester_slashings = vec![
AttesterSlashingElectra {
attestation_1: indexed_attestation.clone(),
attestation_2: indexed_attestation,
};
E::max_attester_slashings_electra()
]
.into();
let attestation = AttestationElectra {
aggregation_bits: BitList::with_capacity(E::MaxValidatorsPerSlot::to_usize()).unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
committee_bits: BitVector::new(),
};
let mut attestations_electra = vec![];
for _ in 0..E::MaxAttestationsElectra::to_usize() {
attestations_electra.push(attestation.clone());
}
let bls_to_execution_changes = vec![
SignedBlsToExecutionChange {
message: BlsToExecutionChange {
validator_index: 0,
from_bls_pubkey: PublicKeyBytes::empty(),
to_execution_address: Address::ZERO,
},
signature: Signature::empty()
};
E::max_bls_to_execution_changes()
]
.into();
let sync_aggregate = SyncAggregate {
sync_committee_signature: AggregateSignature::empty(),
sync_committee_bits: BitVector::default(),
};
BeaconBlockElectra {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyElectra {
proposer_slashings: base_block.body.proposer_slashings,
attester_slashings,
attestations: attestations_electra.into(),
deposits: base_block.body.deposits,
voluntary_exits: base_block.body.voluntary_exits,
bls_to_execution_changes,
sync_aggregate,
randao_reveal: Signature::empty(),
eth1_data: Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
deposit_count: 0,
},
graffiti: Graffiti::default(),
execution_payload: Payload::Electra::default(),
blob_kzg_commitments: VariableList::empty(),
execution_requests: ExecutionRequests::default(),
},
}
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockElectra<E, Payload> {
/// Returns an empty Electra block to be used during genesis.
fn empty(spec: &ChainSpec) -> Self {
@@ -708,79 +589,6 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockElec
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockFulu<E, Payload> {
/// Return a Fulu 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<E> = IndexedAttestationElectra {
attesting_indices: VariableList::new(vec![0_u64; E::MaxValidatorsPerSlot::to_usize()])
.unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
};
let attester_slashings = vec![
AttesterSlashingElectra {
attestation_1: indexed_attestation.clone(),
attestation_2: indexed_attestation,
};
E::max_attester_slashings_electra()
]
.into();
let attestation = AttestationElectra {
aggregation_bits: BitList::with_capacity(E::MaxValidatorsPerSlot::to_usize()).unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
committee_bits: BitVector::new(),
};
let mut attestations_electra = vec![];
for _ in 0..E::MaxAttestationsElectra::to_usize() {
attestations_electra.push(attestation.clone());
}
let bls_to_execution_changes = vec![
SignedBlsToExecutionChange {
message: BlsToExecutionChange {
validator_index: 0,
from_bls_pubkey: PublicKeyBytes::empty(),
to_execution_address: Address::ZERO,
},
signature: Signature::empty()
};
E::max_bls_to_execution_changes()
]
.into();
let sync_aggregate = SyncAggregate {
sync_committee_signature: AggregateSignature::empty(),
sync_committee_bits: BitVector::default(),
};
BeaconBlockFulu {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyFulu {
proposer_slashings: base_block.body.proposer_slashings,
attester_slashings,
attestations: attestations_electra.into(),
deposits: base_block.body.deposits,
voluntary_exits: base_block.body.voluntary_exits,
bls_to_execution_changes,
sync_aggregate,
randao_reveal: Signature::empty(),
eth1_data: Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
deposit_count: 0,
},
graffiti: Graffiti::default(),
execution_payload: Payload::Fulu::default(),
blob_kzg_commitments: VariableList::empty(),
execution_requests: ExecutionRequests::default(),
},
}
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockFulu<E, Payload> {
/// Returns an empty Fulu block to be used during genesis.
fn empty(spec: &ChainSpec) -> Self {

View File

@@ -191,7 +191,6 @@ pub struct ChainSpec {
pub max_pending_partials_per_withdrawals_sweep: u64,
pub min_per_epoch_churn_limit_electra: u64,
pub max_per_epoch_activation_exit_churn_limit: u64,
pub max_blobs_per_block_electra: u64,
/*
* Fulu hard fork params
@@ -205,10 +204,11 @@ pub struct ChainSpec {
* DAS params
*/
pub eip7594_fork_epoch: Option<Epoch>,
pub custody_requirement: u64,
pub number_of_columns: u64,
pub number_of_custody_groups: u64,
pub data_column_sidecar_subnet_count: u64,
pub number_of_columns: usize,
pub samples_per_slot: u64,
pub custody_requirement: u64,
/*
* Networking
@@ -238,7 +238,14 @@ pub struct ChainSpec {
pub max_request_data_column_sidecars: u64,
pub min_epochs_for_blob_sidecars_requests: u64,
pub blob_sidecar_subnet_count: u64,
max_blobs_per_block: u64,
pub max_blobs_per_block: u64,
/*
* Networking Electra
*/
max_blobs_per_block_electra: u64,
pub blob_sidecar_subnet_count_electra: u64,
pub max_request_blob_sidecars_electra: u64,
/*
* Networking Derived
@@ -618,6 +625,14 @@ impl ChainSpec {
}
}
pub fn max_request_blob_sidecars(&self, fork_name: ForkName) -> usize {
if fork_name.electra_enabled() {
self.max_request_blob_sidecars_electra as usize
} else {
self.max_request_blob_sidecars as usize
}
}
/// Return the value of `MAX_BLOBS_PER_BLOCK` appropriate for the fork at `epoch`.
pub fn max_blobs_per_block(&self, epoch: Epoch) -> u64 {
self.max_blobs_per_block_by_fork(self.fork_name_at_epoch(epoch))
@@ -632,10 +647,33 @@ impl ChainSpec {
}
}
pub fn data_columns_per_subnet(&self) -> usize {
/// Returns the number of data columns per custody group.
pub fn data_columns_per_group(&self) -> u64 {
self.number_of_columns
.safe_div(self.data_column_sidecar_subnet_count as usize)
.expect("Subnet count must be greater than 0")
.safe_div(self.number_of_custody_groups)
.expect("Custody group count must be greater than 0")
}
/// Returns the number of column sidecars to sample per slot.
pub fn sampling_size(&self, custody_group_count: u64) -> Result<u64, String> {
let columns_per_custody_group = self
.number_of_columns
.safe_div(self.number_of_custody_groups)
.map_err(|_| "number_of_custody_groups must be greater than 0")?;
let custody_column_count = columns_per_custody_group
.safe_mul(custody_group_count)
.map_err(|_| "Computing sampling size should not overflow")?;
Ok(std::cmp::max(custody_column_count, self.samples_per_slot))
}
pub fn custody_group_count(&self, is_supernode: bool) -> u64 {
if is_supernode {
self.number_of_custody_groups
} else {
self.custody_requirement
}
}
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
@@ -830,7 +868,6 @@ impl ChainSpec {
u64::checked_pow(2, 8)?.checked_mul(u64::checked_pow(10, 9)?)
})
.expect("calculation does not overflow"),
max_blobs_per_block_electra: default_max_blobs_per_block_electra(),
/*
* Fulu hard fork params
@@ -843,10 +880,11 @@ impl ChainSpec {
* DAS params
*/
eip7594_fork_epoch: None,
custody_requirement: 4,
data_column_sidecar_subnet_count: 128,
number_of_columns: 128,
number_of_custody_groups: 128,
data_column_sidecar_subnet_count: 128,
samples_per_slot: 8,
custody_requirement: 4,
/*
* Network specific
@@ -886,6 +924,13 @@ impl ChainSpec {
max_blobs_by_root_request: default_max_blobs_by_root_request(),
max_data_columns_by_root_request: default_data_columns_by_root_request(),
/*
* Networking Electra specific
*/
max_blobs_per_block_electra: default_max_blobs_per_block_electra(),
blob_sidecar_subnet_count_electra: default_blob_sidecar_subnet_count_electra(),
max_request_blob_sidecars_electra: default_max_request_blob_sidecars_electra(),
/*
* Application specific
*/
@@ -1161,7 +1206,6 @@ impl ChainSpec {
u64::checked_pow(2, 8)?.checked_mul(u64::checked_pow(10, 9)?)
})
.expect("calculation does not overflow"),
max_blobs_per_block_electra: default_max_blobs_per_block_electra(),
/*
* Fulu hard fork params
@@ -1174,10 +1218,12 @@ impl ChainSpec {
* DAS params
*/
eip7594_fork_epoch: None,
custody_requirement: 4,
data_column_sidecar_subnet_count: 128,
number_of_columns: 128,
number_of_custody_groups: 128,
data_column_sidecar_subnet_count: 128,
samples_per_slot: 8,
custody_requirement: 4,
/*
* Network specific
*/
@@ -1216,6 +1262,13 @@ impl ChainSpec {
max_blobs_by_root_request: default_max_blobs_by_root_request(),
max_data_columns_by_root_request: default_data_columns_by_root_request(),
/*
* Networking Electra specific
*/
max_blobs_per_block_electra: default_max_blobs_per_block_electra(),
blob_sidecar_subnet_count_electra: default_blob_sidecar_subnet_count_electra(),
max_request_blob_sidecars_electra: default_max_request_blob_sidecars_electra(),
/*
* Application specific
*/
@@ -1421,19 +1474,28 @@ pub struct Config {
#[serde(default = "default_max_blobs_per_block_electra")]
#[serde(with = "serde_utils::quoted_u64")]
max_blobs_per_block_electra: u64,
#[serde(default = "default_blob_sidecar_subnet_count_electra")]
#[serde(with = "serde_utils::quoted_u64")]
pub blob_sidecar_subnet_count_electra: u64,
#[serde(default = "default_max_request_blob_sidecars_electra")]
#[serde(with = "serde_utils::quoted_u64")]
max_request_blob_sidecars_electra: u64,
#[serde(default = "default_custody_requirement")]
#[serde(with = "serde_utils::quoted_u64")]
custody_requirement: u64,
#[serde(default = "default_data_column_sidecar_subnet_count")]
#[serde(with = "serde_utils::quoted_u64")]
data_column_sidecar_subnet_count: u64,
#[serde(default = "default_number_of_columns")]
#[serde(with = "serde_utils::quoted_u64")]
number_of_columns: u64,
#[serde(default = "default_number_of_custody_groups")]
#[serde(with = "serde_utils::quoted_u64")]
number_of_custody_groups: u64,
#[serde(default = "default_data_column_sidecar_subnet_count")]
#[serde(with = "serde_utils::quoted_u64")]
data_column_sidecar_subnet_count: u64,
#[serde(default = "default_samples_per_slot")]
#[serde(with = "serde_utils::quoted_u64")]
samples_per_slot: u64,
#[serde(default = "default_custody_requirement")]
#[serde(with = "serde_utils::quoted_u64")]
custody_requirement: u64,
}
fn default_bellatrix_fork_version() -> [u8; 4] {
@@ -1555,6 +1617,14 @@ const fn default_max_blobs_per_block() -> u64 {
6
}
const fn default_blob_sidecar_subnet_count_electra() -> u64 {
9
}
const fn default_max_request_blob_sidecars_electra() -> u64 {
1152
}
const fn default_min_per_epoch_churn_limit_electra() -> u64 {
128_000_000_000
}
@@ -1587,6 +1657,10 @@ const fn default_number_of_columns() -> u64 {
128
}
const fn default_number_of_custody_groups() -> u64 {
128
}
const fn default_samples_per_slot() -> u64 {
8
}
@@ -1787,11 +1861,14 @@ impl Config {
max_per_epoch_activation_exit_churn_limit: spec
.max_per_epoch_activation_exit_churn_limit,
max_blobs_per_block_electra: spec.max_blobs_per_block_electra,
blob_sidecar_subnet_count_electra: spec.blob_sidecar_subnet_count_electra,
max_request_blob_sidecars_electra: spec.max_request_blob_sidecars_electra,
custody_requirement: spec.custody_requirement,
number_of_columns: spec.number_of_columns,
number_of_custody_groups: spec.number_of_custody_groups,
data_column_sidecar_subnet_count: spec.data_column_sidecar_subnet_count,
number_of_columns: spec.number_of_columns as u64,
samples_per_slot: spec.samples_per_slot,
custody_requirement: spec.custody_requirement,
}
}
@@ -1865,10 +1942,13 @@ impl Config {
min_per_epoch_churn_limit_electra,
max_per_epoch_activation_exit_churn_limit,
max_blobs_per_block_electra,
custody_requirement,
data_column_sidecar_subnet_count,
blob_sidecar_subnet_count_electra,
max_request_blob_sidecars_electra,
number_of_columns,
number_of_custody_groups,
data_column_sidecar_subnet_count,
samples_per_slot,
custody_requirement,
} = self;
if preset_base != E::spec_name().to_string().as_str() {
@@ -1935,6 +2015,8 @@ impl Config {
min_per_epoch_churn_limit_electra,
max_per_epoch_activation_exit_churn_limit,
max_blobs_per_block_electra,
max_request_blob_sidecars_electra,
blob_sidecar_subnet_count_electra,
// We need to re-derive any values that might have changed in the config.
max_blocks_by_root_request: max_blocks_by_root_request_common(max_request_blocks),
@@ -1946,10 +2028,11 @@ impl Config {
max_request_data_column_sidecars,
),
custody_requirement,
number_of_columns,
number_of_custody_groups,
data_column_sidecar_subnet_count,
number_of_columns: number_of_columns as usize,
samples_per_slot,
custody_requirement,
..chain_spec.clone()
})

View File

@@ -0,0 +1,142 @@
use crate::{ChainSpec, ColumnIndex, DataColumnSubnetId};
use alloy_primitives::U256;
use itertools::Itertools;
use maplit::hashset;
use safe_arith::{ArithError, SafeArith};
use std::collections::HashSet;
pub type CustodyIndex = u64;
#[derive(Debug)]
pub enum DataColumnCustodyGroupError {
InvalidCustodyGroup(CustodyIndex),
InvalidCustodyGroupCount(u64),
ArithError(ArithError),
}
/// The `get_custody_groups` function is used to determine the custody groups that a node is
/// assigned to.
///
/// spec: https://github.com/ethereum/consensus-specs/blob/8e0d0d48e81d6c7c5a8253ab61340f5ea5bac66a/specs/fulu/das-core.md#get_custody_groups
pub fn get_custody_groups(
raw_node_id: [u8; 32],
custody_group_count: u64,
spec: &ChainSpec,
) -> Result<HashSet<CustodyIndex>, DataColumnCustodyGroupError> {
if custody_group_count > spec.number_of_custody_groups {
return Err(DataColumnCustodyGroupError::InvalidCustodyGroupCount(
custody_group_count,
));
}
let mut custody_groups: HashSet<u64> = hashset![];
let mut current_id = U256::from_be_slice(&raw_node_id);
while custody_groups.len() < custody_group_count as usize {
let mut node_id_bytes = [0u8; 32];
node_id_bytes.copy_from_slice(current_id.as_le_slice());
let hash = ethereum_hashing::hash_fixed(&node_id_bytes);
let hash_prefix: [u8; 8] = hash[0..8]
.try_into()
.expect("hash_fixed produces a 32 byte array");
let hash_prefix_u64 = u64::from_le_bytes(hash_prefix);
let custody_group = hash_prefix_u64
.safe_rem(spec.number_of_custody_groups)
.expect("spec.number_of_custody_groups must not be zero");
custody_groups.insert(custody_group);
current_id = current_id.wrapping_add(U256::from(1u64));
}
Ok(custody_groups)
}
/// Returns the columns that are associated with a given custody group.
///
/// spec: https://github.com/ethereum/consensus-specs/blob/8e0d0d48e81d6c7c5a8253ab61340f5ea5bac66a/specs/fulu/das-core.md#compute_columns_for_custody_group
pub fn compute_columns_for_custody_group(
custody_group: CustodyIndex,
spec: &ChainSpec,
) -> Result<impl Iterator<Item = ColumnIndex>, DataColumnCustodyGroupError> {
let number_of_custody_groups = spec.number_of_custody_groups;
if custody_group >= number_of_custody_groups {
return Err(DataColumnCustodyGroupError::InvalidCustodyGroup(
custody_group,
));
}
let mut columns = Vec::new();
for i in 0..spec.data_columns_per_group() {
let column = number_of_custody_groups
.safe_mul(i)
.and_then(|v| v.safe_add(custody_group))
.map_err(DataColumnCustodyGroupError::ArithError)?;
columns.push(column);
}
Ok(columns.into_iter())
}
pub fn compute_subnets_for_node(
raw_node_id: [u8; 32],
custody_group_count: u64,
spec: &ChainSpec,
) -> Result<HashSet<DataColumnSubnetId>, DataColumnCustodyGroupError> {
let custody_groups = get_custody_groups(raw_node_id, custody_group_count, spec)?;
let mut subnets = HashSet::new();
for custody_group in custody_groups {
let custody_group_subnets = compute_subnets_from_custody_group(custody_group, spec)?;
subnets.extend(custody_group_subnets);
}
Ok(subnets)
}
/// Returns the subnets that are associated with a given custody group.
pub fn compute_subnets_from_custody_group(
custody_group: CustodyIndex,
spec: &ChainSpec,
) -> Result<impl Iterator<Item = DataColumnSubnetId> + '_, DataColumnCustodyGroupError> {
let result = compute_columns_for_custody_group(custody_group, spec)?
.map(|column_index| DataColumnSubnetId::from_column_index(column_index, spec))
.unique();
Ok(result)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_compute_columns_for_custody_group() {
let mut spec = ChainSpec::mainnet();
spec.number_of_custody_groups = 64;
spec.number_of_columns = 128;
let columns_per_custody_group = spec.number_of_columns / spec.number_of_custody_groups;
for custody_group in 0..spec.number_of_custody_groups {
let columns = compute_columns_for_custody_group(custody_group, &spec)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(columns.len(), columns_per_custody_group as usize);
}
}
#[test]
fn test_compute_subnets_from_custody_group() {
let mut spec = ChainSpec::mainnet();
spec.number_of_custody_groups = 64;
spec.number_of_columns = 256;
spec.data_column_sidecar_subnet_count = 128;
let subnets_per_custody_group =
spec.data_column_sidecar_subnet_count / spec.number_of_custody_groups;
for custody_group in 0..spec.number_of_custody_groups {
let subnets = compute_subnets_from_custody_group(custody_group, &spec)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(subnets.len(), subnets_per_custody_group as usize);
}
}
}

View File

@@ -1,11 +1,8 @@
//! Identifies each data column subnet by an integer identifier.
use crate::data_column_sidecar::ColumnIndex;
use crate::{ChainSpec, EthSpec};
use alloy_primitives::U256;
use itertools::Itertools;
use crate::ChainSpec;
use safe_arith::{ArithError, SafeArith};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fmt::{self, Display};
use std::ops::{Deref, DerefMut};
@@ -18,76 +15,14 @@ impl DataColumnSubnetId {
id.into()
}
pub fn from_column_index<E: EthSpec>(column_index: usize, spec: &ChainSpec) -> Self {
(column_index
.safe_rem(spec.data_column_sidecar_subnet_count as usize)
pub fn from_column_index(column_index: ColumnIndex, spec: &ChainSpec) -> Self {
column_index
.safe_rem(spec.data_column_sidecar_subnet_count)
.expect(
"data_column_sidecar_subnet_count should never be zero if this function is called",
) as u64)
)
.into()
}
#[allow(clippy::arithmetic_side_effects)]
pub fn columns<E: EthSpec>(&self, spec: &ChainSpec) -> impl Iterator<Item = ColumnIndex> {
let subnet = self.0;
let data_column_sidecar_subnet = spec.data_column_sidecar_subnet_count;
let columns_per_subnet = spec.data_columns_per_subnet() as u64;
(0..columns_per_subnet).map(move |i| data_column_sidecar_subnet * i + subnet)
}
/// Compute required subnets to subscribe to given the node id.
#[allow(clippy::arithmetic_side_effects)]
pub fn compute_custody_subnets<E: EthSpec>(
raw_node_id: [u8; 32],
custody_subnet_count: u64,
spec: &ChainSpec,
) -> Result<impl Iterator<Item = DataColumnSubnetId>, Error> {
if custody_subnet_count > spec.data_column_sidecar_subnet_count {
return Err(Error::InvalidCustodySubnetCount(custody_subnet_count));
}
let mut subnets: HashSet<u64> = HashSet::new();
let mut current_id = U256::from_be_slice(&raw_node_id);
while (subnets.len() as u64) < custody_subnet_count {
let mut node_id_bytes = [0u8; 32];
node_id_bytes.copy_from_slice(current_id.as_le_slice());
let hash = ethereum_hashing::hash_fixed(&node_id_bytes);
let hash_prefix: [u8; 8] = hash[0..8]
.try_into()
.expect("hash_fixed produces a 32 byte array");
let hash_prefix_u64 = u64::from_le_bytes(hash_prefix);
let subnet = hash_prefix_u64 % spec.data_column_sidecar_subnet_count;
if !subnets.contains(&subnet) {
subnets.insert(subnet);
}
if current_id == U256::MAX {
current_id = U256::ZERO
}
current_id += U256::from(1u64)
}
Ok(subnets.into_iter().map(DataColumnSubnetId::new))
}
/// Compute the custody subnets for a given node id with the default `custody_requirement`.
/// This operation should be infallable, and empty iterator is returned if it fails unexpectedly.
pub fn compute_custody_requirement_subnets<E: EthSpec>(
node_id: [u8; 32],
spec: &ChainSpec,
) -> impl Iterator<Item = DataColumnSubnetId> {
Self::compute_custody_subnets::<E>(node_id, spec.custody_requirement, spec)
.expect("should compute default custody subnets")
}
pub fn compute_custody_columns<E: EthSpec>(
raw_node_id: [u8; 32],
custody_subnet_count: u64,
spec: &ChainSpec,
) -> Result<impl Iterator<Item = ColumnIndex>, Error> {
Self::compute_custody_subnets::<E>(raw_node_id, custody_subnet_count, spec)
.map(|subnet| subnet.flat_map(|subnet| subnet.columns::<E>(spec)).sorted())
}
}
impl Display for DataColumnSubnetId {
@@ -139,88 +74,3 @@ impl From<ArithError> for Error {
Error::ArithError(e)
}
}
#[cfg(test)]
mod test {
use crate::data_column_subnet_id::DataColumnSubnetId;
use crate::MainnetEthSpec;
use crate::Uint256;
use crate::{EthSpec, GnosisEthSpec, MinimalEthSpec};
type E = MainnetEthSpec;
#[test]
fn test_compute_subnets_for_data_column() {
let spec = E::default_spec();
let node_ids = [
"0",
"88752428858350697756262172400162263450541348766581994718383409852729519486397",
"18732750322395381632951253735273868184515463718109267674920115648614659369468",
"27726842142488109545414954493849224833670205008410190955613662332153332462900",
"39755236029158558527862903296867805548949739810920318269566095185775868999998",
"31899136003441886988955119620035330314647133604576220223892254902004850516297",
"58579998103852084482416614330746509727562027284701078483890722833654510444626",
"28248042035542126088870192155378394518950310811868093527036637864276176517397",
"60930578857433095740782970114409273483106482059893286066493409689627770333527",
"103822458477361691467064888613019442068586830412598673713899771287914656699997",
]
.into_iter()
.map(|v| Uint256::from_str_radix(v, 10).unwrap().to_be_bytes::<32>())
.collect::<Vec<_>>();
let custody_requirement = 4;
for node_id in node_ids {
let computed_subnets = DataColumnSubnetId::compute_custody_subnets::<E>(
node_id,
custody_requirement,
&spec,
)
.unwrap();
let computed_subnets: Vec<_> = computed_subnets.collect();
// the number of subnets is equal to the custody requirement
assert_eq!(computed_subnets.len() as u64, custody_requirement);
let subnet_count = spec.data_column_sidecar_subnet_count;
for subnet in computed_subnets {
let columns: Vec<_> = subnet.columns::<E>(&spec).collect();
// the number of columns is equal to the specified number of columns per subnet
assert_eq!(columns.len(), spec.data_columns_per_subnet());
for pair in columns.windows(2) {
// each successive column index is offset by the number of subnets
assert_eq!(pair[1] - pair[0], subnet_count);
}
}
}
}
#[test]
fn test_compute_custody_requirement_subnets_never_panics() {
let node_id = [1u8; 32];
test_compute_custody_requirement_subnets_with_spec::<MainnetEthSpec>(node_id);
test_compute_custody_requirement_subnets_with_spec::<MinimalEthSpec>(node_id);
test_compute_custody_requirement_subnets_with_spec::<GnosisEthSpec>(node_id);
}
fn test_compute_custody_requirement_subnets_with_spec<E: EthSpec>(node_id: [u8; 32]) {
let _ = DataColumnSubnetId::compute_custody_requirement_subnets::<E>(
node_id,
&E::default_spec(),
);
}
#[test]
fn test_columns_subnet_conversion() {
let spec = E::default_spec();
for subnet in 0..spec.data_column_sidecar_subnet_count {
let subnet_id = DataColumnSubnetId::new(subnet);
for column_index in subnet_id.columns::<E>(&spec) {
assert_eq!(
subnet_id,
DataColumnSubnetId::from_column_index::<E>(column_index as usize, &spec)
);
}
}
}
}

View File

@@ -128,58 +128,6 @@ impl<E: EthSpec> ExecutionPayload<E> {
// Max size of variable length `transactions` field
+ (E::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + E::max_bytes_per_transaction()))
}
#[allow(clippy::arithmetic_side_effects)]
/// Returns the maximum size of an execution payload.
pub fn max_execution_payload_capella_size() -> usize {
// Fixed part
ExecutionPayloadCapella::<E>::default().as_ssz_bytes().len()
// Max size of variable length `extra_data` field
+ (E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len())
// Max size of variable length `transactions` field
+ (E::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + E::max_bytes_per_transaction()))
// Max size of variable length `withdrawals` field
+ (E::max_withdrawals_per_payload() * <Withdrawal as Encode>::ssz_fixed_len())
}
#[allow(clippy::arithmetic_side_effects)]
/// Returns the maximum size of an execution payload.
pub fn max_execution_payload_deneb_size() -> usize {
// Fixed part
ExecutionPayloadDeneb::<E>::default().as_ssz_bytes().len()
// Max size of variable length `extra_data` field
+ (E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len())
// Max size of variable length `transactions` field
+ (E::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + E::max_bytes_per_transaction()))
// Max size of variable length `withdrawals` field
+ (E::max_withdrawals_per_payload() * <Withdrawal as Encode>::ssz_fixed_len())
}
#[allow(clippy::arithmetic_side_effects)]
/// Returns the maximum size of an execution payload.
pub fn max_execution_payload_electra_size() -> usize {
// Fixed part
ExecutionPayloadElectra::<E>::default().as_ssz_bytes().len()
// Max size of variable length `extra_data` field
+ (E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len())
// Max size of variable length `transactions` field
+ (E::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + E::max_bytes_per_transaction()))
// Max size of variable length `withdrawals` field
+ (E::max_withdrawals_per_payload() * <Withdrawal as Encode>::ssz_fixed_len())
}
#[allow(clippy::arithmetic_side_effects)]
/// Returns the maximum size of an execution payload.
pub fn max_execution_payload_fulu_size() -> usize {
// Fixed part
ExecutionPayloadFulu::<E>::default().as_ssz_bytes().len()
// Max size of variable length `extra_data` field
+ (E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len())
// Max size of variable length `transactions` field
+ (E::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + E::max_bytes_per_transaction()))
// Max size of variable length `withdrawals` field
+ (E::max_withdrawals_per_payload() * <Withdrawal as Encode>::ssz_fixed_len())
}
}
impl<E: EthSpec> ForkVersionDeserialize for ExecutionPayload<E> {

View File

@@ -104,6 +104,7 @@ pub mod slot_data;
pub mod sqlite;
pub mod blob_sidecar;
pub mod data_column_custody_group;
pub mod data_column_sidecar;
pub mod data_column_subnet_id;
pub mod light_client_header;
@@ -117,7 +118,7 @@ pub use crate::aggregate_and_proof::{
};
pub use crate::attestation::{
Attestation, AttestationBase, AttestationElectra, AttestationRef, AttestationRefMut,
Error as AttestationError,
Error as AttestationError, SingleAttestation,
};
pub use crate::attestation_data::AttestationData;
pub use crate::attestation_duty::AttestationDuty;

View File

@@ -1,4 +1,5 @@
//! Identifies each shard by an integer identifier.
use crate::SingleAttestation;
use crate::{AttestationRef, ChainSpec, CommitteeIndex, EthSpec, Slot};
use alloy_primitives::{bytes::Buf, U256};
use safe_arith::{ArithError, SafeArith};
@@ -57,6 +58,21 @@ impl SubnetId {
)
}
/// Compute the subnet for an attestation where each slot in the
/// attestation epoch contains `committee_count_per_slot` committees.
pub fn compute_subnet_for_single_attestation<E: EthSpec>(
attestation: &SingleAttestation,
committee_count_per_slot: u64,
spec: &ChainSpec,
) -> Result<SubnetId, ArithError> {
Self::compute_subnet::<E>(
attestation.data.slot,
attestation.committee_index as u64,
committee_count_per_slot,
spec,
)
}
/// Compute the subnet for an attestation with `attestation.data.slot == slot` and
/// `attestation.data.index == committee_index` where each slot in the attestation epoch
/// contains `committee_count_at_slot` committees.