Check ChainSpec consistency with upstream config.yaml (#9008)

Closes:

- https://github.com/sigp/lighthouse/issues/9002


  - Commit `config.yaml` for minimal and mainnet to `consensus/types/configs`. For now we omit any auto-downloading logic, to avoid the hassles of dealing with Github rate limits etc on CI. Unfortunately these files are NOT bundled inside the spec tests.
- Fix the values of `min_builder_withdrawability_delay` for minimal and mainnet. These discrepancies aren't caught by the current spec tests, because the spec tests are missing data: https://github.com/ethereum/consensus-specs/pull/5005. Will be fixed in the next release/when we update to nightly.
- Fix the blob schedule for `minimal`, which should be empty, NOT inherited from mainnet.
- Keep `SECONDS_PER_SLOT` for now because the Kurtosis tests fail upon their complete removal. We will be able to completely remove `SECONDS_PER_SLOT` soon.


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Michael Sproul
2026-03-30 17:43:57 +11:00
committed by dapplion
parent 9f08f48880
commit a1534bbfb3
7 changed files with 703 additions and 50 deletions

View File

@@ -0,0 +1,227 @@
# Mainnet config
# Extends the mainnet preset
PRESET_BASE: 'mainnet'
# Free-form short name of the network that this configuration applies to - known
# canonical network names include:
# * 'mainnet' - there can be only one
# * 'sepolia' - testnet
# * 'holesky' - testnet
# * 'hoodi' - testnet
# Must match the regex: [a-z0-9\-]
CONFIG_NAME: 'mainnet'
# Transition
# ---------------------------------------------------------------
# Estimated on Sept 15, 2022
TERMINAL_TOTAL_DIFFICULTY: 58750000000000000000000
# By default, don't use these params
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
# Genesis
# ---------------------------------------------------------------
# 2**14 (= 16,384) validators
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384
# Dec 1, 2020, 12pm UTC
MIN_GENESIS_TIME: 1606824000
# Initial fork version for mainnet
GENESIS_FORK_VERSION: 0x00000000
# 7 * 24 * 3,600 (= 604,800) seconds, 7 days
GENESIS_DELAY: 604800
# Forking
# ---------------------------------------------------------------
# Some forks are disabled for now:
# - These may be re-assigned to another fork-version later
# - Temporarily set to max uint64 value: 2**64 - 1
# Altair
ALTAIR_FORK_VERSION: 0x01000000
ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC
# Bellatrix
BELLATRIX_FORK_VERSION: 0x02000000
BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC
# Capella
CAPELLA_FORK_VERSION: 0x03000000
CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC
# Deneb
DENEB_FORK_VERSION: 0x04000000
DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC
# Electra
ELECTRA_FORK_VERSION: 0x05000000
ELECTRA_FORK_EPOCH: 364032 # May 7, 2025, 10:05:11am UTC
# Fulu
FULU_FORK_VERSION: 0x06000000
FULU_FORK_EPOCH: 411392 # December 3, 2025, 09:49:11pm UTC
# Gloas
GLOAS_FORK_VERSION: 0x07000000
GLOAS_FORK_EPOCH: 18446744073709551615
# Heze
HEZE_FORK_VERSION: 0x08000000
HEZE_FORK_EPOCH: 18446744073709551615
# EIP7928
EIP7928_FORK_VERSION: 0xe7928000 # temporary stub
EIP7928_FORK_EPOCH: 18446744073709551615
# Time parameters
# ---------------------------------------------------------------
# 12000 milliseconds
SLOT_DURATION_MS: 12000
# 14 (estimate from Eth1 mainnet)
SECONDS_PER_ETH1_BLOCK: 14
# 2**8 (= 256) epochs
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
# 2**8 (= 256) epochs
SHARD_COMMITTEE_PERIOD: 256
# 2**11 (= 2,048) Eth1 blocks
ETH1_FOLLOW_DISTANCE: 2048
# 1667 basis points, ~17% of SLOT_DURATION_MS
PROPOSER_REORG_CUTOFF_BPS: 1667
# 3333 basis points, ~33% of SLOT_DURATION_MS
ATTESTATION_DUE_BPS: 3333
# 6667 basis points, ~67% of SLOT_DURATION_MS
AGGREGATE_DUE_BPS: 6667
# Altair
# 3333 basis points, ~33% of SLOT_DURATION_MS
SYNC_MESSAGE_DUE_BPS: 3333
# 6667 basis points, ~67% of SLOT_DURATION_MS
CONTRIBUTION_DUE_BPS: 6667
# Gloas
# 2**6 (= 64) epochs
MIN_BUILDER_WITHDRAWABILITY_DELAY: 64
# 2500 basis points, 25% of SLOT_DURATION_MS
ATTESTATION_DUE_BPS_GLOAS: 2500
# 5000 basis points, 50% of SLOT_DURATION_MS
AGGREGATE_DUE_BPS_GLOAS: 5000
# 2500 basis points, 25% of SLOT_DURATION_MS
SYNC_MESSAGE_DUE_BPS_GLOAS: 2500
# 5000 basis points, 50% of SLOT_DURATION_MS
CONTRIBUTION_DUE_BPS_GLOAS: 5000
# 7500 basis points, 75% of SLOT_DURATION_MS
PAYLOAD_ATTESTATION_DUE_BPS: 7500
# Heze
# 7500 basis points, 75% of SLOT_DURATION_MS
VIEW_FREEZE_CUTOFF_BPS: 7500
# 6667 basis points, ~67% of SLOT_DURATION_MS
INCLUSION_LIST_SUBMISSION_DUE_BPS: 6667
# 9167 basis points, ~92% of SLOT_DURATION_MS
PROPOSER_INCLUSION_LIST_CUTOFF_BPS: 9167
# Validator cycle
# ---------------------------------------------------------------
# 2**2 (= 4)
INACTIVITY_SCORE_BIAS: 4
# 2**4 (= 16)
INACTIVITY_SCORE_RECOVERY_RATE: 16
# 2**4 * 10**9 (= 16,000,000,000) Gwei
EJECTION_BALANCE: 16000000000
# 2**2 (= 4) validators
MIN_PER_EPOCH_CHURN_LIMIT: 4
# 2**16 (= 65,536)
CHURN_LIMIT_QUOTIENT: 65536
# Deneb
# 2**3 (= 8) (*deprecated*)
MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8
# Electra
# 2**7 * 10**9 (= 128,000,000,000) Gwei
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000
# 2**8 * 10**9 (= 256,000,000,000) Gwei
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000
# Fork choice
# ---------------------------------------------------------------
# 40%
PROPOSER_SCORE_BOOST: 40
# 20%
REORG_HEAD_WEIGHT_THRESHOLD: 20
# 160%
REORG_PARENT_WEIGHT_THRESHOLD: 160
# 2 epochs
REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2
# Deposit contract
# ---------------------------------------------------------------
# Ethereum PoW Mainnet
DEPOSIT_CHAIN_ID: 1
DEPOSIT_NETWORK_ID: 1
DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa
# Networking
# ---------------------------------------------------------------
# 10 * 2**20 (= 10,485,760) bytes, 10 MiB
MAX_PAYLOAD_SIZE: 10485760
# 2**10 (= 1,024) blocks
MAX_REQUEST_BLOCKS: 1024
# 2**8 (= 256) epochs
EPOCHS_PER_SUBNET_SUBSCRIPTION: 256
# 2**5 (= 32) slots
ATTESTATION_PROPAGATION_SLOT_RANGE: 32
# 500ms
MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500
MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000
MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000
# 2 subnets per node
SUBNETS_PER_NODE: 2
# 2**6 (= 64) subnets
ATTESTATION_SUBNET_COUNT: 64
# 0 bits
ATTESTATION_SUBNET_EXTRA_BITS: 0
# Deneb
# 2**7 (= 128) blocks
MAX_REQUEST_BLOCKS_DENEB: 128
# 2**12 (= 4,096) epochs
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096
# 6 subnets
BLOB_SIDECAR_SUBNET_COUNT: 6
# 6 blobs
MAX_BLOBS_PER_BLOCK: 6
# Electra
# 9 subnets
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9
# 9 blobs
MAX_BLOBS_PER_BLOCK_ELECTRA: 9
# Fulu
# 2**7 (= 128) groups
NUMBER_OF_CUSTODY_GROUPS: 128
# 2**7 (= 128) subnets
DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128
# 2**3 (= 8) samples
SAMPLES_PER_SLOT: 8
# 2**2 (= 4) sidecars
CUSTODY_REQUIREMENT: 4
# 2**3 (= 8) sidecars
VALIDATOR_CUSTODY_REQUIREMENT: 8
# 2**5 * 10**9 (= 32,000,000,000) Gwei
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000
# 2**12 (= 4,096) epochs
MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096
# Gloas
# 2**7 (= 128) payloads
MAX_REQUEST_PAYLOADS: 128
# Heze
# 2**4 (= 16) inclusion lists
MAX_REQUEST_INCLUSION_LIST: 16
# 2**13 (= 8,192) bytes
MAX_BYTES_PER_INCLUSION_LIST: 8192
# Blob Scheduling
# ---------------------------------------------------------------
BLOB_SCHEDULE:
- EPOCH: 412672 # December 9, 2025, 02:21:11pm UTC
MAX_BLOBS_PER_BLOCK: 15
- EPOCH: 419072 # January 7, 2026, 01:01:11am UTC
MAX_BLOBS_PER_BLOCK: 21

View File

@@ -0,0 +1,220 @@
# Minimal config
# Extends the minimal preset
PRESET_BASE: 'minimal'
# Free-form short name of the network that this configuration applies to - known
# canonical network names include:
# * 'minimal' - spec-testing
# Must match the regex: [a-z0-9\-]
CONFIG_NAME: 'minimal'
# Transition
# ---------------------------------------------------------------
# 2**256-2**10 for testing minimal network
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912
# By default, don't use these params
TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000
TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615
# Genesis
# ---------------------------------------------------------------
# [customized] 2**6 (= 64) validators
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64
# [customized] Jan 3, 2020, 12am UTC
MIN_GENESIS_TIME: 1578009600
# [customized] Initial fork version for minimal
GENESIS_FORK_VERSION: 0x00000001
# [customized] 5 * 60 (= 300) seconds
GENESIS_DELAY: 300
# Forking
# ---------------------------------------------------------------
# Values provided for illustrative purposes.
# Individual tests/testnets may set different values.
# [customized] Altair
ALTAIR_FORK_VERSION: 0x01000001
ALTAIR_FORK_EPOCH: 18446744073709551615
# [customized] Bellatrix
BELLATRIX_FORK_VERSION: 0x02000001
BELLATRIX_FORK_EPOCH: 18446744073709551615
# [customized] Capella
CAPELLA_FORK_VERSION: 0x03000001
CAPELLA_FORK_EPOCH: 18446744073709551615
# [customized] Deneb
DENEB_FORK_VERSION: 0x04000001
DENEB_FORK_EPOCH: 18446744073709551615
# [customized] Electra
ELECTRA_FORK_VERSION: 0x05000001
ELECTRA_FORK_EPOCH: 18446744073709551615
# [customized] Fulu
FULU_FORK_VERSION: 0x06000001
FULU_FORK_EPOCH: 18446744073709551615
# [customized] Gloas
GLOAS_FORK_VERSION: 0x07000001
GLOAS_FORK_EPOCH: 18446744073709551615
# [customized] Heze
HEZE_FORK_VERSION: 0x08000001
HEZE_FORK_EPOCH: 18446744073709551615
# [customized] EIP7928
EIP7928_FORK_VERSION: 0xe7928001
EIP7928_FORK_EPOCH: 18446744073709551615
# Time parameters
# ---------------------------------------------------------------
# [customized] 6000 milliseconds
SLOT_DURATION_MS: 6000
# 14 (estimate from Eth1 mainnet)
SECONDS_PER_ETH1_BLOCK: 14
# 2**8 (= 256) epochs
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
# [customized] 2**6 (= 64) epochs
SHARD_COMMITTEE_PERIOD: 64
# [customized] 2**4 (= 16) Eth1 blocks
ETH1_FOLLOW_DISTANCE: 16
# 1667 basis points, ~17% of SLOT_DURATION_MS
PROPOSER_REORG_CUTOFF_BPS: 1667
# 3333 basis points, ~33% of SLOT_DURATION_MS
ATTESTATION_DUE_BPS: 3333
# 6667 basis points, ~67% of SLOT_DURATION_MS
AGGREGATE_DUE_BPS: 6667
# Altair
# 3333 basis points, ~33% of SLOT_DURATION_MS
SYNC_MESSAGE_DUE_BPS: 3333
# 6667 basis points, ~67% of SLOT_DURATION_MS
CONTRIBUTION_DUE_BPS: 6667
# Gloas
# [customized] 2**1 (= 2) epochs
MIN_BUILDER_WITHDRAWABILITY_DELAY: 2
# 2500 basis points, 25% of SLOT_DURATION_MS
ATTESTATION_DUE_BPS_GLOAS: 2500
# 5000 basis points, 50% of SLOT_DURATION_MS
AGGREGATE_DUE_BPS_GLOAS: 5000
# 2500 basis points, 25% of SLOT_DURATION_MS
SYNC_MESSAGE_DUE_BPS_GLOAS: 2500
# 5000 basis points, 50% of SLOT_DURATION_MS
CONTRIBUTION_DUE_BPS_GLOAS: 5000
# 7500 basis points, 75% of SLOT_DURATION_MS
PAYLOAD_ATTESTATION_DUE_BPS: 7500
# Heze
# 7500 basis points, 75% of SLOT_DURATION_MS
VIEW_FREEZE_CUTOFF_BPS: 7500
# 6667 basis points, ~67% of SLOT_DURATION_MS
INCLUSION_LIST_SUBMISSION_DUE_BPS: 6667
# 9167 basis points, ~92% of SLOT_DURATION_MS
PROPOSER_INCLUSION_LIST_CUTOFF_BPS: 9167
# Validator cycle
# ---------------------------------------------------------------
# 2**2 (= 4)
INACTIVITY_SCORE_BIAS: 4
# 2**4 (= 16)
INACTIVITY_SCORE_RECOVERY_RATE: 16
# 2**4 * 10**9 (= 16,000,000,000) Gwei
EJECTION_BALANCE: 16000000000
# [customized] 2**1 (= 2) validators
MIN_PER_EPOCH_CHURN_LIMIT: 2
# [customized] 2**5 (= 32)
CHURN_LIMIT_QUOTIENT: 32
# Deneb
# [customized] 2**2 (= 4) (*deprecated*)
MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 4
# Electra
# [customized] 2**6 * 10**9 (= 64,000,000,000) Gwei
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000
# [customized] 2**7 * 10**9 (= 128,000,000,000) Gwei
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000
# Fork choice
# ---------------------------------------------------------------
# 40%
PROPOSER_SCORE_BOOST: 40
# 20%
REORG_HEAD_WEIGHT_THRESHOLD: 20
# 160%
REORG_PARENT_WEIGHT_THRESHOLD: 160
# 2 epochs
REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2
# Deposit contract
# ---------------------------------------------------------------
# Ethereum Goerli testnet
DEPOSIT_CHAIN_ID: 5
DEPOSIT_NETWORK_ID: 5
# Configured on a per testnet basis
DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890
# Networking
# ---------------------------------------------------------------
# 10 * 2**20 (= 10,485,760) bytes, 10 MiB
MAX_PAYLOAD_SIZE: 10485760
# 2**10 (= 1,024) blocks
MAX_REQUEST_BLOCKS: 1024
# 2**8 (= 256) epochs
EPOCHS_PER_SUBNET_SUBSCRIPTION: 256
# 2**5 (= 32) slots
ATTESTATION_PROPAGATION_SLOT_RANGE: 32
# 500ms
MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500
MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000
MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000
# 2 subnets per node
SUBNETS_PER_NODE: 2
# 2**6 (= 64) subnets
ATTESTATION_SUBNET_COUNT: 64
# 0 bits
ATTESTATION_SUBNET_EXTRA_BITS: 0
# Deneb
# 2**7 (= 128) blocks
MAX_REQUEST_BLOCKS_DENEB: 128
# 2**12 (= 4,096) epochs
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096
# 6 subnets
BLOB_SIDECAR_SUBNET_COUNT: 6
# 6 blobs
MAX_BLOBS_PER_BLOCK: 6
# Electra
# 9 subnets
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9
# 9 blobs
MAX_BLOBS_PER_BLOCK_ELECTRA: 9
# Fulu
# 2**7 (= 128) groups
NUMBER_OF_CUSTODY_GROUPS: 128
# 2**7 (= 128) subnets
DATA_COLUMN_SIDECAR_SUBNET_COUNT: 128
# 2**3 (= 8) samples
SAMPLES_PER_SLOT: 8
# 2**2 (= 4) sidecars
CUSTODY_REQUIREMENT: 4
# 2**3 (= 8) sidecars
VALIDATOR_CUSTODY_REQUIREMENT: 8
# 2**5 * 10**9 (= 32,000,000,000) Gwei
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000
# 2**12 (= 4,096) epochs
MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096
# Gloas
# 2**7 (= 128) payloads
MAX_REQUEST_PAYLOADS: 128
# Heze
# 2**4 (= 16) inclusion lists
MAX_REQUEST_INCLUSION_LIST: 16
# 2**13 (= 8,192) bytes
MAX_BYTES_PER_INCLUSION_LIST: 8192
# Blob Scheduling
# ---------------------------------------------------------------
BLOB_SCHEDULE: []

View File

@@ -96,8 +96,7 @@ pub struct ChainSpec {
* Time parameters
*/
pub genesis_delay: u64,
// TODO deprecate seconds_per_slot
pub seconds_per_slot: u64,
seconds_per_slot: u64,
// Private so that this value can't get changed except via the `set_slot_duration_ms` function.
slot_duration_ms: u64,
pub min_attestation_inclusion_delay: u64,
@@ -914,6 +913,7 @@ impl ChainSpec {
/// Set the duration of a slot (in ms).
pub fn set_slot_duration_ms<E: EthSpec>(mut self, slot_duration_ms: u64) -> Self {
self.slot_duration_ms = slot_duration_ms;
self.seconds_per_slot = slot_duration_ms.saturating_div(1000);
self.compute_derived_values::<E>()
}
@@ -1235,7 +1235,7 @@ impl ChainSpec {
gloas_fork_epoch: None,
builder_payment_threshold_numerator: 6,
builder_payment_threshold_denominator: 10,
min_builder_withdrawability_delay: Epoch::new(4096),
min_builder_withdrawability_delay: Epoch::new(64),
max_request_payloads: 128,
/*
@@ -1381,6 +1381,7 @@ impl ChainSpec {
// Gloas
gloas_fork_version: [0x07, 0x00, 0x00, 0x01],
gloas_fork_epoch: None,
min_builder_withdrawability_delay: Epoch::new(2),
/*
* Derived time values (set by `compute_derived_values()`)
@@ -1391,6 +1392,9 @@ impl ChainSpec {
sync_message_due: Duration::from_millis(1999),
contribution_and_proof_due: Duration::from_millis(4000),
// Networking Fulu
blob_schedule: BlobSchedule::default(),
// Other
network_id: 2, // lighthouse testnet network id
deposit_chain_id: 5,
@@ -1631,7 +1635,7 @@ impl ChainSpec {
gloas_fork_epoch: None,
builder_payment_threshold_numerator: 6,
builder_payment_threshold_denominator: 10,
min_builder_withdrawability_delay: Epoch::new(4096),
min_builder_withdrawability_delay: Epoch::new(64),
max_request_payloads: 128,
/*
@@ -1908,8 +1912,9 @@ pub struct Config {
#[serde(deserialize_with = "deserialize_fork_epoch")]
pub gloas_fork_epoch: Option<MaybeQuoted<Epoch>>,
#[serde(with = "serde_utils::quoted_u64")]
seconds_per_slot: u64,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
seconds_per_slot: Option<MaybeQuoted<u64>>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
slot_duration_ms: Option<MaybeQuoted<u64>>,
@@ -2064,6 +2069,10 @@ pub struct Config {
#[serde(default = "default_contribution_due_bps")]
#[serde(with = "serde_utils::quoted_u64")]
contribution_due_bps: u64,
#[serde(default = "default_min_builder_withdrawability_delay")]
#[serde(with = "serde_utils::quoted_u64")]
min_builder_withdrawability_delay: u64,
}
fn default_bellatrix_fork_version() -> [u8; 4] {
@@ -2289,6 +2298,10 @@ const fn default_contribution_due_bps() -> u64 {
6667
}
const fn default_min_builder_withdrawability_delay() -> u64 {
64
}
fn max_blocks_by_root_request_common(max_request_blocks: u64) -> usize {
let max_request_blocks = max_request_blocks as usize;
RuntimeVariableList::<Hash256>::new(
@@ -2459,7 +2472,9 @@ impl Config {
.gloas_fork_epoch
.map(|epoch| MaybeQuoted { value: epoch }),
seconds_per_slot: spec.seconds_per_slot,
seconds_per_slot: Some(MaybeQuoted {
value: spec.seconds_per_slot,
}),
slot_duration_ms: Some(MaybeQuoted {
value: spec.slot_duration_ms,
}),
@@ -2525,6 +2540,8 @@ impl Config {
aggregate_due_bps: spec.aggregate_due_bps,
sync_message_due_bps: spec.sync_message_due_bps,
contribution_due_bps: spec.contribution_due_bps,
min_builder_withdrawability_delay: spec.min_builder_withdrawability_delay.as_u64(),
}
}
@@ -2616,12 +2633,21 @@ impl Config {
aggregate_due_bps,
sync_message_due_bps,
contribution_due_bps,
min_builder_withdrawability_delay,
} = self;
if preset_base != E::spec_name().to_string().as_str() {
return None;
}
// Fail if seconds_per_slot and slot_duration_ms are both set but are inconsistent.
if let (Some(seconds_per_slot), Some(slot_duration_ms)) =
(seconds_per_slot, slot_duration_ms)
&& seconds_per_slot.value.saturating_mul(1000) != slot_duration_ms.value
{
return None;
}
let spec = ChainSpec {
config_name: config_name.clone(),
min_genesis_active_validator_count,
@@ -2642,10 +2668,12 @@ impl Config {
fulu_fork_version,
gloas_fork_version,
gloas_fork_epoch: gloas_fork_epoch.map(|q| q.value),
seconds_per_slot,
seconds_per_slot: seconds_per_slot
.map(|q| q.value)
.or_else(|| slot_duration_ms.and_then(|q| q.value.checked_div(1000)))?,
slot_duration_ms: slot_duration_ms
.map(|q| q.value)
.unwrap_or_else(|| seconds_per_slot.saturating_mul(1000)),
.or_else(|| seconds_per_slot.map(|q| q.value.saturating_mul(1000)))?,
seconds_per_eth1_block,
min_validator_withdrawability_delay,
shard_committee_period,
@@ -2705,6 +2733,8 @@ impl Config {
sync_message_due_bps,
contribution_due_bps,
min_builder_withdrawability_delay: Epoch::new(min_builder_withdrawability_delay),
..chain_spec.clone()
};
Some(spec.compute_derived_values::<E>())
@@ -2853,6 +2883,9 @@ mod yaml_tests {
use super::*;
use crate::core::MinimalEthSpec;
use paste::paste;
use std::collections::BTreeSet;
use std::env;
use std::path::PathBuf;
use std::sync::Arc;
use tempfile::NamedTempFile;
@@ -2902,6 +2935,67 @@ mod yaml_tests {
assert_eq!(from, yamlconfig);
}
#[test]
fn slot_duration_fallback_both_fields() {
let mainnet = ChainSpec::mainnet();
let mut config = Config::from_chain_spec::<MainnetEthSpec>(&mainnet);
config.seconds_per_slot = Some(MaybeQuoted { value: 12 });
config.slot_duration_ms = Some(MaybeQuoted { value: 12000 });
let spec = config
.apply_to_chain_spec::<MainnetEthSpec>(&mainnet)
.unwrap();
assert_eq!(spec.seconds_per_slot, 12);
assert_eq!(spec.slot_duration_ms, 12000);
}
#[test]
fn slot_duration_fallback_both_fields_inconsistent() {
let mainnet = ChainSpec::mainnet();
let mut config = Config::from_chain_spec::<MainnetEthSpec>(&mainnet);
config.seconds_per_slot = Some(MaybeQuoted { value: 10 });
config.slot_duration_ms = Some(MaybeQuoted { value: 12000 });
assert_eq!(config.apply_to_chain_spec::<MainnetEthSpec>(&mainnet), None);
}
#[test]
fn slot_duration_fallback_seconds_only() {
let mainnet = ChainSpec::mainnet();
let mut config = Config::from_chain_spec::<MainnetEthSpec>(&mainnet);
config.seconds_per_slot = Some(MaybeQuoted { value: 12 });
config.slot_duration_ms = None;
let spec = config
.apply_to_chain_spec::<MainnetEthSpec>(&mainnet)
.unwrap();
assert_eq!(spec.seconds_per_slot, 12);
assert_eq!(spec.slot_duration_ms, 12000);
}
#[test]
fn slot_duration_fallback_ms_only() {
let mainnet = ChainSpec::mainnet();
let mut config = Config::from_chain_spec::<MainnetEthSpec>(&mainnet);
config.seconds_per_slot = None;
config.slot_duration_ms = Some(MaybeQuoted { value: 12000 });
let spec = config
.apply_to_chain_spec::<MainnetEthSpec>(&mainnet)
.unwrap();
assert_eq!(spec.seconds_per_slot, 12);
assert_eq!(spec.slot_duration_ms, 12000);
}
#[test]
fn slot_duration_fallback_neither() {
let mainnet = ChainSpec::mainnet();
let mut config = Config::from_chain_spec::<MainnetEthSpec>(&mainnet);
config.seconds_per_slot = None;
config.slot_duration_ms = None;
assert!(
config
.apply_to_chain_spec::<MainnetEthSpec>(&mainnet)
.is_none()
);
}
#[test]
fn blob_schedule_max_blobs_per_block() {
let spec_contents = r#"
@@ -3375,7 +3469,6 @@ mod yaml_tests {
// Test slot duration
let slot_duration = spec.get_slot_duration();
assert_eq!(slot_duration, Duration::from_millis(12000));
assert_eq!(slot_duration, Duration::from_secs(spec.seconds_per_slot));
// Test edge cases with custom spec
let mut custom_spec = spec.clone();
@@ -3485,4 +3578,133 @@ mod yaml_tests {
spec.attestation_due_bps = 15000;
spec.compute_derived_values::<MainnetEthSpec>();
}
fn configs_base_path() -> PathBuf {
env::var("CARGO_MANIFEST_DIR")
.expect("should know manifest dir")
.parse::<PathBuf>()
.expect("should parse manifest dir as path")
.join("configs")
}
/// Upstream config keys that Lighthouse intentionally does not include in its
/// `Config` struct. These are forks/features not yet implemented. Update this
/// list as new forks are added.
const UPSTREAM_KEYS_NOT_IN_LIGHTHOUSE: &[&str] = &[
// Forks not yet implemented
"HEZE_FORK_VERSION",
"HEZE_FORK_EPOCH",
"EIP7928_FORK_VERSION",
"EIP7928_FORK_EPOCH",
// Gloas params not yet in Config
"ATTESTATION_DUE_BPS_GLOAS",
"AGGREGATE_DUE_BPS_GLOAS",
"SYNC_MESSAGE_DUE_BPS_GLOAS",
"CONTRIBUTION_DUE_BPS_GLOAS",
"PAYLOAD_ATTESTATION_DUE_BPS",
"MAX_REQUEST_PAYLOADS",
// Gloas fork choice params not yet in Config
"REORG_HEAD_WEIGHT_THRESHOLD",
"REORG_PARENT_WEIGHT_THRESHOLD",
"REORG_MAX_EPOCHS_SINCE_FINALIZATION",
// Heze networking
"VIEW_FREEZE_CUTOFF_BPS",
"INCLUSION_LIST_SUBMISSION_DUE_BPS",
"PROPOSER_INCLUSION_LIST_CUTOFF_BPS",
"MAX_REQUEST_INCLUSION_LIST",
"MAX_BYTES_PER_INCLUSION_LIST",
];
/// Compare a `ChainSpec` against an upstream consensus-specs config YAML file.
///
/// 1. Extracts keys from the raw YAML text (to avoid yaml_serde's inability
/// to parse integers > u64 into `Value`/`Mapping` types) and checks that
/// every key is either known to `Config` or explicitly listed in
/// `UPSTREAM_KEYS_NOT_IN_LIGHTHOUSE`.
/// 2. Deserializes the upstream YAML as `Config` (which has custom
/// deserializers for large values like `TERMINAL_TOTAL_DIFFICULTY`) and
/// compares against `Config::from_chain_spec`.
fn config_test<E: EthSpec>(spec: &ChainSpec, config_name: &str) {
let file_path = configs_base_path().join(format!("{config_name}.yaml"));
let upstream_yaml = std::fs::read_to_string(&file_path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", file_path.display()));
// Extract top-level keys from the raw YAML text. We can't parse as
// yaml_serde::Mapping because yaml_serde cannot represent integers
// exceeding u64 (e.g. TERMINAL_TOTAL_DIFFICULTY). Config YAML uses a
// simple `KEY: value` format with no indentation for top-level keys.
let upstream_keys: BTreeSet<String> = upstream_yaml
.lines()
.filter_map(|line| {
// Skip comments, blank lines, and indented lines (nested YAML).
if line.is_empty()
|| line.starts_with('#')
|| line.starts_with(' ')
|| line.starts_with('\t')
{
return None;
}
line.split(':').next().map(|k| k.to_string())
})
.collect();
// Get the set of keys that Config knows about by serializing and collecting
// keys. Also include keys for optional fields that may be skipped during
// serialization (e.g. CONFIG_NAME).
let our_config = Config::from_chain_spec::<E>(spec);
let our_yaml = yaml_serde::to_string(&our_config).expect("failed to serialize Config");
let our_mapping: yaml_serde::Mapping =
yaml_serde::from_str(&our_yaml).expect("failed to re-parse our Config");
let mut known_keys: BTreeSet<String> = our_mapping
.keys()
.filter_map(|k| k.as_str().map(String::from))
.collect();
// Fields that Config knows but may skip during serialization.
known_keys.insert("CONFIG_NAME".to_string());
// Check for upstream keys that our Config doesn't know about.
let mut missing_keys: Vec<&String> = upstream_keys
.iter()
.filter(|k| {
!known_keys.contains(k.as_str())
&& !UPSTREAM_KEYS_NOT_IN_LIGHTHOUSE.contains(&k.as_str())
})
.collect();
missing_keys.sort();
assert!(
missing_keys.is_empty(),
"Upstream {config_name} config has keys not present in Lighthouse Config \
(add to Config or to UPSTREAM_KEYS_NOT_IN_LIGHTHOUSE): {missing_keys:?}"
);
// Compare values for all fields Config knows about.
let mut upstream_config: Config = yaml_serde::from_str(&upstream_yaml)
.unwrap_or_else(|e| panic!("failed to parse {config_name} as Config: {e}"));
// CONFIG_NAME is network metadata (not a spec parameter), so align it
// before comparing.
upstream_config.config_name = our_config.config_name.clone();
// SECONDS_PER_SLOT is deprecated upstream but we still emit it, so
// fill it in if the upstream YAML omitted it.
if upstream_config.seconds_per_slot.is_none() {
upstream_config.seconds_per_slot = our_config.seconds_per_slot;
}
assert_eq!(
upstream_config, our_config,
"Config mismatch for {config_name}"
);
}
#[test]
fn mainnet_config_consistent() {
let spec = ChainSpec::mainnet();
config_test::<MainnetEthSpec>(&spec, "mainnet");
}
#[test]
fn minimal_config_consistent() {
let spec = ChainSpec::minimal();
config_test::<MinimalEthSpec>(&spec, "minimal");
}
}