This commit is contained in:
Eitan Seri-Levi
2026-04-29 22:38:13 +02:00
70 changed files with 2749 additions and 330 deletions

View File

@@ -105,12 +105,8 @@ CONTRIBUTION_DUE_BPS_GLOAS: 5000
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
INCLUSION_LIST_DUE_BPS: 6667
# Validator cycle
# ---------------------------------------------------------------
@@ -135,6 +131,14 @@ MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000
# 2**8 * 10**9 (= 256,000,000,000) Gwei
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000
# Gloas
# 2**15 (= 32,768)
CHURN_LIMIT_QUOTIENT_GLOAS: 32768
# 2**16 (= 65,536)
CONSOLIDATION_CHURN_LIMIT_QUOTIENT: 65536
# 2**8 * 10**9 (= 256,000,000,000) Gwei
MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GLOAS: 256000000000
# Fork choice
# ---------------------------------------------------------------
# 40%

View File

@@ -101,12 +101,8 @@ CONTRIBUTION_DUE_BPS_GLOAS: 5000
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
INCLUSION_LIST_DUE_BPS: 6667
# Validator cycle
# ---------------------------------------------------------------
@@ -131,6 +127,14 @@ 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
# Gloas
# [customized] 2**4 (= 16)
CHURN_LIMIT_QUOTIENT_GLOAS: 16
# [customized] 2**5 (= 32)
CONSOLIDATION_CHURN_LIMIT_QUOTIENT: 32
# [customized] 2**7 * 10**9 (= 128,000,000,000) Gwei
MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GLOAS: 128000000000
# Fork choice
# ---------------------------------------------------------------
# 40%

View File

@@ -2,8 +2,8 @@
# Misc
# ---------------------------------------------------------------
# [customized] 2**1 (= 2) validators
PTC_SIZE: 2
# [customized] 2**4 (= 16) validators
PTC_SIZE: 16
# Max operations per block
# ---------------------------------------------------------------

View File

@@ -400,15 +400,13 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> SignedBeaconBlock<E, Payload>
/// `block_hash` from the parent beacon block's bid. If the parent beacon state is available
/// this can alternatively be fetched from `state.latest_payload_bid`.
///
/// This function returns `false` for all blocks prior to Gloas and for the zero
/// `parent_block_hash`.
/// This function returns `false` for all blocks prior to Gloas.
pub fn is_parent_block_full(&self, parent_block_hash: ExecutionBlockHash) -> bool {
let Ok(signed_payload_bid) = self.message().body().signed_execution_payload_bid() else {
// Prior to Gloas.
return false;
};
parent_block_hash != ExecutionBlockHash::zero()
&& signed_payload_bid.message.parent_block_hash == parent_block_hash
signed_payload_bid.message.parent_block_hash == parent_block_hash
}
}

View File

