mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-21 13:54:44 +00:00
Gloas attestation verification (#8705)
https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#attestation-subnets Implements attestation verification logic for Gloas and adds a few gloas related tests. Note that a few of these tests rely on gloas test harness block production which hasn't been built out yet. So for now those tests are ignored. Co-Authored-By: Eitan Seri- Levi <eserilev@gmail.com> Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
@@ -368,6 +368,13 @@ impl GossipTester {
|
||||
self.harness.chain.epoch().unwrap()
|
||||
}
|
||||
|
||||
pub fn is_gloas(&self) -> bool {
|
||||
self.harness
|
||||
.spec
|
||||
.fork_name_at_slot::<E>(self.valid_attestation.data.slot)
|
||||
.gloas_enabled()
|
||||
}
|
||||
|
||||
pub fn earliest_valid_attestation_slot(&self) -> Slot {
|
||||
let offset = if self
|
||||
.harness
|
||||
@@ -522,6 +529,44 @@ impl GossipTester {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Like `inspect_aggregate_err`, but only runs the check if gloas is enabled.
|
||||
/// If gloas is not enabled, this is a no-op that returns self.
|
||||
pub fn inspect_aggregate_err_if_gloas<G, I>(
|
||||
self,
|
||||
desc: &str,
|
||||
get_attn: G,
|
||||
inspect_err: I,
|
||||
) -> Self
|
||||
where
|
||||
G: Fn(&Self, &mut SignedAggregateAndProof<E>),
|
||||
I: Fn(&Self, AttnError),
|
||||
{
|
||||
if self.is_gloas() {
|
||||
self.inspect_aggregate_err(desc, get_attn, inspect_err)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `inspect_unaggregate_err`, but only runs the check if gloas is enabled.
|
||||
/// If gloas is not enabled, this is a no-op that returns self.
|
||||
pub fn inspect_unaggregate_err_if_gloas<G, I>(
|
||||
self,
|
||||
desc: &str,
|
||||
get_attn: G,
|
||||
inspect_err: I,
|
||||
) -> Self
|
||||
where
|
||||
G: Fn(&Self, &mut SingleAttestation, &mut SubnetId, &ChainSpec),
|
||||
I: Fn(&Self, AttnError),
|
||||
{
|
||||
if self.is_gloas() {
|
||||
self.inspect_unaggregate_err(desc, get_attn, inspect_err)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Tests verification of `SignedAggregateAndProof` from the gossip network.
|
||||
#[tokio::test]
|
||||
@@ -854,6 +899,27 @@ async fn aggregated_gossip_verification() {
|
||||
))
|
||||
},
|
||||
)
|
||||
/*
|
||||
* [New in Gloas]: attestation.data.index must be < 2
|
||||
*/
|
||||
.inspect_aggregate_err_if_gloas(
|
||||
"gloas: aggregate with index >= 2",
|
||||
|_, a| match a.to_mut() {
|
||||
SignedAggregateAndProofRefMut::Base(_) => {
|
||||
panic!("Expected Electra attestation variant");
|
||||
}
|
||||
SignedAggregateAndProofRefMut::Electra(att) => {
|
||||
att.message.aggregate.data.index = 2;
|
||||
}
|
||||
},
|
||||
|_, err| {
|
||||
assert!(
|
||||
matches!(err, AttnError::CommitteeIndexInvalid),
|
||||
"expected CommitteeIndexInvalid, got {:?}",
|
||||
err
|
||||
)
|
||||
},
|
||||
)
|
||||
// NOTE: from here on, the tests are stateful, and rely on the valid attestation having
|
||||
// been seen.
|
||||
.import_valid_aggregate()
|
||||
@@ -1071,6 +1137,22 @@ async fn unaggregated_gossip_verification() {
|
||||
))
|
||||
},
|
||||
)
|
||||
/*
|
||||
* [New in Gloas]: attestation.data.index must be < 2
|
||||
*/
|
||||
.inspect_unaggregate_err_if_gloas(
|
||||
"gloas: attestation with index >= 2",
|
||||
|_, a, _, _| {
|
||||
a.data.index = 2;
|
||||
},
|
||||
|_, err| {
|
||||
assert!(
|
||||
matches!(err, AttnError::CommitteeIndexInvalid),
|
||||
"expected CommitteeIndexInvalid, got {:?}",
|
||||
err
|
||||
)
|
||||
},
|
||||
)
|
||||
// NOTE: from here on, the tests are stateful, and rely on the valid attestation having
|
||||
// been seen.
|
||||
.import_valid_unaggregate()
|
||||
@@ -1700,3 +1782,180 @@ async fn aggregated_attestation_verification_use_head_state_fork() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [New in Gloas]: Tests that unaggregated attestations with `data.index == 1` are rejected
|
||||
/// when `head_block.slot == attestation.data.slot`.
|
||||
///
|
||||
/// This test only runs when `FORK_NAME=gloas` is set with `fork_from_env` feature.
|
||||
// TODO(EIP-7732): Enable this test once gloas block production works in test harness.
|
||||
// `state.latest_execution_payload_header()` not available in Gloas.
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn gloas_unaggregated_attestation_same_slot_index_must_be_zero() {
|
||||
let harness = get_harness(VALIDATOR_COUNT);
|
||||
|
||||
// Skip this test if not running with gloas fork
|
||||
if !harness
|
||||
.spec
|
||||
.fork_name_at_epoch(Epoch::new(0))
|
||||
.gloas_enabled()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Extend the chain out a few epochs so we have some chain depth to play with.
|
||||
harness
|
||||
.extend_chain(
|
||||
MainnetEthSpec::slots_per_epoch() as usize * 3 - 1,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Produce a block in the current slot (this creates the same-slot scenario)
|
||||
harness
|
||||
.extend_chain(
|
||||
1,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::SomeValidators(vec![]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let current_slot = harness.chain.slot().expect("should get slot");
|
||||
let head = harness.chain.head_snapshot();
|
||||
|
||||
// Verify head block is in the current slot
|
||||
assert_eq!(
|
||||
head.beacon_block.slot(),
|
||||
current_slot,
|
||||
"head block should be in current slot for same-slot test"
|
||||
);
|
||||
|
||||
// Produce an attestation for the current slot
|
||||
let (mut attestation, _attester_sk, subnet_id) =
|
||||
get_valid_unaggregated_attestation(&harness.chain);
|
||||
|
||||
// Verify we have a same-slot scenario
|
||||
let attested_block_slot = harness
|
||||
.chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block(&attestation.data.beacon_block_root)
|
||||
.expect("block should exist")
|
||||
.slot;
|
||||
assert_eq!(
|
||||
attested_block_slot, attestation.data.slot,
|
||||
"attested block slot should equal attestation slot for same-slot test"
|
||||
);
|
||||
|
||||
// index == 1 should be rejected when head_block.slot == attestation.data.slot
|
||||
attestation.data.index = 1;
|
||||
let result = harness
|
||||
.chain
|
||||
.verify_unaggregated_attestation_for_gossip(&attestation, Some(subnet_id));
|
||||
assert!(
|
||||
matches!(result, Err(AttnError::CommitteeIndexNonZero(_))),
|
||||
"gloas: attestation with index == 1 when head_block.slot == attestation.data.slot should be rejected, got {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
/// [New in Gloas]: Tests that aggregated attestations with `data.index == 1` are rejected
|
||||
/// when `head_block.slot == attestation.data.slot`.
|
||||
///
|
||||
/// This test only runs when `FORK_NAME=gloas` is set with `fork_from_env` feature.
|
||||
// TODO(EIP-7732): Enable this test once gloas block production works in test harness.
|
||||
// `state.latest_execution_payload_header()` not available in Gloas.
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn gloas_aggregated_attestation_same_slot_index_must_be_zero() {
|
||||
let harness = get_harness(VALIDATOR_COUNT);
|
||||
|
||||
// Skip this test if not running with gloas fork
|
||||
if !harness
|
||||
.spec
|
||||
.fork_name_at_epoch(Epoch::new(0))
|
||||
.gloas_enabled()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Extend the chain out a few epochs so we have some chain depth to play with.
|
||||
harness
|
||||
.extend_chain(
|
||||
MainnetEthSpec::slots_per_epoch() as usize * 3 - 1,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Produce a block in the current slot (this creates the same-slot scenario)
|
||||
harness
|
||||
.extend_chain(
|
||||
1,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::SomeValidators(vec![]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let current_slot = harness.chain.slot().expect("should get slot");
|
||||
let head = harness.chain.head_snapshot();
|
||||
|
||||
// Verify head block is in the current slot
|
||||
assert_eq!(
|
||||
head.beacon_block.slot(),
|
||||
current_slot,
|
||||
"head block should be in current slot for same-slot test"
|
||||
);
|
||||
|
||||
// Produce an attestation for the current slot
|
||||
let (valid_attestation, _attester_sk, _subnet_id) =
|
||||
get_valid_unaggregated_attestation(&harness.chain);
|
||||
|
||||
// Verify we have a same-slot scenario
|
||||
let attested_block_slot = harness
|
||||
.chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block(&valid_attestation.data.beacon_block_root)
|
||||
.expect("block should exist")
|
||||
.slot;
|
||||
assert_eq!(
|
||||
attested_block_slot, valid_attestation.data.slot,
|
||||
"attested block slot should equal attestation slot for same-slot test"
|
||||
);
|
||||
|
||||
// Convert to aggregate
|
||||
let committee = head
|
||||
.beacon_state
|
||||
.get_beacon_committee(current_slot, valid_attestation.committee_index)
|
||||
.expect("should get committee");
|
||||
let fork_name = harness
|
||||
.spec
|
||||
.fork_name_at_slot::<E>(valid_attestation.data.slot);
|
||||
let aggregate_attestation =
|
||||
single_attestation_to_attestation(&valid_attestation, committee.committee, fork_name)
|
||||
.unwrap();
|
||||
|
||||
let (mut valid_aggregate, _, _) =
|
||||
get_valid_aggregated_attestation(&harness.chain, aggregate_attestation);
|
||||
|
||||
// index == 1 should be rejected when head_block.slot == attestation.data.slot
|
||||
match valid_aggregate.to_mut() {
|
||||
SignedAggregateAndProofRefMut::Base(att) => {
|
||||
att.message.aggregate.data.index = 1;
|
||||
}
|
||||
SignedAggregateAndProofRefMut::Electra(att) => {
|
||||
att.message.aggregate.data.index = 1;
|
||||
}
|
||||
}
|
||||
|
||||
let result = harness
|
||||
.chain
|
||||
.verify_aggregated_attestation_for_gossip(&valid_aggregate);
|
||||
assert!(
|
||||
matches!(result, Err(AttnError::CommitteeIndexNonZero(_))),
|
||||
"gloas: aggregate with index == 1 when head_block.slot == attestation.data.slot should be rejected, got {:?}",
|
||||
result.err()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user