Mallory - Single commit

This commit is contained in:
Age Manning
2025-03-25 16:53:10 +11:00
parent b8178515cd
commit e2acce9468
118 changed files with 4753 additions and 3938 deletions

View File

@@ -843,6 +843,7 @@ impl<'de, E: EthSpec, Payload: AbstractExecPayload<E>> ContextDeserialize<'de, F
}
}
#[derive(Clone, Copy)]
pub enum BlockImportSource {
Gossip,
Lookup,

View File

@@ -25,6 +25,7 @@ pub struct ForkVersionedResponse<T, M = EmptyMetadata> {
/// `Deserialize`.
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct UnversionedResponse<T, M = EmptyMetadata> {
#[serde(flatten)]
pub metadata: M,
pub data: T,
}
@@ -195,9 +196,10 @@ impl<T, M> From<UnversionedResponse<T, M>> for BeaconResponse<T, M> {
#[cfg(test)]
mod fork_version_response_tests {
use crate::beacon_response::ExecutionOptimisticFinalizedMetadata;
use crate::{
ExecutionPayload, ExecutionPayloadBellatrix, ForkName, ForkVersionedResponse,
MainnetEthSpec,
MainnetEthSpec, UnversionedResponse,
};
use serde_json::json;
@@ -236,4 +238,24 @@ mod fork_version_response_tests {
assert!(result.is_err());
}
// The following test should only pass by having the attribute #[serde(flatten)] on the metadata
#[test]
fn unversioned_response_serialize_dezerialize_round_trip_test() {
// Create an UnversionedResponse with some data
let data = UnversionedResponse {
metadata: ExecutionOptimisticFinalizedMetadata {
execution_optimistic: Some(false),
finalized: Some(false),
},
data: "some_test_data".to_string(),
};
let serialized = serde_json::to_string(&data);
let deserialized =
serde_json::from_str(&serialized.unwrap()).expect("Failed to deserialize");
assert_eq!(data, deserialized);
}
}

View File

@@ -173,7 +173,21 @@ pub enum Error {
AggregatorNotInCommittee {
aggregator_index: u64,
},
PleaseNotifyTheDevs(String),
ComputeProposerIndicesPastEpoch {
current_epoch: Epoch,
request_epoch: Epoch,
},
ComputeProposerIndicesInsufficientLookahead {
current_epoch: Epoch,
request_epoch: Epoch,
},
ComputeProposerIndicesExcessiveLookahead {
current_epoch: Epoch,
request_epoch: Epoch,
},
ProposerLookaheadOutOfBounds {
i: usize,
},
}
/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
@@ -578,6 +592,7 @@ where
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Fulu, Gloas))]
#[serde(with = "ssz_types::serde_utils::quoted_u64_fixed_vec")]
pub proposer_lookahead: Vector<u64, E::ProposerLookaheadSlots>,
// Gloas
@@ -886,8 +901,9 @@ impl<E: EthSpec> BeaconState<E> {
&self,
epoch: Epoch,
block_root: Hash256,
spec: &ChainSpec,
) -> Result<Hash256, Error> {
let decision_slot = self.proposer_shuffling_decision_slot(epoch);
let decision_slot = spec.proposer_shuffling_decision_slot::<E>(epoch);
if self.slot() <= decision_slot {
Ok(block_root)
} else {
@@ -902,19 +918,18 @@ impl<E: EthSpec> BeaconState<E> {
///
/// The `block_root` covers the one-off scenario where the genesis block decides its own
/// shuffling. It should be set to the latest block applied to `self` or the genesis block root.
pub fn proposer_shuffling_decision_root(&self, block_root: Hash256) -> Result<Hash256, Error> {
let decision_slot = self.proposer_shuffling_decision_slot(self.current_epoch());
if self.slot() == decision_slot {
Ok(block_root)
} else {
self.get_block_root(decision_slot).copied()
}
pub fn proposer_shuffling_decision_root(
&self,
block_root: Hash256,
spec: &ChainSpec,
) -> Result<Hash256, Error> {
self.proposer_shuffling_decision_root_at_epoch(self.current_epoch(), block_root, spec)
}
/// Returns the slot at which the proposer shuffling was decided. The block root at this slot
/// can be used to key the proposer shuffling for the given epoch.
fn proposer_shuffling_decision_slot(&self, epoch: Epoch) -> Slot {
epoch.start_slot(E::slots_per_epoch()).saturating_sub(1_u64)
pub fn epoch_cache_decision_root(&self, block_root: Hash256) -> Result<Hash256, Error> {
// Epoch cache decision root for the current epoch (N) is the block root at the end of epoch
// N - 1. This is the same as the root that determines the next epoch attester shuffling.
self.attester_shuffling_decision_root(block_root, RelativeEpoch::Next)
}
/// Returns the block root which decided the attester shuffling for the given `relative_epoch`.
@@ -998,6 +1013,45 @@ impl<E: EthSpec> BeaconState<E> {
indices: &[usize],
spec: &ChainSpec,
) -> Result<Vec<usize>, Error> {
// Regardless of fork, we never support computing proposer indices for past epochs.
let current_epoch = self.current_epoch();
if epoch < current_epoch {
return Err(Error::ComputeProposerIndicesPastEpoch {
current_epoch,
request_epoch: epoch,
});
}
if spec.fork_name_at_epoch(epoch).fulu_enabled() {
// Post-Fulu we must never compute proposer indices using insufficient lookahead. This
// would be very dangerous as it would lead to conflicts between the *true* proposer as
// defined by `self.proposer_lookahead` and the output of this function.
// With MIN_SEED_LOOKAHEAD=1 (common config), this is equivalent to checking that the
// requested epoch is not the current epoch.
//
// We do not run this check if this function is called from `upgrade_to_fulu`,
// which runs *after* the slot is incremented, and needs to compute the proposer
// shuffling for the epoch that was just transitioned into.
if self.fork_name_unchecked().fulu_enabled()
&& epoch < current_epoch.safe_add(spec.min_seed_lookahead)?
{
return Err(Error::ComputeProposerIndicesInsufficientLookahead {
current_epoch,
request_epoch: epoch,
});
}
} else {
// Pre-Fulu the situation is reversed, we *should not* compute proposer indices using
// too much lookahead. To do so would make us vulnerable to changes in the proposer
// indices caused by effective balance changes.
if epoch >= current_epoch.safe_add(spec.min_seed_lookahead)? {
return Err(Error::ComputeProposerIndicesExcessiveLookahead {
current_epoch,
request_epoch: epoch,
});
}
}
epoch
.slot_iter(E::slots_per_epoch())
.map(|slot| {
@@ -1146,10 +1200,7 @@ impl<E: EthSpec> BeaconState<E> {
let index = slot.as_usize().safe_rem(E::slots_per_epoch() as usize)?;
proposer_lookahead
.get(index)
.ok_or(Error::PleaseNotifyTheDevs(format!(
"Proposer lookahead out of bounds: {} for slot: {}",
index, slot
)))
.ok_or(Error::ProposerLookaheadOutOfBounds { i: index })
.map(|index| *index as usize)
} else {
// Pre-Fulu
@@ -1168,6 +1219,25 @@ impl<E: EthSpec> BeaconState<E> {
epoch: Epoch,
spec: &ChainSpec,
) -> Result<Vec<usize>, Error> {
// This isn't in the spec, but we remove the footgun that is requesting the current epoch
// for a Fulu state.
if let Ok(proposer_lookahead) = self.proposer_lookahead()
&& epoch >= self.current_epoch()
&& epoch <= self.next_epoch()?
{
let slots_per_epoch = E::slots_per_epoch() as usize;
let start_offset = if epoch == self.current_epoch() {
0
} else {
slots_per_epoch
};
return Ok(proposer_lookahead
.iter_from(start_offset)?
.take(slots_per_epoch)
.map(|x| *x as usize)
.collect());
}
// Not using the cached validator indices since they are shuffled.
let indices = self.get_active_validator_indices(epoch, spec)?;

View File

@@ -227,7 +227,7 @@ pub struct ChainSpec {
pub ttfb_timeout: u64,
pub resp_timeout: u64,
pub attestation_propagation_slot_range: u64,
pub maximum_gossip_clock_disparity_millis: u64,
pub maximum_gossip_clock_disparity: u64,
pub message_domain_invalid_snappy: [u8; 4],
pub message_domain_valid_snappy: [u8; 4],
pub subnets_per_node: u8,
@@ -670,7 +670,7 @@ impl ChainSpec {
}
pub fn maximum_gossip_clock_disparity(&self) -> Duration {
Duration::from_millis(self.maximum_gossip_clock_disparity_millis)
Duration::from_millis(self.maximum_gossip_clock_disparity)
}
pub fn ttfb_timeout(&self) -> Duration {
@@ -865,6 +865,34 @@ impl ChainSpec {
)
}
/// Returns the slot at which the proposer shuffling was decided.
///
/// The block root at this slot can be used to key the proposer shuffling for the given epoch.
pub fn proposer_shuffling_decision_slot<E: EthSpec>(&self, epoch: Epoch) -> Slot {
// At the Fulu fork epoch itself, the shuffling is computed "the old way" with no lookahead.
// Therefore for `epoch == fulu_fork_epoch` we must take the `else` branch. Checking if Fulu
// is enabled at `epoch - 1` accomplishes this neatly.
if self
.fork_name_at_epoch(epoch.saturating_sub(1_u64))
.fulu_enabled()
{
// Post-Fulu the proposer shuffling decision slot for epoch N is the slot at the end
// of epoch N - 2 (note: min_seed_lookahead=1 in all current configs).
epoch
.saturating_sub(self.min_seed_lookahead)
.start_slot(E::slots_per_epoch())
.saturating_sub(1_u64)
} else {
// Pre-Fulu the proposer shuffling decision slot for epoch N is the slot at the end of
// epoch N - 1 (note: +1 -1 for min_seed_lookahead=1 in all current configs).
epoch
.saturating_add(Epoch::new(1))
.saturating_sub(self.min_seed_lookahead)
.start_slot(E::slots_per_epoch())
.saturating_sub(1_u64)
}
}
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
pub fn mainnet() -> Self {
Self {
@@ -1084,7 +1112,7 @@ impl ChainSpec {
attestation_propagation_slot_range: default_attestation_propagation_slot_range(),
attestation_subnet_count: 64,
subnets_per_node: 2,
maximum_gossip_clock_disparity_millis: default_maximum_gossip_clock_disparity_millis(),
maximum_gossip_clock_disparity: default_maximum_gossip_clock_disparity(),
target_aggregators_per_committee: 16,
max_payload_size: default_max_payload_size(),
min_epochs_for_block_requests: default_min_epochs_for_block_requests(),
@@ -1430,7 +1458,7 @@ impl ChainSpec {
attestation_propagation_slot_range: default_attestation_propagation_slot_range(),
attestation_subnet_count: 64,
subnets_per_node: 4, // Make this larger than usual to avoid network damage
maximum_gossip_clock_disparity_millis: default_maximum_gossip_clock_disparity_millis(),
maximum_gossip_clock_disparity: default_maximum_gossip_clock_disparity(),
target_aggregators_per_committee: 16,
max_payload_size: default_max_payload_size(),
min_epochs_for_block_requests: 33024,
@@ -1751,9 +1779,9 @@ pub struct Config {
#[serde(default = "default_attestation_propagation_slot_range")]
#[serde(with = "serde_utils::quoted_u64")]
attestation_propagation_slot_range: u64,
#[serde(default = "default_maximum_gossip_clock_disparity_millis")]
#[serde(default = "default_maximum_gossip_clock_disparity")]
#[serde(with = "serde_utils::quoted_u64")]
maximum_gossip_clock_disparity_millis: u64,
maximum_gossip_clock_disparity: u64,
#[serde(default = "default_message_domain_invalid_snappy")]
#[serde(with = "serde_utils::bytes_4_hex")]
message_domain_invalid_snappy: [u8; 4],
@@ -1967,7 +1995,7 @@ const fn default_attestation_propagation_slot_range() -> u64 {
32
}
const fn default_maximum_gossip_clock_disparity_millis() -> u64 {
const fn default_maximum_gossip_clock_disparity() -> u64 {
500
}
@@ -2186,7 +2214,7 @@ impl Config {
ttfb_timeout: spec.ttfb_timeout,
resp_timeout: spec.resp_timeout,
attestation_propagation_slot_range: spec.attestation_propagation_slot_range,
maximum_gossip_clock_disparity_millis: spec.maximum_gossip_clock_disparity_millis,
maximum_gossip_clock_disparity: spec.maximum_gossip_clock_disparity,
message_domain_invalid_snappy: spec.message_domain_invalid_snappy,
message_domain_valid_snappy: spec.message_domain_valid_snappy,
max_request_blocks_deneb: spec.max_request_blocks_deneb,
@@ -2274,7 +2302,7 @@ impl Config {
message_domain_valid_snappy,
max_request_blocks,
attestation_propagation_slot_range,
maximum_gossip_clock_disparity_millis,
maximum_gossip_clock_disparity,
max_request_blocks_deneb,
max_request_blob_sidecars,
max_request_data_column_sidecars,
@@ -2350,7 +2378,7 @@ impl Config {
attestation_subnet_prefix_bits,
max_request_blocks,
attestation_propagation_slot_range,
maximum_gossip_clock_disparity_millis,
maximum_gossip_clock_disparity,
max_request_blocks_deneb,
max_request_blob_sidecars,
max_request_data_column_sidecars,
@@ -2977,4 +3005,32 @@ mod yaml_tests {
spec.min_epoch_data_availability_boundary(current_epoch)
);
}
#[test]
fn proposer_shuffling_decision_root_around_epoch_boundary() {
type E = MainnetEthSpec;
let fulu_fork_epoch = 5;
let spec = {
let mut spec = ForkName::Electra.make_genesis_spec(E::default_spec());
spec.fulu_fork_epoch = Some(Epoch::new(fulu_fork_epoch));
Arc::new(spec)
};
// For epochs prior to AND including the Fulu fork epoch, the decision slot is the end
// of the previous epoch (i.e. only 1 slot lookahead).
for epoch in (0..=fulu_fork_epoch).map(Epoch::new) {
assert_eq!(
spec.proposer_shuffling_decision_slot::<E>(epoch),
epoch.start_slot(E::slots_per_epoch()) - 1
);
}
// For epochs after Fulu, the decision slot is the end of the epoch two epochs prior.
for epoch in ((fulu_fork_epoch + 1)..(fulu_fork_epoch + 10)).map(Epoch::new) {
assert_eq!(
spec.proposer_shuffling_decision_slot::<E>(epoch),
(epoch - 1).start_slot(E::slots_per_epoch()) - 1
);
}
}
}

View File

@@ -143,6 +143,7 @@ pub enum DataColumnSidecarError {
PreDeneb,
SszError(SszError),
BuildSidecarFailed(String),
InvalidCellProofLength { expected: usize, actual: usize },
}
impl From<ArithError> for DataColumnSidecarError {

View File

@@ -5,9 +5,13 @@ use std::sync::Arc;
/// Cache of values which are uniquely determined at the start of an epoch.
///
/// The values are fixed with respect to the last block of the _prior_ epoch, which we refer
/// to as the "decision block". This cache is very similar to the `BeaconProposerCache` in that
/// beacon proposers are determined at exactly the same time as the values in this cache, so
/// the keys for the two caches are identical.
/// to as the "decision block".
///
/// Prior to Fulu this cache was similar to the `BeaconProposerCache` in that beacon proposers were
/// determined at exactly the same time as the values in this cache, so the keys for the two caches
/// were identical.
///
/// Post-Fulu, we use a different key (the proposers have more lookahead).
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct EpochCache {

View File

@@ -51,7 +51,7 @@ impl ForkName {
/// This fork serves as the baseline for many tests, and the goal
/// is to ensure features are passing on this fork.
pub fn latest_stable() -> ForkName {
ForkName::Electra
ForkName::Fulu
}
/// Set the activation slots in the given `ChainSpec` so that the fork named by `self`
@@ -201,6 +201,46 @@ impl ForkName {
pub fn gloas_enabled(self) -> bool {
self >= ForkName::Gloas
}
pub fn fork_ascii(self) {
if self == ForkName::Fulu {
println!(
r#"
╔═══════════════════════════════════════╗
║ ║
║ TO FULU, MOAR BLOBS TO ETHEREUM ║
║ ║
║ III DECEMBER MMXXV ║
║ ║
╚═══════════════════════════════════════╝
=============================================================================
|||| ||||
|---------------------------------------------------------------------------|
|___-----___-----___-----___-----___-----___-----___-----___-----___-----___|
/ _ \===/ _ \ / _ \===/ _ \ / _ \===/ _ \ / _ \===/ _ \
( (.\ oOo /.) ) ( (.\ oOo /.) ) ( (.\ oOo /.) ) ( (.\ oOo /.) )
\__/=====\__/ \__/=====\__/ \__/=====\__/ \__/=====\__/
||||||| ||||||| ||||||| |||||||
||||||| ||||||| \\/), ||||||| |||||||
||||||| ||||||| ,'.' /, ||||||| |||||||
||||||| ||||||| (_)- / /, ||||||| |||||||
||||||| ||||||| /\_/ |__..--, * ||||||| |||||||
||||||| ||||||| (\___/\ \ \ / ).' ||||||| |||||||
||||||| ||||||| \____/ / (_ // ||||||| |||||||
||||||| ||||||| \\_ ,'--'\_( ||||||| |||||||
(oOoOo) (oOoOo) )_)_/ )_/ )_) (oOoOo) (oOoOo)
J%%%%%L J%%%%%L (_(_.'(_.'(_.' J%%%%%L J%%%%%L
ZZZZZZZZZ ZZZZZZZZZ ZZZZZZZZZ ZZZZZZZZZ
===========================================================================
|_________________________________________________________________________|
|___________________________________________________________________________|
|_____________________________________________________________________________|
|_______________________________________________________________________________|
"#
);
}
}
}
/// Map a fork name into a fork-versioned superstruct type like `BeaconBlock`.

View File

@@ -208,6 +208,8 @@ pub struct DenebPreset {
#[serde(with = "serde_utils::quoted_u64")]
pub max_blob_commitments_per_block: u64,
#[serde(with = "serde_utils::quoted_u64")]
pub kzg_commitment_inclusion_proof_depth: u64,
#[serde(with = "serde_utils::quoted_u64")]
pub field_elements_per_blob: u64,
}
@@ -215,6 +217,7 @@ impl DenebPreset {
pub fn from_chain_spec<E: EthSpec>(_spec: &ChainSpec) -> Self {
Self {
max_blob_commitments_per_block: E::max_blob_commitments_per_block() as u64,
kzg_commitment_inclusion_proof_depth: E::KzgCommitmentInclusionProofDepth::to_u64(),
field_elements_per_blob: E::field_elements_per_blob() as u64,
}
}