Skip to content

Commit 27dce06

Browse files
authored
Gloas add process_payload_attestation (#8286)
* process_payload_attestation implemented per eip-7732 * allow duplicates in indexed payload attestation indices * updates per pr review
1 parent e354038 commit 27dce06

File tree

14 files changed

+523
-38
lines changed

14 files changed

+523
-38
lines changed

beacon_node/store/src/consensus_context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ impl<E: EthSpec> OnDiskConsensusContext<E> {
3535
proposer_index,
3636
current_block_root,
3737
indexed_attestations,
38+
indexed_payload_attestations: _,
39+
// TODO(EIP-7732): add indexed_payload_attestations to the on-disk format.
3840
} = ctxt;
3941
OnDiskConsensusContext {
4042
slot,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use crate::per_block_processing::errors::{
2+
BlockOperationError, PayloadAttestationInvalid as Invalid,
3+
};
4+
use types::*;
5+
6+
pub fn get_indexed_payload_attestation<E: EthSpec>(
7+
state: &BeaconState<E>,
8+
slot: Slot,
9+
payload_attestation: &PayloadAttestation<E>,
10+
spec: &ChainSpec,
11+
) -> Result<IndexedPayloadAttestation<E>, BlockOperationError<Invalid>> {
12+
let attesting_indices = get_payload_attesting_indices(state, slot, payload_attestation, spec)?;
13+
14+
Ok(IndexedPayloadAttestation {
15+
attesting_indices: VariableList::new(attesting_indices)?,
16+
data: payload_attestation.data.clone(),
17+
signature: payload_attestation.signature.clone(),
18+
})
19+
}
20+
21+
pub fn get_payload_attesting_indices<E: EthSpec>(
22+
state: &BeaconState<E>,
23+
slot: Slot,
24+
payload_attestation: &PayloadAttestation<E>,
25+
spec: &ChainSpec,
26+
) -> Result<Vec<u64>, BeaconStateError> {
27+
let ptc = state.get_ptc(slot, spec)?;
28+
29+
let bitlist = &payload_attestation.aggregation_bits;
30+
if bitlist.len() != E::PTCSize::to_usize() {
31+
return Err(BeaconStateError::InvalidBitfield);
32+
}
33+
34+
let mut attesting_indices = Vec::<u64>::new();
35+
for (i, index) in ptc.into_iter().enumerate() {
36+
if let Ok(true) = bitlist.get(i) {
37+
attesting_indices.push(index as u64);
38+
}
39+
}
40+
attesting_indices.sort_unstable();
41+
42+
Ok(attesting_indices)
43+
}

consensus/state_processing/src/common/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
mod deposit_data_tree;
22
mod get_attestation_participation;
33
mod get_attesting_indices;
4+
mod get_payload_attesting_indices;
45
mod initiate_validator_exit;
56
mod slash_validator;
67

@@ -13,6 +14,9 @@ pub use get_attestation_participation::get_attestation_participation_flag_indice
1314
pub use get_attesting_indices::{
1415
attesting_indices_base, attesting_indices_electra, get_attesting_indices_from_state,
1516
};
17+
pub use get_payload_attesting_indices::{
18+
get_indexed_payload_attestation, get_payload_attesting_indices,
19+
};
1620
pub use initiate_validator_exit::initiate_validator_exit;
1721
pub use slash_validator::slash_validator;
1822

consensus/state_processing/src/consensus_context.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
use crate::EpochCacheError;
2-
use crate::common::{attesting_indices_base, attesting_indices_electra};
3-
use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError};
2+
use crate::common::{
3+
attesting_indices_base, attesting_indices_electra, get_indexed_payload_attestation,
4+
};
5+
use crate::per_block_processing::errors::{
6+
AttestationInvalid, BlockOperationError, PayloadAttestationInvalid,
7+
};
48
use std::collections::{HashMap, hash_map::Entry};
59
use tree_hash::TreeHash;
610
use types::{
711
AbstractExecPayload, AttestationRef, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec,
8-
Hash256, IndexedAttestation, IndexedAttestationRef, SignedBeaconBlock, Slot,
12+
Hash256, IndexedAttestation, IndexedAttestationRef, IndexedPayloadAttestation,
13+
PayloadAttestation, SignedBeaconBlock, Slot,
914
};
1015

1116
#[derive(Debug, PartialEq, Clone)]
@@ -22,6 +27,8 @@ pub struct ConsensusContext<E: EthSpec> {
2227
pub current_block_root: Option<Hash256>,
2328
/// Cache of indexed attestations constructed during block processing.
2429
pub indexed_attestations: HashMap<Hash256, IndexedAttestation<E>>,
30+
/// Cache of indexed payload attestations constructed during block processing.
31+
pub indexed_payload_attestations: HashMap<Hash256, IndexedPayloadAttestation<E>>,
2532
}
2633

2734
#[derive(Debug, PartialEq, Clone)]
@@ -55,6 +62,7 @@ impl<E: EthSpec> ConsensusContext<E> {
5562
proposer_index: None,
5663
current_block_root: None,
5764
indexed_attestations: HashMap::new(),
65+
indexed_payload_attestations: HashMap::new(),
5866
}
5967
}
6068

