Fulu update to spec v1.6.0-alpha.4 (#7890)

Fulu update to spec [v1.6.0-alpha.4](https://github.com/ethereum/consensus-specs/releases/tag/v1.6.0-alpha.4).
- Make `number_of_columns` a preset
- Optimise `get_custody_groups` to avoid computing if cgc = 128
- Add support for additional typenum values in type_dispatch macro
This commit is contained in:
Jimmy Chen
2025-08-20 12:05:04 +10:00
committed by GitHub
parent 95882bfa66
commit b4704eab4a
56 changed files with 214 additions and 400 deletions

View File

@@ -8,3 +8,7 @@ FIELD_ELEMENTS_PER_CELL: 64
FIELD_ELEMENTS_PER_EXT_BLOB: 8192
# uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments'))
KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4
# FIELD_ELEMENTS_PER_EXT_BLOB // FIELD_ELEMENTS_PER_CELL (= 128)
CELLS_PER_EXT_BLOB: 128
# CELLS_PER_EXT_BLOB (= 128)
NUMBER_OF_COLUMNS: 128

View File

@@ -8,3 +8,7 @@ FIELD_ELEMENTS_PER_CELL: 64
FIELD_ELEMENTS_PER_EXT_BLOB: 8192
# uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments'))
KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4
# FIELD_ELEMENTS_PER_EXT_BLOB // FIELD_ELEMENTS_PER_CELL (= 128)
CELLS_PER_EXT_BLOB: 128
# CELLS_PER_EXT_BLOB (= 128)
NUMBER_OF_COLUMNS: 128

View File

@@ -8,3 +8,7 @@ FIELD_ELEMENTS_PER_CELL: 64
FIELD_ELEMENTS_PER_EXT_BLOB: 8192
# uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments'))
KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4
# FIELD_ELEMENTS_PER_EXT_BLOB // FIELD_ELEMENTS_PER_CELL (= 128)
CELLS_PER_EXT_BLOB: 128
# CELLS_PER_EXT_BLOB (= 128)
NUMBER_OF_COLUMNS: 128

View File

@@ -1094,22 +1094,12 @@ mod tests {
slot: fulu_slot,
..<_>::random_for_test(rng)
});
// It's invalid to have a Fulu block with a epoch lower than the fork epoch.
let _bad_block = {
let mut bad = good_block.clone();
*bad.slot_mut() = electra_slot;
bad
};
assert_eq!(
BeaconBlock::from_ssz_bytes(&good_block.as_ssz_bytes(), &spec)
.expect("good fulu block can be decoded"),
good_block
);
// TODO(fulu): Uncomment once Fulu has features since without features
// and with an Electra slot it decodes successfully to Electra.
//BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec)
// .expect_err("bad fulu block cannot be decoded");
}
}
}

View File

