mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 04:37:13 +00:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user