@@ -1,5 +1,5 @@
use crate::test_utils::TestRandom;
use crate::{Address, ForkName, SignedRoot, Slot};
use crate::{Address, ForkName, Hash256, SignedRoot, Slot};
use bls::Signature;
use context_deserialize::context_deserialize;
use educe::Educe;
@@ -16,6 +16,7 @@ use tree_hash_derive::TreeHash;
#[context_deserialize(ForkName)]
// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#new-proposerpreferences
pub struct ProposerPreferences {
pub checkpoint_root: Hash256,
pub proposal_slot: Slot,
pub validator_index: u64,
pub fee_recipient: Address,

View File

@@ -251,6 +251,9 @@ pub struct ChainSpec {
pub builder_payment_threshold_numerator: u64,
pub builder_payment_threshold_denominator: u64,
pub min_builder_withdrawability_delay: Epoch,
pub churn_limit_quotient_gloas: u64,
pub consolidation_churn_limit_quotient: u64,
pub max_per_epoch_activation_churn_limit_gloas: u64,
/*
* Heze hard fork params
@@ -1284,6 +1287,14 @@ impl ChainSpec {
builder_payment_threshold_numerator: 6,
builder_payment_threshold_denominator: 10,
min_builder_withdrawability_delay: Epoch::new(64),
churn_limit_quotient_gloas: option_wrapper(|| u64::checked_pow(2, 15))
.expect("calculation does not overflow"),
consolidation_churn_limit_quotient: option_wrapper(|| u64::checked_pow(2, 16))
.expect("calculation does not overflow"),
max_per_epoch_activation_churn_limit_gloas: option_wrapper(|| {
u64::checked_pow(2, 8)?.checked_mul(u64::checked_pow(10, 9)?)
})
.expect("calculation does not overflow"),
max_request_payloads: 128,
/*
@@ -1436,6 +1447,13 @@ impl ChainSpec {
gloas_fork_version: [0x07, 0x00, 0x00, 0x01],
gloas_fork_epoch: None,
min_builder_withdrawability_delay: Epoch::new(2),
churn_limit_quotient_gloas: option_wrapper(|| u64::checked_pow(2, 4))
.expect("calculation does not overflow"),
consolidation_churn_limit_quotient: option_wrapper(|| u64::checked_pow(2, 5))
.expect("calculation does not overflow"),
max_per_epoch_activation_churn_limit_gloas: option_wrapper(|| {
u64::checked_pow(2, 7)?.checked_mul(u64::checked_pow(10, 9)?)
}).expect("calculation does not overflow"),
// Heze
heze_fork_version: [0x08, 0x00, 0x00, 0x01],
heze_fork_epoch: None,
@@ -1700,6 +1718,14 @@ impl ChainSpec {
builder_payment_threshold_numerator: 6,
builder_payment_threshold_denominator: 10,
min_builder_withdrawability_delay: Epoch::new(64),
churn_limit_quotient_gloas: option_wrapper(|| u64::checked_pow(2, 15))
.expect("calculation does not overflow"),
consolidation_churn_limit_quotient: option_wrapper(|| u64::checked_pow(2, 16))
.expect("calculation does not overflow"),
max_per_epoch_activation_churn_limit_gloas: option_wrapper(|| {
u64::checked_pow(2, 8)?.checked_mul(u64::checked_pow(10, 9)?)
})
.expect("calculation does not overflow"),
max_request_payloads: 128,
/*
@@ -2164,6 +2190,16 @@ pub struct Config {
#[serde(default = "default_min_builder_withdrawability_delay")]
#[serde(with = "serde_utils::quoted_u64")]
min_builder_withdrawability_delay: u64,
#[serde(default = "default_churn_limit_quotient_gloas")]
#[serde(with = "serde_utils::quoted_u64")]
churn_limit_quotient_gloas: u64,
#[serde(default = "default_consolidation_churn_limit_quotient")]
#[serde(with = "serde_utils::quoted_u64")]
consolidation_churn_limit_quotient: u64,
#[serde(default = "default_max_per_epoch_activation_churn_limit_gloas")]
#[serde(with = "serde_utils::quoted_u64")]
max_per_epoch_activation_churn_limit_gloas: u64,
}
fn default_bellatrix_fork_version() -> [u8; 4] {
@@ -2406,6 +2442,18 @@ const fn default_min_builder_withdrawability_delay() -> u64 {
64
}
const fn default_churn_limit_quotient_gloas() -> u64 {
32_768
}
const fn default_consolidation_churn_limit_quotient() -> u64 {
65_536
}
const fn default_max_per_epoch_activation_churn_limit_gloas() -> u64 {
256_000_000_000
}
fn max_blocks_by_root_request_common(max_request_blocks: u64) -> usize {
let max_request_blocks = max_request_blocks as usize;
RuntimeVariableList::<Hash256>::new(
@@ -2662,6 +2710,11 @@ impl Config {
contribution_due_bps: spec.contribution_due_bps,
min_builder_withdrawability_delay: spec.min_builder_withdrawability_delay.as_u64(),
churn_limit_quotient_gloas: spec.churn_limit_quotient_gloas,
consolidation_churn_limit_quotient: spec.consolidation_churn_limit_quotient,
max_per_epoch_activation_churn_limit_gloas: spec
.max_per_epoch_activation_churn_limit_gloas,
}
}
@@ -2761,6 +2814,9 @@ impl Config {
sync_message_due_bps,
contribution_due_bps,
min_builder_withdrawability_delay,
churn_limit_quotient_gloas,
consolidation_churn_limit_quotient,
max_per_epoch_activation_churn_limit_gloas,
} = self;
if preset_base != E::spec_name().to_string().as_str() {
@@ -2870,6 +2926,10 @@ impl Config {
min_builder_withdrawability_delay: Epoch::new(min_builder_withdrawability_delay),
churn_limit_quotient_gloas,
consolidation_churn_limit_quotient,
max_per_epoch_activation_churn_limit_gloas,
..chain_spec.clone()
};
Some(spec.compute_derived_values::<E>())
@@ -3770,9 +3830,7 @@ mod yaml_tests {
"CONTRIBUTION_DUE_BPS_GLOAS",
"MAX_REQUEST_PAYLOADS",
// Heze networking
"VIEW_FREEZE_CUTOFF_BPS",
"INCLUSION_LIST_SUBMISSION_DUE_BPS",
"PROPOSER_INCLUSION_LIST_CUTOFF_BPS",
"INCLUSION_LIST_DUE_BPS",
"MAX_REQUEST_INCLUSION_LIST",
"MAX_BYTES_PER_INCLUSION_LIST",
];

View File

@@ -572,7 +572,7 @@ impl EthSpec for MinimalEthSpec {
type NumberOfColumns = U128;
type ProposerLookaheadSlots = U16; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
type BuilderPendingPaymentsLimit = U16; // 2 * SLOTS_PER_EPOCH = 2 * 8 = 16
type PTCSize = U2;
type PTCSize = U16;
type PtcWindowLength = U24; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH
type MaxBuildersPerWithdrawalsSweep = U16;

View File

@@ -5,7 +5,10 @@ use rand::RngCore;
use serde::{Deserialize, Serialize};
use ssz::{Decode, DecodeError, Encode};
use crate::{core::Hash256, test_utils::TestRandom};
use crate::{
core::{Hash256, Hash256Ext},
test_utils::TestRandom,
};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Hash)]
@@ -20,13 +23,7 @@ impl fmt::Debug for ExecutionBlockHash {
impl fmt::Display for ExecutionBlockHash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let hash = format!("{}", self.0);
write!(
f,
"{}…{}",
&hash[..6],
&hash[hash.len().saturating_sub(4)..]
)
self.0.short().fmt(f)
}
}

View File

@@ -49,3 +49,29 @@ pub type Hash64 = alloy_primitives::B64;
pub type Address = alloy_primitives::Address;
pub type VersionedHash = Hash256;
pub type MerkleProof = Vec<Hash256>;
/// Extension trait for `Hash256` to allow us to implement additional methods on it.
pub trait Hash256Ext {
fn short(&self) -> ShortenedHash<'_>;
}
impl Hash256Ext for Hash256 {
fn short(&self) -> ShortenedHash<'_> {
ShortenedHash(self)
}
}
pub struct ShortenedHash<'a>(&'a Hash256);
impl<'a> std::fmt::Display for ShortenedHash<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let hash: &[u8; 32] = self.0.as_ref();
write!(
f,
// Format as hex, padded to 2 digits per byte.
// This outputs a consistent "0x1234...abcd" format.
"0x{:02x}{:02x}…{:02x}{:02x}",
hash[0], hash[1], hash[30], hash[31]
)
}
}

View File

@@ -20,6 +20,7 @@ pub struct ExecutionPayloadEnvelope<E: EthSpec> {
#[serde(with = "serde_utils::quoted_u64")]
pub builder_index: u64,
pub beacon_block_root: Hash256,
pub parent_beacon_block_root: Hash256,
}
impl<E: EthSpec> ExecutionPayloadEnvelope<E> {
@@ -30,6 +31,7 @@ impl<E: EthSpec> ExecutionPayloadEnvelope<E> {
execution_requests: ExecutionRequests::default(),
builder_index: 0,
beacon_block_root: Hash256::zero(),
parent_beacon_block_root: Hash256::zero(),
}
}

View File

@@ -2797,29 +2797,55 @@ impl<E: EthSpec> BeaconState<E> {
/// Return the churn limit for the current epoch.
pub fn get_balance_churn_limit(&self, spec: &ChainSpec) -> Result<u64, BeaconStateError> {
let total_active_balance = self.get_total_active_balance()?;
let quotient = if self.fork_name_unchecked().gloas_enabled() {
spec.churn_limit_quotient_gloas
} else {
spec.churn_limit_quotient
};
let churn = std::cmp::max(
spec.min_per_epoch_churn_limit_electra,
total_active_balance.safe_div(spec.churn_limit_quotient)?,
total_active_balance.safe_div(quotient)?,
);
Ok(churn.safe_sub(churn.safe_rem(spec.effective_balance_increment)?)?)
}
/// Return the churn limit for the current epoch dedicated to activations and exits.
///
/// From Gloas onwards this is the activation-only churn limit (EIP-8061); exits use
/// [`Self::get_exit_churn_limit`].
pub fn get_activation_exit_churn_limit(
&self,
spec: &ChainSpec,
) -> Result<u64, BeaconStateError> {
let max_limit = if self.fork_name_unchecked().gloas_enabled() {
spec.max_per_epoch_activation_churn_limit_gloas
} else {
spec.max_per_epoch_activation_exit_churn_limit
};
Ok(std::cmp::min(
spec.max_per_epoch_activation_exit_churn_limit,
max_limit,
self.get_balance_churn_limit(spec)?,
))
}
/// Return the Gloas (EIP-8061) exit churn limit for the current epoch.
///
/// Unlike [`Self::get_activation_exit_churn_limit`], this is uncapped.
pub fn get_exit_churn_limit(&self, spec: &ChainSpec) -> Result<u64, BeaconStateError> {
self.get_balance_churn_limit(spec)
}
pub fn get_consolidation_churn_limit(&self, spec: &ChainSpec) -> Result<u64, BeaconStateError> {
self.get_balance_churn_limit(spec)?
.safe_sub(self.get_activation_exit_churn_limit(spec)?)
.map_err(Into::into)
if self.fork_name_unchecked().gloas_enabled() {
let total_active_balance = self.get_total_active_balance()?;
let churn = total_active_balance.safe_div(spec.consolidation_churn_limit_quotient)?;
Ok(churn.safe_sub(churn.safe_rem(spec.effective_balance_increment)?)?)
} else {
self.get_balance_churn_limit(spec)?
.safe_sub(self.get_activation_exit_churn_limit(spec)?)
.map_err(Into::into)
}
}
pub fn get_pending_balance_to_withdraw(
@@ -2914,7 +2940,11 @@ impl<E: EthSpec> BeaconState<E> {
self.compute_activation_exit_epoch(self.current_epoch(), spec)?,
);
let per_epoch_churn = self.get_activation_exit_churn_limit(spec)?;
let per_epoch_churn = if self.fork_name_unchecked().gloas_enabled() {
self.get_exit_churn_limit(spec)?
} else {
self.get_activation_exit_churn_limit(spec)?
};
// New epoch for exits
let mut exit_balance_to_consume = if self.earliest_exit_epoch()? < earliest_exit_epoch {
per_epoch_churn
@@ -3152,7 +3182,19 @@ impl<E: EthSpec> BeaconState<E> {
let total_active_balance = self.get_total_active_balance()?;
let fork_name = self.fork_name_unchecked();
if fork_name.electra_enabled() {
if fork_name.gloas_enabled() {
// [Modified in Gloas:EIP8061]
let exit_churn = self.get_exit_churn_limit(spec)?;
let activation_churn = self.get_activation_exit_churn_limit(spec)?;
let consolidation_churn = self.get_consolidation_churn_limit(spec)?;
compute_weak_subjectivity_period_gloas(
total_active_balance,
exit_churn,
activation_churn,
consolidation_churn,
spec,
)
} else if fork_name.electra_enabled() {
let balance_churn_limit = self.get_balance_churn_limit(spec)?;
compute_weak_subjectivity_period_electra(
total_active_balance,
@@ -3660,6 +3702,30 @@ pub fn compute_weak_subjectivity_period_electra(
Ok(ws_period)
}
/// Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/weak-subjectivity.md
pub fn compute_weak_subjectivity_period_gloas(
total_active_balance: u64,
exit_churn_limit: u64,
activation_churn_limit: u64,
consolidation_churn_limit: u64,
spec: &ChainSpec,
) -> Result<Epoch, BeaconStateError> {
// delta = 2 * exit_churn // 3 + activation_churn // 3 + consolidation_churn
let delta = exit_churn_limit
.safe_mul(2)?
.safe_div(3)?
.safe_add(activation_churn_limit.safe_div(3)?)?
.safe_add(consolidation_churn_limit)?;
let epochs_for_validator_set_churn = SAFETY_DECAY
.safe_mul(total_active_balance)?
.safe_div(delta.safe_mul(200)?)?;
let ws_period = spec
.min_validator_withdrawability_delay
.safe_add(epochs_for_validator_set_churn)?;
Ok(ws_period)
}
#[cfg(test)]
mod weak_subjectivity_tests {
use crate::state::beacon_state::compute_weak_subjectivity_period_electra;