mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-09 11:41:51 +00:00
Gloas add process_payload_attestation (#8286)
* process_payload_attestation implemented per eip-7732 * allow duplicates in indexed payload attestation indices * updates per pr review
This commit is contained in:
@@ -35,6 +35,8 @@ impl<E: EthSpec> OnDiskConsensusContext<E> {
|
||||
proposer_index,
|
||||
current_block_root,
|
||||
indexed_attestations,
|
||||
indexed_payload_attestations: _,
|
||||
// TODO(EIP-7732): add indexed_payload_attestations to the on-disk format.
|
||||
} = ctxt;
|
||||
OnDiskConsensusContext {
|
||||
slot,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use crate::per_block_processing::errors::{
|
||||
BlockOperationError, PayloadAttestationInvalid as Invalid,
|
||||
};
|
||||
use types::*;
|
||||
|
||||
pub fn get_indexed_payload_attestation<E: EthSpec>(
|
||||
state: &BeaconState<E>,
|
||||
slot: Slot,
|
||||
payload_attestation: &PayloadAttestation<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<IndexedPayloadAttestation<E>, BlockOperationError<Invalid>> {
|
||||
let attesting_indices = get_payload_attesting_indices(state, slot, payload_attestation, spec)?;
|
||||
|
||||
Ok(IndexedPayloadAttestation {
|
||||
attesting_indices: VariableList::new(attesting_indices)?,
|
||||
data: payload_attestation.data.clone(),
|
||||
signature: payload_attestation.signature.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_payload_attesting_indices<E: EthSpec>(
|
||||
state: &BeaconState<E>,
|
||||
slot: Slot,
|
||||
payload_attestation: &PayloadAttestation<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<u64>, BeaconStateError> {
|
||||
let ptc = state.get_ptc(slot, spec)?;
|
||||
|
||||
let bitlist = &payload_attestation.aggregation_bits;
|
||||
if bitlist.len() != E::PTCSize::to_usize() {
|
||||
return Err(BeaconStateError::InvalidBitfield);
|
||||
}
|
||||
|
||||
let mut attesting_indices = Vec::<u64>::new();
|
||||
for (i, index) in ptc.into_iter().enumerate() {
|
||||
if let Ok(true) = bitlist.get(i) {
|
||||
attesting_indices.push(index as u64);
|
||||
}
|
||||
}
|
||||
attesting_indices.sort_unstable();
|
||||
|
||||
Ok(attesting_indices)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod deposit_data_tree;
|
||||
mod get_attestation_participation;
|
||||
mod get_attesting_indices;
|
||||
mod get_payload_attesting_indices;
|
||||
mod initiate_validator_exit;
|
||||
mod slash_validator;
|
||||
|
||||
@@ -13,6 +14,9 @@ pub use get_attestation_participation::get_attestation_participation_flag_indice
|
||||
pub use get_attesting_indices::{
|
||||
attesting_indices_base, attesting_indices_electra, get_attesting_indices_from_state,
|
||||
};
|
||||
pub use get_payload_attesting_indices::{
|
||||
get_indexed_payload_attestation, get_payload_attesting_indices,
|
||||
};
|
||||
pub use initiate_validator_exit::initiate_validator_exit;
|
||||
pub use slash_validator::slash_validator;
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use crate::EpochCacheError;
|
||||
use crate::common::{attesting_indices_base, attesting_indices_electra};
|
||||
use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError};
|
||||
use crate::common::{
|
||||
attesting_indices_base, attesting_indices_electra, get_indexed_payload_attestation,
|
||||
};
|
||||
use crate::per_block_processing::errors::{
|
||||
AttestationInvalid, BlockOperationError, PayloadAttestationInvalid,
|
||||
};
|
||||
use std::collections::{HashMap, hash_map::Entry};
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
AbstractExecPayload, AttestationRef, BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec,
|
||||
Hash256, IndexedAttestation, IndexedAttestationRef, SignedBeaconBlock, Slot,
|
||||
Hash256, IndexedAttestation, IndexedAttestationRef, IndexedPayloadAttestation,
|
||||
PayloadAttestation, SignedBeaconBlock, Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -22,6 +27,8 @@ pub struct ConsensusContext<E: EthSpec> {
|
||||
pub current_block_root: Option<Hash256>,
|
||||
/// Cache of indexed attestations constructed during block processing.
|
||||
pub indexed_attestations: HashMap<Hash256, IndexedAttestation<E>>,
|
||||
/// Cache of indexed payload attestations constructed during block processing.
|
||||
pub indexed_payload_attestations: HashMap<Hash256, IndexedPayloadAttestation<E>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -55,6 +62,7 @@ impl<E: EthSpec> ConsensusContext<E> {
|
||||
proposer_index: None,
|
||||
current_block_root: None,
|
||||
indexed_attestations: HashMap::new(),
|
||||
indexed_payload_attestations: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +185,25 @@ impl<E: EthSpec> ConsensusContext<E> {
|
||||
.map(|indexed_attestation| (*indexed_attestation).to_ref())
|
||||
}
|
||||
|
||||
pub fn get_indexed_payload_attestation<'a>(
|
||||
&'a mut self,
|
||||
state: &BeaconState<E>,
|
||||
slot: Slot,
|
||||
payload_attestation: &'a PayloadAttestation<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<&'a IndexedPayloadAttestation<E>, BlockOperationError<PayloadAttestationInvalid>>
|
||||
{
|
||||
let key = payload_attestation.tree_hash_root();
|
||||
match self.indexed_payload_attestations.entry(key) {
|
||||
Entry::Occupied(occupied) => Ok(occupied.into_mut()),
|
||||
Entry::Vacant(vacant) => {
|
||||
let indexed_payload_attestation =
|
||||
get_indexed_payload_attestation(state, slot, payload_attestation, spec)?;
|
||||
Ok(vacant.insert(indexed_payload_attestation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn num_cached_indexed_attestations(&self) -> usize {
|
||||
self.indexed_attestations.len()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub use self::verify_proposer_slashing::verify_proposer_slashing;
|
||||
pub use altair::sync_committee::process_sync_aggregate;
|
||||
pub use block_signature_verifier::{BlockSignatureVerifier, ParallelSignatureSets};
|
||||
pub use is_valid_indexed_attestation::is_valid_indexed_attestation;
|
||||
pub use is_valid_indexed_payload_attestation::is_valid_indexed_payload_attestation;
|
||||
pub use process_operations::process_operations;
|
||||
pub use verify_attestation::{
|
||||
verify_attestation_for_block_inclusion, verify_attestation_for_state,
|
||||
@@ -33,6 +34,7 @@ pub mod block_signature_verifier;
|
||||
pub mod deneb;
|
||||
pub mod errors;
|
||||
mod is_valid_indexed_attestation;
|
||||
mod is_valid_indexed_payload_attestation;
|
||||
pub mod process_operations;
|
||||
pub mod process_withdrawals;
|
||||
pub mod signature_sets;
|
||||
@@ -42,6 +44,7 @@ mod verify_attester_slashing;
|
||||
mod verify_bls_to_execution_change;
|
||||
mod verify_deposit;
|
||||
mod verify_exit;
|
||||
mod verify_payload_attestation;
|
||||
mod verify_proposer_slashing;
|
||||
|
||||
use crate::common::update_progressive_balances_cache::{
|
||||
|
||||
@@ -41,6 +41,10 @@ pub enum BlockProcessingError {
|
||||
index: usize,
|
||||
reason: AttestationInvalid,
|
||||
},
|
||||
PayloadAttestationInvalid {
|
||||
index: usize,
|
||||
reason: PayloadAttestationInvalid,
|
||||
},
|
||||
DepositInvalid {
|
||||
index: usize,
|
||||
reason: DepositInvalid,
|
||||
@@ -209,7 +213,8 @@ impl_into_block_processing_error_with_index!(
|
||||
AttestationInvalid,
|
||||
DepositInvalid,
|
||||
ExitInvalid,
|
||||
BlsExecutionChangeInvalid
|
||||
BlsExecutionChangeInvalid,
|
||||
PayloadAttestationInvalid
|
||||
);
|
||||
|
||||
pub type HeaderValidationError = BlockOperationError<HeaderInvalid>;
|
||||
@@ -410,6 +415,58 @@ pub enum IndexedAttestationInvalid {
|
||||
SignatureSetError(SignatureSetError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum PayloadAttestationInvalid {
|
||||
/// Block root does not match the parent beacon block root.
|
||||
BlockRootMismatch {
|
||||
expected: Hash256,
|
||||
found: Hash256,
|
||||
},
|
||||
/// The attestation slot is not the previous slot.
|
||||
SlotMismatch {
|
||||
expected: Slot,
|
||||
found: Slot,
|
||||
},
|
||||
BadIndexedPayloadAttestation(IndexedPayloadAttestationInvalid),
|
||||
}
|
||||
|
||||
impl From<BlockOperationError<IndexedPayloadAttestationInvalid>>
|
||||
for BlockOperationError<PayloadAttestationInvalid>
|
||||
{
|
||||
fn from(e: BlockOperationError<IndexedPayloadAttestationInvalid>) -> Self {
|
||||
match e {
|
||||
BlockOperationError::Invalid(e) => BlockOperationError::invalid(
|
||||
PayloadAttestationInvalid::BadIndexedPayloadAttestation(e),
|
||||
),
|
||||
BlockOperationError::BeaconStateError(e) => BlockOperationError::BeaconStateError(e),
|
||||
BlockOperationError::SignatureSetError(e) => BlockOperationError::SignatureSetError(e),
|
||||
BlockOperationError::SszTypesError(e) => BlockOperationError::SszTypesError(e),
|
||||
BlockOperationError::BitfieldError(e) => BlockOperationError::BitfieldError(e),
|
||||
BlockOperationError::ConsensusContext(e) => BlockOperationError::ConsensusContext(e),
|
||||
BlockOperationError::ArithError(e) => BlockOperationError::ArithError(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum IndexedPayloadAttestationInvalid {
|
||||
/// The number of indices is 0.
|
||||
IndicesEmpty,
|
||||
/// The validator indices were not in increasing order.
|
||||
///
|
||||
/// The error occurred between the given `index` and `index + 1`
|
||||
BadValidatorIndicesOrdering(usize),
|
||||
/// The validator index is unknown. One cannot slash one who does not exist.
|
||||
UnknownValidator(u64),
|
||||
/// The indexed attestation aggregate signature was not valid.
|
||||
BadSignature,
|
||||
/// There was an error whilst attempting to get a set of signatures. The signatures may have
|
||||
/// been invalid or an internal error occurred.
|
||||
SignatureSetError(SignatureSetError),
|
||||
/// Invalid Payload Status
|
||||
PayloadStatusInvalid,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum DepositInvalid {
|
||||
/// The signature (proof-of-possession) does not match the given pubkey.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
use super::errors::{BlockOperationError, IndexedPayloadAttestationInvalid as Invalid};
|
||||
use super::signature_sets::{get_pubkey_from_state, indexed_payload_attestation_signature_set};
|
||||
use crate::VerifySignatures;
|
||||
use itertools::Itertools;
|
||||
use types::*;
|
||||
|
||||
fn error(reason: Invalid) -> BlockOperationError<Invalid> {
|
||||
BlockOperationError::invalid(reason)
|
||||
}
|
||||
|
||||
pub fn is_valid_indexed_payload_attestation<E: EthSpec>(
|
||||
state: &BeaconState<E>,
|
||||
indexed_payload_attestation: &IndexedPayloadAttestation<E>,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockOperationError<Invalid>> {
|
||||
// Verify indices are non-empty and sorted (duplicates allowed)
|
||||
let indices = &indexed_payload_attestation.attesting_indices;
|
||||
verify!(!indices.is_empty(), Invalid::IndicesEmpty);
|
||||
let check_sorted = |list: &[u64]| -> Result<(), BlockOperationError<Invalid>> {
|
||||
list.iter()
|
||||
.tuple_windows()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, (x, y))| {
|
||||
if x <= y {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error(Invalid::BadValidatorIndicesOrdering(i)))
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
};
|
||||
check_sorted(indices)?;
|
||||
|
||||
if verify_signatures.is_true() {
|
||||
verify!(
|
||||
indexed_payload_attestation_signature_set(
|
||||
state,
|
||||
|i| get_pubkey_from_state(state, i),
|
||||
&indexed_payload_attestation.signature,
|
||||
indexed_payload_attestation,
|
||||
spec
|
||||
)?
|
||||
.verify(),
|
||||
Invalid::BadSignature
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use crate::common::{
|
||||
slash_validator,
|
||||
};
|
||||
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
|
||||
use crate::per_block_processing::verify_payload_attestation::verify_payload_attestation;
|
||||
use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR};
|
||||
use types::typenum::U33;
|
||||
|
||||
@@ -37,7 +38,15 @@ pub fn process_operations<E: EthSpec, Payload: AbstractExecPayload<E>>(
|
||||
process_bls_to_execution_changes(state, bls_to_execution_changes, verify_signatures, spec)?;
|
||||
}
|
||||
|
||||
if state.fork_name_unchecked().electra_enabled() {
|
||||
if state.fork_name_unchecked().gloas_enabled() {
|
||||
process_payload_attestations(
|
||||
state,
|
||||
block_body.payload_attestations()?.iter(),
|
||||
verify_signatures,
|
||||
ctxt,
|
||||
spec,
|
||||
)?;
|
||||
} else if state.fork_name_unchecked().electra_enabled() {
|
||||
state.update_pubkey_cache()?;
|
||||
process_deposit_requests(state, &block_body.execution_requests()?.deposits, spec)?;
|
||||
process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?;
|
||||
@@ -789,3 +798,52 @@ pub fn process_consolidation_request<E: EthSpec>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO(EIP-7732): Add test cases for `process_payload_attestations` to
|
||||
// `consensus/state_processing/src/per_block_processing/tests.rs`.
|
||||
// The tests will require being able to build Gloas blocks with PayloadAttestations,
|
||||
// which currently fails due to incomplete Gloas block structure as mentioned here
|
||||
// https://github.com/sigp/lighthouse/pull/8273
|
||||
pub fn process_payload_attestation<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
payload_attestation: &PayloadAttestation<E>,
|
||||
att_index: usize,
|
||||
verify_signatures: VerifySignatures,
|
||||
ctxt: &mut ConsensusContext<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
verify_payload_attestation(state, payload_attestation, ctxt, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(att_index))
|
||||
}
|
||||
|
||||
pub fn process_payload_attestations<'a, E: EthSpec, I>(
|
||||
state: &mut BeaconState<E>,
|
||||
payload_attestations: I,
|
||||
verify_signatures: VerifySignatures,
|
||||
ctxt: &mut ConsensusContext<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError>
|
||||
where
|
||||
I: Iterator<Item = &'a PayloadAttestation<E>>,
|
||||
{
|
||||
// Ensure required caches are all built. These should be no-ops during regular operation.
|
||||
// TODO(EIP-7732): verify necessary caches
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
initialize_epoch_cache(state, spec)?;
|
||||
initialize_progressive_balances_cache(state, spec)?;
|
||||
state.build_slashings_cache()?;
|
||||
|
||||
payload_attestations
|
||||
.enumerate()
|
||||
.try_for_each(|(i, payload_attestation)| {
|
||||
process_payload_attestation(
|
||||
state,
|
||||
payload_attestation,
|
||||
i,
|
||||
verify_signatures,
|
||||
ctxt,
|
||||
spec,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@ use tree_hash::TreeHash;
|
||||
use types::{
|
||||
AbstractExecPayload, AggregateSignature, AttesterSlashingRef, BeaconBlockRef, BeaconState,
|
||||
BeaconStateError, ChainSpec, DepositData, Domain, Epoch, EthSpec, Fork, Hash256,
|
||||
InconsistentFork, IndexedAttestation, IndexedAttestationRef, ProposerSlashing, PublicKey,
|
||||
PublicKeyBytes, Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader,
|
||||
SignedBlsToExecutionChange, SignedContributionAndProof, SignedExecutionPayloadBid,
|
||||
SignedExecutionPayloadEnvelope, SignedRoot, SignedVoluntaryExit, SigningData, Slot,
|
||||
SyncAggregate, SyncAggregatorSelectionData, Unsigned,
|
||||
InconsistentFork, IndexedAttestation, IndexedAttestationRef, IndexedPayloadAttestation,
|
||||
ProposerSlashing, PublicKey, PublicKeyBytes, Signature, SignedAggregateAndProof,
|
||||
SignedBeaconBlock, SignedBeaconBlockHeader, SignedBlsToExecutionChange,
|
||||
SignedContributionAndProof, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope,
|
||||
SignedRoot, SignedVoluntaryExit, SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData,
|
||||
Unsigned,
|
||||
};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -299,6 +300,35 @@ where
|
||||
Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message))
|
||||
}
|
||||
|
||||
pub fn indexed_payload_attestation_signature_set<'a, 'b, E, F>(
|
||||
state: &'a BeaconState<E>,
|
||||
get_pubkey: F,
|
||||
signature: &'a AggregateSignature,
|
||||
indexed_payload_attestation: &'b IndexedPayloadAttestation<E>,
|
||||
spec: &'a ChainSpec,
|
||||
) -> Result<SignatureSet<'a>>
|
||||
where
|
||||
E: EthSpec,
|
||||
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
|
||||
{
|
||||
let mut pubkeys = Vec::with_capacity(indexed_payload_attestation.attesting_indices.len());
|
||||
for &validator_idx in indexed_payload_attestation.attesting_indices.iter() {
|
||||
pubkeys.push(
|
||||
get_pubkey(validator_idx as usize).ok_or(Error::ValidatorUnknown(validator_idx))?,
|
||||
);
|
||||
}
|
||||
|
||||
let domain = spec.compute_domain(
|
||||
Domain::PTCAttester,
|
||||
spec.genesis_fork_version,
|
||||
state.genesis_validators_root(),
|
||||
);
|
||||
|
||||
let message = indexed_payload_attestation.data.signing_root(domain);
|
||||
|
||||
Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message))
|
||||
}
|
||||
|
||||
/// Returns the signature set for the given `indexed_attestation` but pubkeys are supplied directly
|
||||
/// instead of from the state.
|
||||
pub fn indexed_attestation_signature_set_from_pubkeys<'a, 'b, E, F>(
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
use super::VerifySignatures;
|
||||
use super::errors::{BlockOperationError, PayloadAttestationInvalid as Invalid};
|
||||
use crate::ConsensusContext;
|
||||
use crate::per_block_processing::is_valid_indexed_payload_attestation;
|
||||
use safe_arith::SafeArith;
|
||||
use types::*;
|
||||
|
||||
pub fn verify_payload_attestation<'ctxt, E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
payload_attestation: &'ctxt PayloadAttestation<E>,
|
||||
ctxt: &'ctxt mut ConsensusContext<E>,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockOperationError<Invalid>> {
|
||||
let data = &payload_attestation.data;
|
||||
|
||||
// Check that the attestation is for the parent beacon block
|
||||
verify!(
|
||||
data.beacon_block_root == state.latest_block_header().parent_root,
|
||||
Invalid::BlockRootMismatch {
|
||||
expected: state.latest_block_header().parent_root,
|
||||
found: data.beacon_block_root,
|
||||
}
|
||||
);
|
||||
|
||||
// Check that the attestation is for the previous slot
|
||||
verify!(
|
||||
data.slot.safe_add(1)? == state.slot(),
|
||||
Invalid::SlotMismatch {
|
||||
expected: state.slot().saturating_sub(Slot::new(1)),
|
||||
found: data.slot,
|
||||
}
|
||||
);
|
||||
|
||||
let indexed_payload_attestation =
|
||||
ctxt.get_indexed_payload_attestation(state, data.slot, payload_attestation, spec)?;
|
||||
|
||||
is_valid_indexed_payload_attestation(
|
||||
state,
|
||||
indexed_payload_attestation,
|
||||
verify_signatures,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -188,6 +188,8 @@ pub enum Error {
|
||||
ProposerLookaheadOutOfBounds {
|
||||
i: usize,
|
||||
},
|
||||
InvalidIndicesCount,
|
||||
PleaseNotifyTheDevs(String),
|
||||
}
|
||||
|
||||
/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
|
||||
@@ -1088,13 +1090,22 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
}
|
||||
}
|
||||
|
||||
let gloas_enabled = self.fork_name_unchecked().gloas_enabled();
|
||||
epoch
|
||||
.slot_iter(E::slots_per_epoch())
|
||||
.map(|slot| {
|
||||
let mut preimage = seed.to_vec();
|
||||
preimage.append(&mut int_to_bytes8(slot.as_u64()));
|
||||
let seed = hash(&preimage);
|
||||
self.compute_proposer_index(indices, &seed, spec)
|
||||
|
||||
if gloas_enabled {
|
||||
self.compute_balance_weighted_selection(indices, &seed, 1, true, spec)?
|
||||
.first()
|
||||
.copied()
|
||||
.ok_or(Error::InsufficientValidators)
|
||||
} else {
|
||||
self.compute_proposer_index(indices, &seed, spec)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -1336,39 +1347,50 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
let epoch = self.current_epoch().safe_add(1)?;
|
||||
|
||||
let active_validator_indices = self.get_active_validator_indices(epoch, spec)?;
|
||||
let active_validator_count = active_validator_indices.len();
|
||||
|
||||
let seed = self.get_seed(epoch, Domain::SyncCommittee, spec)?;
|
||||
let max_effective_balance = spec.max_effective_balance_for_fork(self.fork_name_unchecked());
|
||||
let max_random_value = if self.fork_name_unchecked().electra_enabled() {
|
||||
MAX_RANDOM_VALUE
|
||||
} else {
|
||||
MAX_RANDOM_BYTE
|
||||
};
|
||||
|
||||
let mut i = 0;
|
||||
let mut sync_committee_indices = Vec::with_capacity(E::SyncCommitteeSize::to_usize());
|
||||
while sync_committee_indices.len() < E::SyncCommitteeSize::to_usize() {
|
||||
let shuffled_index = compute_shuffled_index(
|
||||
i.safe_rem(active_validator_count)?,
|
||||
active_validator_count,
|
||||
if self.fork_name_unchecked().gloas_enabled() {
|
||||
self.compute_balance_weighted_selection(
|
||||
&active_validator_indices,
|
||||
seed.as_slice(),
|
||||
spec.shuffle_round_count,
|
||||
E::SyncCommitteeSize::to_usize(),
|
||||
true,
|
||||
spec,
|
||||
)
|
||||
.ok_or(Error::UnableToShuffle)?;
|
||||
let candidate_index = *active_validator_indices
|
||||
.get(shuffled_index)
|
||||
.ok_or(Error::ShuffleIndexOutOfBounds(shuffled_index))?;
|
||||
let random_value = self.shuffling_random_value(i, seed.as_slice())?;
|
||||
let effective_balance = self.get_validator(candidate_index)?.effective_balance;
|
||||
if effective_balance.safe_mul(max_random_value)?
|
||||
>= max_effective_balance.safe_mul(random_value)?
|
||||
{
|
||||
sync_committee_indices.push(candidate_index);
|
||||
} else {
|
||||
let active_validator_count = active_validator_indices.len();
|
||||
let max_effective_balance =
|
||||
spec.max_effective_balance_for_fork(self.fork_name_unchecked());
|
||||
let max_random_value = if self.fork_name_unchecked().electra_enabled() {
|
||||
MAX_RANDOM_VALUE
|
||||
} else {
|
||||
MAX_RANDOM_BYTE
|
||||
};
|
||||
|
||||
let mut i = 0;
|
||||
let mut sync_committee_indices = Vec::with_capacity(E::SyncCommitteeSize::to_usize());
|
||||
while sync_committee_indices.len() < E::SyncCommitteeSize::to_usize() {
|
||||
let shuffled_index = compute_shuffled_index(
|
||||
i.safe_rem(active_validator_count)?,
|
||||
active_validator_count,
|
||||
seed.as_slice(),
|
||||
spec.shuffle_round_count,
|
||||
)
|
||||
.ok_or(Error::UnableToShuffle)?;
|
||||
let candidate_index = *active_validator_indices
|
||||
.get(shuffled_index)
|
||||
.ok_or(Error::ShuffleIndexOutOfBounds(shuffled_index))?;
|
||||
let random_value = self.shuffling_random_value(i, seed.as_slice())?;
|
||||
let effective_balance = self.get_validator(candidate_index)?.effective_balance;
|
||||
if effective_balance.safe_mul(max_random_value)?
|
||||
>= max_effective_balance.safe_mul(random_value)?
|
||||
{
|
||||
sync_committee_indices.push(candidate_index);
|
||||
}
|
||||
i.safe_add_assign(1)?;
|
||||
}
|
||||
i.safe_add_assign(1)?;
|
||||
Ok(sync_committee_indices)
|
||||
}
|
||||
Ok(sync_committee_indices)
|
||||
}
|
||||
|
||||
/// Compute the next sync committee.
|
||||
@@ -2776,6 +2798,114 @@ impl<E: EthSpec> BeaconState<E> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the PTC
|
||||
/// Requires the committee cache to be initialized.
|
||||
/// TODO(EIP-7732): definitely gonna have to cache this..
|
||||
pub fn get_ptc(&self, slot: Slot, spec: &ChainSpec) -> Result<PTC<E>, Error> {
|
||||
let committee_cache = self.committee_cache_at_slot(slot)?;
|
||||
let committees = committee_cache.get_beacon_committees_at_slot(slot)?;
|
||||
|
||||
let seed = self.get_ptc_attester_seed(slot, spec)?;
|
||||
|
||||
let committee_indices: Vec<usize> = committees
|
||||
.iter()
|
||||
.flat_map(|committee| committee.committee.iter().copied())
|
||||
.collect();
|
||||
let selected_indices = self.compute_balance_weighted_selection(
|
||||
&committee_indices,
|
||||
&seed,
|
||||
E::ptc_size(),
|
||||
false,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
Ok(PTC(FixedVector::new(selected_indices)?))
|
||||
}
|
||||
|
||||
/// Compute the seed to use for the ptc attester selection at the given `slot`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn get_ptc_attester_seed(&self, slot: Slot, spec: &ChainSpec) -> Result<Vec<u8>, Error> {
|
||||
let epoch = slot.epoch(E::slots_per_epoch());
|
||||
let mut preimage = self
|
||||
.get_seed(epoch, Domain::PTCAttester, spec)?
|
||||
.as_slice()
|
||||
.to_vec();
|
||||
preimage.append(&mut int_to_bytes8(slot.as_u64()));
|
||||
Ok(hash(&preimage))
|
||||
}
|
||||
|
||||
/// Return size indices sampled by effective balance, using indices as candidates.
|
||||
///
|
||||
/// If shuffle_indices is True, candidate indices are themselves sampled from indices
|
||||
/// by shuffling it, otherwise indices is traversed in order.
|
||||
fn compute_balance_weighted_selection(
|
||||
&self,
|
||||
indices: &[usize],
|
||||
seed: &[u8],
|
||||
size: usize,
|
||||
shuffle_indices: bool,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<usize>, Error> {
|
||||
let total = indices.len();
|
||||
if total == 0 {
|
||||
return Err(Error::InvalidIndicesCount);
|
||||
}
|
||||
|
||||
let mut selected = Vec::with_capacity(size);
|
||||
let mut count = 0usize;
|
||||
|
||||
while selected.len() < size {
|
||||
let mut next_index = count.safe_rem(total)?;
|
||||
|
||||
if shuffle_indices {
|
||||
next_index =
|
||||
compute_shuffled_index(next_index, total, seed, spec.shuffle_round_count)
|
||||
.ok_or(Error::UnableToShuffle)?;
|
||||
}
|
||||
|
||||
let candidate_index = indices.get(next_index).ok_or(Error::InvalidIndicesCount)?;
|
||||
|
||||
if self.compute_balance_weighted_acceptance(*candidate_index, seed, count, spec)? {
|
||||
selected.push(*candidate_index);
|
||||
}
|
||||
|
||||
count.safe_add_assign(1)?;
|
||||
}
|
||||
|
||||
Ok(selected)
|
||||
}
|
||||
|
||||
/// Return whether to accept the selection of the validator `index`, with probability
|
||||
/// proportional to its `effective_balance`, and randomness given by `seed` and `iteration`.
|
||||
fn compute_balance_weighted_acceptance(
|
||||
&self,
|
||||
index: usize,
|
||||
seed: &[u8],
|
||||
iteration: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<bool, Error> {
|
||||
// TODO(EIP-7732): Consider grabbing effective balances from the epoch cache here.
|
||||
// Note that this function will be used in a loop, so using cached values could be nice for performance.
|
||||
// However, post-gloas, this function will be used in `compute_proposer_indices`, `get_next_sync_committee_indices`, and `get_ptc`, which has ~15 call sites in total
|
||||
// so we will need to check each one to ensure epoch cache is initialized first, if we deem a good idea.
|
||||
// Currently, we can't test if making the change would work since the test suite is not ready for gloas.
|
||||
let effective_balance = self.get_effective_balance(index)?;
|
||||
let max_effective_balance = spec.max_effective_balance_for_fork(self.fork_name_unchecked());
|
||||
|
||||
let random_value = self.shuffling_random_value(iteration, seed)?;
|
||||
|
||||
// this codepath should technically never be hit pre-gloas, but added this defensively
|
||||
let max_random_value = if self.fork_name_unchecked().electra_enabled() {
|
||||
MAX_RANDOM_VALUE
|
||||
} else {
|
||||
MAX_RANDOM_BYTE
|
||||
};
|
||||
|
||||
Ok(effective_balance.safe_mul(max_random_value)?
|
||||
>= max_effective_balance.safe_mul(random_value)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> ForkVersionDecode for BeaconState<E> {
|
||||
|
||||
@@ -53,6 +53,16 @@ async fn build_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
|
||||
.head_beacon_state_cloned()
|
||||
}
|
||||
|
||||
/// TODO(EIP-7732): Add tests for PTC (Payload Timeliness Committee) functions:
|
||||
/// - get_ptc: Test committee selection, size, balance-weighted selection
|
||||
/// - get_ptc_attester_seed: Test seed generation and determinism
|
||||
/// - compute_balance_weighted_selection: Test selection algorithm with various balances
|
||||
/// - compute_balance_weighted_acceptance: Test acceptance probability
|
||||
/// These tests require being able to build Gloas states with initialized committee caches,
|
||||
/// which currently fails due to incomplete Gloas block structure as mentioned here:
|
||||
/// https://github.com/sigp/lighthouse/pull/8273
|
||||
/// Similar to existing committee_consistency_test suite for get_beacon_committee.
|
||||
|
||||
async fn test_beacon_proposer_index<E: EthSpec>() {
|
||||
let spec = E::default_spec();
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ pub mod pending_deposit;
|
||||
pub mod pending_partial_withdrawal;
|
||||
pub mod proposer_preparation_data;
|
||||
pub mod proposer_slashing;
|
||||
pub mod ptc;
|
||||
pub mod relative_epoch;
|
||||
pub mod selection_proof;
|
||||
pub mod shuffling_id;
|
||||
@@ -249,6 +250,7 @@ pub use crate::preset::{
|
||||
};
|
||||
pub use crate::proposer_preparation_data::ProposerPreparationData;
|
||||
pub use crate::proposer_slashing::ProposerSlashing;
|
||||
pub use crate::ptc::PTC;
|
||||
pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch};
|
||||
pub use crate::runtime_fixed_vector::RuntimeFixedVector;
|
||||
pub use crate::runtime_var_list::RuntimeVariableList;
|
||||
|
||||
23
consensus/types/src/ptc.rs
Normal file
23
consensus/types/src/ptc.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::*;
|
||||
|
||||
/// TODO(EIP-7732): is it easier to return u64 or usize?
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PTC<E: EthSpec>(pub FixedVector<usize, E::PTCSize>);
|
||||
|
||||
impl<'a, E: EthSpec> IntoIterator for &'a PTC<E> {
|
||||
type Item = &'a usize;
|
||||
type IntoIter = std::slice::Iter<'a, usize>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> IntoIterator for PTC<E> {
|
||||
type Item = usize;
|
||||
type IntoIter = std::vec::IntoIter<usize>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user