@@ -201,7 +201,6 @@ pub struct ChainSpec {
pub fulu_fork_version: [u8; 4],
/// The Fulu fork epoch is optional, with `None` representing "Fulu never happens".
pub fulu_fork_epoch: Option<Epoch>,
pub number_of_columns: u64,
pub number_of_custody_groups: u64,
pub data_column_sidecar_subnet_count: u64,
pub samples_per_slot: u64,
@@ -773,20 +772,19 @@ impl ChainSpec {
}
/// Returns the number of data columns per custody group.
pub fn data_columns_per_group(&self) -> u64 {
self.number_of_columns
pub fn data_columns_per_group<E: EthSpec>(&self) -> u64 {
(E::number_of_columns() as u64)
.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_columns(&self, custody_group_count: u64) -> Result<usize, String> {
pub fn sampling_size_columns<E: EthSpec>(
&self,
custody_group_count: u64,
) -> Result<usize, String> {
let sampling_size_groups = self.sampling_size_custody_groups(custody_group_count)?;
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 columns_per_custody_group = self.data_columns_per_group::<E>();
let sampling_size_columns = columns_per_custody_group
.safe_mul(sampling_size_groups)
@@ -1048,7 +1046,6 @@ impl ChainSpec {
custody_requirement: 4,
number_of_custody_groups: 128,
data_column_sidecar_subnet_count: 128,
number_of_columns: 128,
samples_per_slot: 8,
validator_custody_requirement: 8,
balance_per_additional_custody_group: 32000000000,
@@ -1088,7 +1085,6 @@ impl ChainSpec {
max_blocks_by_root_request: default_max_blocks_by_root_request(),
max_blocks_by_root_request_deneb: default_max_blocks_by_root_request_deneb(),
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
@@ -1103,6 +1099,7 @@ impl ChainSpec {
blob_schedule: BlobSchedule::default(),
min_epochs_for_data_column_sidecars_requests:
default_min_epochs_for_data_column_sidecars_requests(),
max_data_columns_by_root_request: default_data_columns_by_root_request(),
/*
* Application specific
@@ -1386,7 +1383,6 @@ impl ChainSpec {
custody_requirement: 4,
number_of_custody_groups: 128,
data_column_sidecar_subnet_count: 128,
number_of_columns: 128,
samples_per_slot: 8,
validator_custody_requirement: 8,
balance_per_additional_custody_group: 32000000000,
@@ -1426,7 +1422,6 @@ impl ChainSpec {
max_blocks_by_root_request: default_max_blocks_by_root_request(),
max_blocks_by_root_request_deneb: default_max_blocks_by_root_request_deneb(),
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
@@ -1441,6 +1436,7 @@ impl ChainSpec {
blob_schedule: BlobSchedule::default(),
min_epochs_for_data_column_sidecars_requests:
default_min_epochs_for_data_column_sidecars_requests(),
max_data_columns_by_root_request: default_data_columns_by_root_request(),
/*
* Application specific
@@ -1759,9 +1755,6 @@ pub struct Config {
#[serde(with = "serde_utils::quoted_u64")]
max_request_blob_sidecars_electra: 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,
@@ -1938,10 +1931,6 @@ const fn default_data_column_sidecar_subnet_count() -> u64 {
128
}
const fn default_number_of_columns() -> u64 {
128
}
const fn default_number_of_custody_groups() -> u64 {
128
}
@@ -1987,19 +1976,15 @@ fn max_blobs_by_root_request_common(max_request_blob_sidecars: u64) -> usize {
.len()
}
fn max_data_columns_by_root_request_common(
max_request_blocks: u64,
number_of_columns: u64,
) -> usize {
fn max_data_columns_by_root_request_common<E: EthSpec>(max_request_blocks: u64) -> usize {
let max_request_blocks = max_request_blocks as usize;
let number_of_columns = number_of_columns as usize;
let empty_data_columns_by_root_id = DataColumnsByRootIdentifier {
block_root: Hash256::zero(),
columns: RuntimeVariableList::from_vec(vec![0; number_of_columns], number_of_columns),
columns: VariableList::from(vec![0]),
};
RuntimeVariableList::<DataColumnsByRootIdentifier>::from_vec(
RuntimeVariableList::<DataColumnsByRootIdentifier<E>>::from_vec(
vec![empty_data_columns_by_root_id; max_request_blocks],
max_request_blocks,
)
@@ -2020,10 +2005,7 @@ fn default_max_blobs_by_root_request() -> usize {
}
fn default_data_columns_by_root_request() -> usize {
max_data_columns_by_root_request_common(
default_max_request_blocks_deneb(),
default_number_of_columns(),
)
max_data_columns_by_root_request_common::<MainnetEthSpec>(default_max_request_blocks_deneb())
}
impl Default for Config {
@@ -2165,7 +2147,6 @@ impl Config {
blob_sidecar_subnet_count_electra: spec.blob_sidecar_subnet_count_electra,
max_request_blob_sidecars_electra: spec.max_request_blob_sidecars_electra,
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,
samples_per_slot: spec.samples_per_slot,
@@ -2248,7 +2229,6 @@ impl Config {
max_blobs_per_block_electra,
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,
@@ -2330,12 +2310,10 @@ impl Config {
max_request_blocks_deneb,
),
max_blobs_by_root_request: max_blobs_by_root_request_common(max_request_blob_sidecars),
max_data_columns_by_root_request: max_data_columns_by_root_request_common(
max_data_columns_by_root_request: max_data_columns_by_root_request_common::<E>(
max_request_blocks_deneb,
number_of_columns,
),
number_of_columns,
number_of_custody_groups,
data_column_sidecar_subnet_count,
samples_per_slot,
@@ -2815,7 +2793,6 @@ mod yaml_tests {
DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa
CUSTODY_REQUIREMENT: 1
DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128
NUMBER_OF_COLUMNS: 128
SAMPLES_PER_SLOT: 8
"#;

View File

@@ -1,4 +1,4 @@
use crate::{ChainSpec, ColumnIndex, DataColumnSubnetId};
use crate::{ChainSpec, ColumnIndex, DataColumnSubnetId, EthSpec};
use alloy_primitives::U256;
use itertools::Itertools;
use safe_arith::{ArithError, SafeArith};
@@ -24,8 +24,12 @@ pub fn get_custody_groups(
custody_group_count: u64,
spec: &ChainSpec,
) -> Result<HashSet<CustodyIndex>, DataColumnCustodyGroupError> {
get_custody_groups_ordered(raw_node_id, custody_group_count, spec)
.map(|custody_groups| custody_groups.into_iter().collect())
if custody_group_count == spec.number_of_custody_groups {
Ok(HashSet::from_iter(0..spec.number_of_custody_groups))
} else {
get_custody_groups_ordered(raw_node_id, custody_group_count, spec)
.map(|custody_groups| custody_groups.into_iter().collect())
}
}
/// Returns a deterministically ordered list of custody groups assigned to a node,
@@ -75,7 +79,7 @@ pub fn get_custody_groups_ordered(
/// 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(
pub fn compute_columns_for_custody_group<E: EthSpec>(
custody_group: CustodyIndex,
spec: &ChainSpec,
) -> Result<impl Iterator<Item = ColumnIndex>, DataColumnCustodyGroupError> {
@@ -87,7 +91,7 @@ pub fn compute_columns_for_custody_group(
}
let mut columns = Vec::new();
for i in 0..spec.data_columns_per_group() {
for i in 0..spec.data_columns_per_group::<E>() {
let column = number_of_custody_groups
.safe_mul(i)
.and_then(|v| v.safe_add(custody_group))
@@ -98,7 +102,7 @@ pub fn compute_columns_for_custody_group(
Ok(columns.into_iter())
}
pub fn compute_subnets_for_node(
pub fn compute_subnets_for_node<E: EthSpec>(
raw_node_id: [u8; 32],
custody_group_count: u64,
spec: &ChainSpec,
@@ -107,7 +111,7 @@ pub fn compute_subnets_for_node(
let mut subnets = HashSet::new();
for custody_group in custody_groups {
let custody_group_subnets = compute_subnets_from_custody_group(custody_group, spec)?;
let custody_group_subnets = compute_subnets_from_custody_group::<E>(custody_group, spec)?;
subnets.extend(custody_group_subnets);
}
@@ -115,11 +119,11 @@ pub fn compute_subnets_for_node(
}
/// Returns the subnets that are associated with a given custody group.
pub fn compute_subnets_from_custody_group(
pub fn compute_subnets_from_custody_group<E: EthSpec>(
custody_group: CustodyIndex,
spec: &ChainSpec,
) -> Result<impl Iterator<Item = DataColumnSubnetId> + '_, DataColumnCustodyGroupError> {
let result = compute_columns_for_custody_group(custody_group, spec)?
let result = compute_columns_for_custody_group::<E>(custody_group, spec)?
.map(|column_index| DataColumnSubnetId::from_column_index(column_index, spec))
.unique();
Ok(result)
@@ -128,19 +132,23 @@ pub fn compute_subnets_from_custody_group(
#[cfg(test)]
mod test {
use super::*;
use crate::MainnetEthSpec;
type E = MainnetEthSpec;
#[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;
let columns_per_custody_group =
E::number_of_columns() / (spec.number_of_custody_groups as usize);
for custody_group in 0..spec.number_of_custody_groups {
let columns = compute_columns_for_custody_group(custody_group, &spec)
let columns = compute_columns_for_custody_group::<E>(custody_group, &spec)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(columns.len(), columns_per_custody_group as usize);
assert_eq!(columns.len(), columns_per_custody_group);
}
}
@@ -148,14 +156,13 @@ mod 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)
let subnets = compute_subnets_from_custody_group::<E>(custody_group, &spec)
.unwrap()
.collect::<Vec<_>>();
assert_eq!(subnets.len(), subnets_per_custody_group as usize);

View File

@@ -2,19 +2,17 @@ use crate::beacon_block_body::{BLOB_KZG_COMMITMENTS_INDEX, KzgCommitments};
use crate::context_deserialize;
use crate::test_utils::TestRandom;
use crate::{
BeaconBlockHeader, BeaconStateError, Epoch, EthSpec, ForkName, Hash256, RuntimeVariableList,
BeaconBlockHeader, BeaconStateError, Epoch, EthSpec, ForkName, Hash256,
SignedBeaconBlockHeader, Slot,
};
use bls::Signature;
use context_deserialize::ContextDeserialize;
use derivative::Derivative;
use kzg::Error as KzgError;
use kzg::{KzgCommitment, KzgProof};
use merkle_proof::verify_merkle_proof;
use safe_arith::ArithError;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};
use ssz::{DecodeError, Encode};
use serde::{Deserialize, Serialize};
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use ssz_types::Error as SszError;
use ssz_types::{FixedVector, VariableList};
@@ -28,69 +26,11 @@ pub type Cell<E> = FixedVector<u8, <E as EthSpec>::BytesPerCell>;
pub type DataColumn<E> = VariableList<Cell<E>, <E as EthSpec>::MaxBlobCommitmentsPerBlock>;
/// Identifies a set of data columns associated with a specific beacon block.
#[derive(Encode, Clone, Debug, PartialEq, TreeHash)]
pub struct DataColumnsByRootIdentifier {
#[derive(Encode, Decode, Clone, Debug, PartialEq, TreeHash, Deserialize)]
#[context_deserialize(ForkName)]
pub struct DataColumnsByRootIdentifier<E: EthSpec> {
pub block_root: Hash256,
pub columns: RuntimeVariableList<ColumnIndex>,
}
impl<'de> ContextDeserialize<'de, (ForkName, usize)> for DataColumnsByRootIdentifier {
fn context_deserialize<D>(deserializer: D, context: (ForkName, usize)) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
block_root: Hash256,
columns: serde_json::Value,
}
let helper = Helper::deserialize(deserializer)?;
Ok(Self {
block_root: helper.block_root,
columns: RuntimeVariableList::context_deserialize(helper.columns, context)
.map_err(Error::custom)?,
})
}
}
impl DataColumnsByRootIdentifier {
pub fn from_ssz_bytes(bytes: &[u8], num_columns: usize) -> Result<Self, DecodeError> {
let mut builder = ssz::SszDecoderBuilder::new(bytes);
builder.register_type::<Hash256>()?;
builder.register_anonymous_variable_length_item()?;
let mut decoder = builder.build()?;
let block_root = decoder.decode_next()?;
let columns = decoder
.decode_next_with(|bytes| RuntimeVariableList::from_ssz_bytes(bytes, num_columns))?;
Ok(DataColumnsByRootIdentifier {
block_root,
columns,
})
}
}
impl RuntimeVariableList<DataColumnsByRootIdentifier> {
pub fn from_ssz_bytes_with_nested(
bytes: &[u8],
max_len: usize,
num_columns: usize,
) -> Result<Self, DecodeError> {
if bytes.is_empty() {
return Ok(RuntimeVariableList::empty(max_len));
}
let vec = ssz::decode_list_of_variable_length_items::<Vec<u8>, Vec<Vec<u8>>>(
bytes,
Some(max_len),
)?
.into_iter()
.map(|bytes| DataColumnsByRootIdentifier::from_ssz_bytes(&bytes, num_columns))
.collect::<Result<Vec<_>, _>>()?;
Ok(RuntimeVariableList::from_vec(vec, max_len))
}
pub columns: VariableList<ColumnIndex, E::NumberOfColumns>,
}
pub type DataColumnSidecarList<E> = Vec<Arc<DataColumnSidecar<E>>>;
@@ -228,45 +168,3 @@ impl From<SszError> for DataColumnSidecarError {
Self::SszError(e)
}
}
#[cfg(test)]
mod test {
use super::*;
use bls::FixedBytesExtended;
#[test]
fn round_trip_dcbroot_list() {
let max_outer = 5;
let max_inner = 10;
let data = vec![
DataColumnsByRootIdentifier {
block_root: Hash256::from_low_u64_be(10),
columns: RuntimeVariableList::<ColumnIndex>::from_vec(vec![1u64, 2, 3], max_inner),
},
DataColumnsByRootIdentifier {
block_root: Hash256::from_low_u64_be(20),
columns: RuntimeVariableList::<ColumnIndex>::from_vec(vec![4u64, 5], max_inner),
},
];
let list = RuntimeVariableList::from_vec(data.clone(), max_outer);
let ssz_bytes = list.as_ssz_bytes();
let decoded =
RuntimeVariableList::<DataColumnsByRootIdentifier>::from_ssz_bytes_with_nested(
&ssz_bytes, max_outer, max_inner,
)
.expect("should decode list of DataColumnsByRootIdentifier");
assert_eq!(decoded.len(), data.len());
for (original, decoded) in data.iter().zip(decoded.iter()) {
assert_eq!(decoded.block_root, original.block_root);
assert_eq!(
decoded.columns.iter().copied().collect::<Vec<_>>(),
original.columns.iter().copied().collect::<Vec<_>>()
);
}
}
}

View File

@@ -111,11 +111,13 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
type BytesPerFieldElement: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type KzgCommitmentInclusionProofDepth: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/*
* New in PeerDAS
* New in Fulu
*/
type FieldElementsPerCell: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type FieldElementsPerExtBlob: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type KzgCommitmentsInclusionProofDepth: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type CellsPerExtBlob: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type NumberOfColumns: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type ProposerLookaheadSlots: Unsigned + Clone + Sync + Send + Debug + PartialEq;
/*
* Derived values (set these CAREFULLY)
@@ -378,6 +380,14 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
Self::KzgCommitmentsInclusionProofDepth::to_usize()
}
fn cells_per_ext_blob() -> usize {
Self::CellsPerExtBlob::to_usize()
}
fn number_of_columns() -> usize {
Self::NumberOfColumns::to_usize()
}
fn proposer_lookahead_slots() -> usize {
Self::ProposerLookaheadSlots::to_usize()
}
@@ -433,6 +443,8 @@ impl EthSpec for MainnetEthSpec {
type MaxCellsPerBlock = U33554432;
type KzgCommitmentInclusionProofDepth = U17;
type KzgCommitmentsInclusionProofDepth = U4; // inclusion of the whole list of commitments
type CellsPerExtBlob = U128;
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U64; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count
type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch
@@ -487,6 +499,8 @@ impl EthSpec for MinimalEthSpec {
type MaxCellsPerBlock = U33554432;
type BytesPerCell = U2048;
type KzgCommitmentsInclusionProofDepth = U4;
type CellsPerExtBlob = U128;
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U16; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
params_from_eth_spec!(MainnetEthSpec {
@@ -584,6 +598,8 @@ impl EthSpec for GnosisEthSpec {
type MaxCellsPerBlock = U33554432;
type BytesPerCell = U2048;
type KzgCommitmentsInclusionProofDepth = U4;
type CellsPerExtBlob = U128;
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
fn default_spec() -> ChainSpec {

View File

@@ -297,6 +297,10 @@ pub struct FuluPreset {
pub field_elements_per_ext_blob: u64,
#[serde(with = "serde_utils::quoted_u64")]
pub kzg_commitments_inclusion_proof_depth: u64,
#[serde(with = "serde_utils::quoted_u64")]
pub cells_per_ext_blob: u64,
#[serde(with = "serde_utils::quoted_u64")]
pub number_of_columns: u64,
}
impl FuluPreset {
@@ -306,6 +310,8 @@ impl FuluPreset {
field_elements_per_ext_blob: E::field_elements_per_ext_blob() as u64,
kzg_commitments_inclusion_proof_depth: E::kzg_commitments_inclusion_proof_depth()
as u64,
cells_per_ext_blob: E::cells_per_ext_blob() as u64,
number_of_columns: E::number_of_columns() as u64,
}
}
}