Gloas alpha spec 8 (#9315)

https://github.com/ethereum/consensus-specs/releases/tag/v1.7.0-alpha.8


  


Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>

Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Eitan Seri-Levi
2026-05-21 23:21:20 -07:00
committed by GitHub
parent b5d5644eeb
commit 60abd4b5b9
36 changed files with 863 additions and 243 deletions

View File

@@ -1,6 +1,6 @@
use super::*;
use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
use ::fork_choice::{PayloadVerificationStatus, ProposerHeadError};
use ::fork_choice::{AttestationFromBlock, PayloadVerificationStatus, ProposerHeadError};
use beacon_chain::beacon_proposer_cache::compute_proposer_duties_from_head;
use beacon_chain::blob_verification::GossipBlobError;
use beacon_chain::block_verification_types::LookupBlock;
@@ -19,13 +19,16 @@ use beacon_chain::{
custody_context::NodeCustodyType,
test_utils::{BeaconChainHarness, EphemeralHarnessType},
};
use bls::AggregateSignature;
use execution_layer::{
PayloadStatusV1, PayloadStatusV1Status, json_structures::JsonPayloadStatusV1Status,
};
use serde::Deserialize;
use ssz_derive::Decode;
use ssz_types::VariableList;
use state_processing::VerifySignatures;
use state_processing::envelope_processing::verify_execution_payload_envelope;
use state_processing::per_block_processing::is_valid_indexed_payload_attestation;
use state_processing::state_advance::complete_state_advance;
use std::future::Future;
use std::sync::Arc;
@@ -34,8 +37,8 @@ use types::{
Attestation, AttestationRef, AttesterSlashing, AttesterSlashingRef, BeaconBlock, BeaconState,
BlobSidecar, BlobsList, BlockImportSource, Checkpoint, DataColumnSidecar,
DataColumnSidecarList, DataColumnSubnetId, ExecutionBlockHash, Hash256, IndexedAttestation,
KzgProof, ProposerPreparationData, SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot,
Uint256,
IndexedPayloadAttestation, KzgProof, PayloadAttestationMessage, ProposerPreparationData,
SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot, Uint256,
};
// When set to true, cache any states fetched from the db.
@@ -63,6 +66,13 @@ pub struct ShouldOverrideFcu {
result: bool,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PayloadVoteCheck {
block_root: Hash256,
votes: Vec<Option<bool>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Checks {
@@ -78,6 +88,8 @@ pub struct Checks {
get_proposer_head: Option<Hash256>,
should_override_forkchoice_update: Option<ShouldOverrideFcu>,
head_payload_status: Option<u8>,
payload_timeliness_vote: Option<PayloadVoteCheck>,
payload_data_availability_vote: Option<PayloadVoteCheck>,
}
#[derive(Debug, Clone, Deserialize)]
@@ -108,6 +120,7 @@ pub enum Step<
TAttesterSlashing,
TPowBlock,
TExecutionPayload = String,
TPayloadAttestationMessage = String,
> {
Tick {
tick: u64,
@@ -146,6 +159,15 @@ pub enum Step<
execution_payload: TExecutionPayload,
valid: bool,
},
PayloadAttestationMessage {
payload_attestation_message: TPayloadAttestationMessage,
#[serde(default = "default_true")]
valid: bool,
},
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Deserialize)]
@@ -170,6 +192,7 @@ pub struct ForkChoiceTest<E: EthSpec> {
AttesterSlashing<E>,
PowBlock,
SignedExecutionPayloadEnvelope<E>,
PayloadAttestationMessage,
>,
>,
}
@@ -184,8 +207,12 @@ impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
.expect("path must be valid OsStr")
.to_string();
let spec = &testing_spec::<E>(fork_name);
let steps: Vec<Step<String, String, Vec<String>, String, String, String>> =
yaml_decode_file(&path.join("steps.yaml"))?;
#[allow(clippy::type_complexity)]
let steps: Vec<
Step<String, String, Vec<String>, String, String, String, String, String>,
> = yaml_decode_file(&path.join("steps.yaml"))?;
// Resolve the object names in `steps.yaml` into actual decoded block/attestation objects.
let steps = steps
.into_iter()
@@ -301,6 +328,18 @@ impl<E: EthSpec> LoadCase for ForkChoiceTest<E> {
valid,
})
}
Step::PayloadAttestationMessage {
payload_attestation_message,
valid,
} => {
let msg: PayloadAttestationMessage = ssz_decode_file(
&path.join(format!("{payload_attestation_message}.ssz_snappy")),
)?;
Ok(Step::PayloadAttestationMessage {
payload_attestation_message: msg,
valid,
})
}
})
.collect::<Result<_, _>>()?;
let anchor_state = ssz_decode_state(&path.join("anchor_state.ssz_snappy"), spec)?;
@@ -381,6 +420,8 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
get_proposer_head,
should_override_forkchoice_update: should_override_fcu,
head_payload_status,
payload_timeliness_vote,
payload_data_availability_vote,
} = checks.as_ref();
if let Some(expected_head) = head {
@@ -431,6 +472,14 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
if let Some(expected_status) = head_payload_status {
tester.check_head_payload_status(*expected_status)?;
}
if let Some(expected) = payload_timeliness_vote {
tester.check_payload_timeliness_vote(expected)?;
}
if let Some(expected) = payload_data_availability_vote {
tester.check_payload_data_availability_vote(expected)?;
}
}
Step::MaybeValidBlockAndColumns {
@@ -446,6 +495,13 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
} => {
tester.process_execution_payload(execution_payload, *valid)?;
}
Step::PayloadAttestationMessage {
payload_attestation_message,
valid,
} => {
tester
.process_payload_attestation_message(payload_attestation_message, *valid)?;
}
}
}
@@ -1149,6 +1205,173 @@ impl<E: EthSpec> Tester<E> {
expected_should_override_fcu.result,
)
}
pub fn process_payload_attestation_message(
&self,
msg: &PayloadAttestationMessage,
valid: bool,
) -> Result<(), Error> {
let slot = msg.data.slot;
let block_root = msg.data.beacon_block_root;
// Get the state at the block to compute the PTC and verify signature.
let store = &self.harness.chain.store;
let block = store
.get_blinded_block(&block_root)
.map_err(|e| Error::InternalError(format!("Failed to load block: {e:?}")))?;
let state_opt = block.and_then(|block| {
store
.get_hot_state(&block.state_root(), CACHE_STATE_IN_TESTS)
.ok()?
});
// Build IndexedPayloadAttestation from the message.
let indexed = IndexedPayloadAttestation::<E> {
attesting_indices: VariableList::new(vec![msg.validator_index]).unwrap(),
data: msg.data.clone(),
signature: AggregateSignature::from(&msg.signature),
};
let result = if let Some(ref state) = state_opt {
is_valid_indexed_payload_attestation(
state,
&indexed,
VerifySignatures::True,
&self.spec,
)
.map_err(|e| {
Error::InternalError(format!(
"payload attestation signature verification failed for validator {}: {:?}",
msg.validator_index, e
))
})
.and_then(|_| {
let ptc = state.get_ptc(slot, &self.spec).map_err(|e| {
Error::InternalError(format!(
"Could not compute PTC for block root {block_root:?} at slot {slot:?}: {e:?}"
))
})?;
self.harness
.chain
.canonical_head
.fork_choice_write_lock()
.on_payload_attestation(
self.harness.chain.slot().unwrap(),
&indexed,
AttestationFromBlock::False,
&ptc.0,
)
.map_err(|e| {
Error::InternalError(format!(
"on_payload_attestation for validator {} failed: {:?}",
msg.validator_index, e
))
})
})
} else {
Err(Error::InternalError(format!(
"Could not get state for block root {block_root:?} at slot {slot:?}"
)))
};
if valid {
result?;
} else if result.is_ok() {
return Err(Error::DidntFail(format!(
"payload_attestation_message for validator {} should have failed",
msg.validator_index
)));
}
Ok(())
}
pub fn check_payload_timeliness_vote(&self, expected: &PayloadVoteCheck) -> Result<(), Error> {
let fc = self.harness.chain.canonical_head.fork_choice_read_lock();
let proto_array = fc.proto_array().core_proto_array();
let node_index = proto_array
.indices
.get(&expected.block_root)
.ok_or_else(|| {
Error::InternalError(format!(
"Block root {:?} not found in proto array",
expected.block_root
))
})?;
let node = proto_array
.nodes
.get(*node_index)
.ok_or_else(|| Error::InternalError(format!("Node index {} not found", node_index)))?;
let v29 = node
.as_v29()
.map_err(|_| Error::InternalError("Node is not V29".to_string()))?;
let timeliness_votes = &v29.payload_timeliness_votes;
let participation = &v29.ptc_participation;
for (i, expected_vote) in expected.votes.iter().enumerate() {
let actual = if !participation.get(i).unwrap() {
None // not yet voted
} else {
Some(timeliness_votes.get(i).unwrap())
};
if actual != *expected_vote {
return Err(Error::NotEqual(format!(
"payload_timeliness_vote[{}]: Got {:?} | Expected {:?}",
i, actual, expected_vote
)));
}
}
Ok(())
}
pub fn check_payload_data_availability_vote(
&self,
expected: &PayloadVoteCheck,
) -> Result<(), Error> {
let fc = self.harness.chain.canonical_head.fork_choice_read_lock();
let proto_array = fc.proto_array().core_proto_array();
let node_index = proto_array
.indices
.get(&expected.block_root)
.ok_or_else(|| {
Error::InternalError(format!(
"Block root {:?} not found in proto array",
expected.block_root
))
})?;
let node = proto_array
.nodes
.get(*node_index)
.ok_or_else(|| Error::InternalError(format!("Node index {} not found", node_index)))?;
let v29 = node
.as_v29()
.map_err(|_| Error::InternalError("Node is not V29".to_string()))?;
let availability_votes = &v29.payload_data_availability_votes;
let participation = &v29.ptc_participation;
for (i, expected_vote) in expected.votes.iter().enumerate() {
let actual = if !participation.get(i).unwrap() {
None // not yet voted
} else {
Some(availability_votes.get(i).unwrap())
};
if actual != *expected_vote {
return Err(Error::NotEqual(format!(
"payload_data_availability_vote[{}]: Got {:?} | Expected {:?}",
i, actual, expected_vote
)));
}
}
Ok(())
}
}
/// Checks that the `head` checkpoint from the beacon chain head matches the `fc` checkpoint gleaned

View File

@@ -715,10 +715,8 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceHandler<E> {
return false;
}
// Deposit tests exist only for Electra and Fulu (not Gloas).
if self.handler_name == "deposit_with_reorg"
&& (!fork_name.electra_enabled() || fork_name.gloas_enabled())
{
// Deposit tests exist only for Electra and later.
if self.handler_name == "deposit_with_reorg" && !fork_name.electra_enabled() {
return false;
}
@@ -727,10 +725,11 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceHandler<E> {
return false;
}
// on_execution_payload_envelope and get_parent_payload_status tests exist only for
// Gloas and later.
// on_execution_payload_envelope, get_parent_payload_status, and
// on_payload_attestation_message tests exist only for Gloas and later.
if (self.handler_name == "on_execution_payload_envelope"
|| self.handler_name == "get_parent_payload_status")
|| self.handler_name == "get_parent_payload_status"
|| self.handler_name == "on_payload_attestation_message")
&& !fork_name.gloas_enabled()
{
return false;