@@ -177,6 +185,25 @@ impl<E: EthSpec> ConsensusContext<E> {
177185
.map(|indexed_attestation| (*indexed_attestation).to_ref())
178186
}
179187

188+
pub fn get_indexed_payload_attestation<'a>(
189+
&'a mut self,
190+
state: &BeaconState<E>,
191+
slot: Slot,
192+
payload_attestation: &'a PayloadAttestation<E>,
193+
spec: &ChainSpec,
194+
) -> Result<&'a IndexedPayloadAttestation<E>, BlockOperationError<PayloadAttestationInvalid>>
195+
{
196+
let key = payload_attestation.tree_hash_root();
197+
match self.indexed_payload_attestations.entry(key) {
198+
Entry::Occupied(occupied) => Ok(occupied.into_mut()),
199+
Entry::Vacant(vacant) => {
200+
let indexed_payload_attestation =
201+
get_indexed_payload_attestation(state, slot, payload_attestation, spec)?;
202+
Ok(vacant.insert(indexed_payload_attestation))
203+
}
204+
}
205+
}
206+
180207
pub fn num_cached_indexed_attestations(&self) -> usize {
181208
self.indexed_attestations.len()
182209
}

consensus/state_processing/src/per_block_processing.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub use self::verify_proposer_slashing::verify_proposer_slashing;
1818
pub use altair::sync_committee::process_sync_aggregate;
1919
pub use block_signature_verifier::{BlockSignatureVerifier, ParallelSignatureSets};
2020
pub use is_valid_indexed_attestation::is_valid_indexed_attestation;
21+
pub use is_valid_indexed_payload_attestation::is_valid_indexed_payload_attestation;
2122
pub use process_operations::process_operations;
2223
pub use verify_attestation::{
2324
verify_attestation_for_block_inclusion, verify_attestation_for_state,
@@ -33,6 +34,7 @@ pub mod block_signature_verifier;
3334
pub mod deneb;
3435
pub mod errors;
3536
mod is_valid_indexed_attestation;
37+
mod is_valid_indexed_payload_attestation;
3638
pub mod process_operations;
3739
pub mod process_withdrawals;
3840
pub mod signature_sets;
@@ -42,6 +44,7 @@ mod verify_attester_slashing;
4244
mod verify_bls_to_execution_change;
4345
mod verify_deposit;
4446
mod verify_exit;
47+
mod verify_payload_attestation;
4548
mod verify_proposer_slashing;
4649

4750
use crate::common::update_progressive_balances_cache::{

consensus/state_processing/src/per_block_processing/errors.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ pub enum BlockProcessingError {
4141
index: usize,
4242
reason: AttestationInvalid,
4343
},
44+
PayloadAttestationInvalid {
45+
index: usize,
46+
reason: PayloadAttestationInvalid,
47+
},
4448
DepositInvalid {
4549
index: usize,
4650
reason: DepositInvalid,
@@ -209,7 +213,8 @@ impl_into_block_processing_error_with_index!(
209213
AttestationInvalid,
210214
DepositInvalid,
211215
ExitInvalid,
212-
BlsExecutionChangeInvalid
216+
BlsExecutionChangeInvalid,
217+
PayloadAttestationInvalid
213218
);
214219

215220
pub type HeaderValidationError = BlockOperationError<HeaderInvalid>;
@@ -410,6 +415,58 @@ pub enum IndexedAttestationInvalid {
410415
SignatureSetError(SignatureSetError),
411416
}
412417

418+
#[derive(Debug, PartialEq, Clone)]
419+
pub enum PayloadAttestationInvalid {
420+
/// Block root does not match the parent beacon block root.
421+
BlockRootMismatch {
422+
expected: Hash256,
423+
found: Hash256,
424+
},
425+
/// The attestation slot is not the previous slot.
426+
SlotMismatch {
427+
expected: Slot,
428+
found: Slot,
429+
},
430+
BadIndexedPayloadAttestation(IndexedPayloadAttestationInvalid),
431+
}
432+
433+
impl From<BlockOperationError<IndexedPayloadAttestationInvalid>>
434+
for BlockOperationError<PayloadAttestationInvalid>
435+
{
436+
fn from(e: BlockOperationError<IndexedPayloadAttestationInvalid>) -> Self {
437+
match e {
438+
BlockOperationError::Invalid(e) => BlockOperationError::invalid(
439+
PayloadAttestationInvalid::BadIndexedPayloadAttestation(e),
440+
),
441+
BlockOperationError::BeaconStateError(e) => BlockOperationError::BeaconStateError(e),
442+
BlockOperationError::SignatureSetError(e) => BlockOperationError::SignatureSetError(e),
443+
BlockOperationError::SszTypesError(e) => BlockOperationError::SszTypesError(e),
444+
BlockOperationError::BitfieldError(e) => BlockOperationError::BitfieldError(e),
445+
BlockOperationError::ConsensusContext(e) => BlockOperationError::ConsensusContext(e),
446+
BlockOperationError::ArithError(e) => BlockOperationError::ArithError(e),
447+
}
448+
}
449+
}
450+
451+
#[derive(Debug, PartialEq, Clone)]
452+
pub enum IndexedPayloadAttestationInvalid {
453+
/// The number of indices is 0.
454+
IndicesEmpty,
455+
/// The validator indices were not in increasing order.
456+
///
457+
/// The error occurred between the given `index` and `index + 1`
458+
BadValidatorIndicesOrdering(usize),
459+
/// The validator index is unknown. One cannot slash one who does not exist.
460+
UnknownValidator(u64),
461+
/// The indexed attestation aggregate signature was not valid.
462+
BadSignature,
463+
/// There was an error whilst attempting to get a set of signatures. The signatures may have
464+
/// been invalid or an internal error occurred.
465+
SignatureSetError(SignatureSetError),
466+
/// Invalid Payload Status
467+
PayloadStatusInvalid,
468+
}
469+
413470
#[derive(Debug, PartialEq, Clone)]
414471
pub enum DepositInvalid {
415472
/// The signature (proof-of-possession) does not match the given pubkey.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use super::errors::{BlockOperationError, IndexedPayloadAttestationInvalid as Invalid};
2+
use super::signature_sets::{get_pubkey_from_state, indexed_payload_attestation_signature_set};
3+
use crate::VerifySignatures;
4+
use itertools::Itertools;
5+
use types::*;
6+
7+
fn error(reason: Invalid) -> BlockOperationError<Invalid> {
8+
BlockOperationError::invalid(reason)
9+
}
10+
11+
pub fn is_valid_indexed_payload_attestation<E: EthSpec>(
12+
state: &BeaconState<E>,
13+
indexed_payload_attestation: &IndexedPayloadAttestation<E>,
14+
verify_signatures: VerifySignatures,
15+
spec: &ChainSpec,
16+
) -> Result<(), BlockOperationError<Invalid>> {
17+
// Verify indices are non-empty and sorted (duplicates allowed)
18+
let indices = &indexed_payload_attestation.attesting_indices;
19+
verify!(!indices.is_empty(), Invalid::IndicesEmpty);
20+
let check_sorted = |list: &[u64]| -> Result<(), BlockOperationError<Invalid>> {
21+
list.iter()
22+
.tuple_windows()
23+
.enumerate()
24+
.try_for_each(|(i, (x, y))| {
25+
if x <= y {
26+
Ok(())
27+
} else {
28+
Err(error(Invalid::BadValidatorIndicesOrdering(i)))
29+
}
30+
})?;
31+
Ok(())
32+
};
33+
check_sorted(indices)?;
34+
35+
if verify_signatures.is_true() {
36+
verify!(
37+
indexed_payload_attestation_signature_set(
38+
state,
39+
|i| get_pubkey_from_state(state, i),
40+
&indexed_payload_attestation.signature,
41+
indexed_payload_attestation,
42+
spec
43+
)?
44+
.verify(),
45+
Invalid::BadSignature
46+
);
47+
}
48+
49+
Ok(())
50+
}

consensus/state_processing/src/per_block_processing/process_operations.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::common::{
55
slash_validator,
66
};
77
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
8+
use crate::per_block_processing::verify_payload_attestation::verify_payload_attestation;
89
use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR};
910
use types::typenum::U33;
1011

@@ -37,7 +38,15 @@ pub fn process_operations<E: EthSpec, Payload: AbstractExecPayload<E>>(
3738
process_bls_to_execution_changes(state, bls_to_execution_changes, verify_signatures, spec)?;
3839
}
3940

40-
if state.fork_name_unchecked().electra_enabled() {
41+
if state.fork_name_unchecked().gloas_enabled() {
42+
process_payload_attestations(
43+
state,
44+
block_body.payload_attestations()?.iter(),
45+
verify_signatures,
46+
ctxt,
47+
spec,
48+
)?;
49+
} else if state.fork_name_unchecked().electra_enabled() {
4150
state.update_pubkey_cache()?;
4251
process_deposit_requests(state, &block_body.execution_requests()?.deposits, spec)?;
4352
process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?;
@@ -789,3 +798,52 @@ pub fn process_consolidation_request<E: EthSpec>(
789798

790799
Ok(())
791800
}
801+
802+
// TODO(EIP-7732): Add test cases for `process_payload_attestations` to
803+
// `consensus/state_processing/src/per_block_processing/tests.rs`.
804+
// The tests will require being able to build Gloas blocks with PayloadAttestations,
805+
// which currently fails due to incomplete Gloas block structure as mentioned here
806+
// https://github.com/sigp/lighthouse/pull/8273
807+
pub fn process_payload_attestation<E: EthSpec>(
808+
state: &mut BeaconState<E>,
809+
payload_attestation: &PayloadAttestation<E>,
810+
att_index: usize,
811+
verify_signatures: VerifySignatures,
812+
ctxt: &mut ConsensusContext<E>,
813+
spec: &ChainSpec,
814+
) -> Result<(), BlockProcessingError> {
815+
verify_payload_attestation(state, payload_attestation, ctxt, verify_signatures, spec)
816+
.map_err(|e| e.into_with_index(att_index))
817+
}
818+
819+
pub fn process_payload_attestations<'a, E: EthSpec, I>(
820+
state: &mut BeaconState<E>,
821+
payload_attestations: I,
822+
verify_signatures: VerifySignatures,
823+
ctxt: &mut ConsensusContext<E>,
824+
spec: &ChainSpec,
825+
) -> Result<(), BlockProcessingError>
826+
where
827+
I: Iterator<Item = &'a PayloadAttestation<E>>,
828+
{
829+
// Ensure required caches are all built. These should be no-ops during regular operation.
830+
// TODO(EIP-7732): verify necessary caches
831+
state.build_committee_cache(RelativeEpoch::Current, spec)?;
832+
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
833+
initialize_epoch_cache(state, spec)?;
834+
initialize_progressive_balances_cache(state, spec)?;
835+
state.build_slashings_cache()?;
836+
837+
payload_attestations
838+
.enumerate()
839+
.try_for_each(|(i, payload_attestation)| {
840+
process_payload_attestation(
841+
state,
842+
payload_attestation,
843+
i,
844+
verify_signatures,
845+
ctxt,
846+
spec,
847+
)
848+
})
849+
}

0 commit comments

Comments
 (0)