mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-11 18:04:18 +00:00
[Altair] Sync committee pools (#2321)
Add pools supporting sync committees: - naive sync aggregation pool - observed sync contributions pool - observed sync contributors pool - observed sync aggregators pool Add SSZ types and tests related to sync committee signatures. Co-authored-by: Michael Sproul <michael@sigmaprime.io> Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
use super::{
|
||||
AggregateSignature, AttestationData, BitList, ChainSpec, Domain, EthSpec, Fork, SecretKey,
|
||||
SignedRoot,
|
||||
};
|
||||
use crate::{test_utils::TestRandom, Hash256};
|
||||
use safe_arith::ArithError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
use crate::slot_data::SlotData;
|
||||
use crate::{test_utils::TestRandom, Hash256, Slot};
|
||||
|
||||
use super::{
|
||||
AggregateSignature, AttestationData, BitList, ChainSpec, Domain, EthSpec, Fork, SecretKey,
|
||||
SignedRoot,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
SszTypesError(ssz_types::Error),
|
||||
@@ -84,10 +87,17 @@ impl<T: EthSpec> Attestation<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SlotData for Attestation<T> {
|
||||
fn get_slot(&self) -> Slot {
|
||||
self.data.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(Attestation<MainnetEthSpec>);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{Checkpoint, Hash256, SignedRoot, Slot};
|
||||
|
||||
use crate::slot_data::SlotData;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
@@ -39,6 +40,12 @@ pub struct AttestationData {
|
||||
|
||||
impl SignedRoot for AttestationData {}
|
||||
|
||||
impl SlotData for AttestationData {
|
||||
fn get_slot(&self) -> Slot {
|
||||
self.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -15,7 +15,7 @@ use ssz_derive::{Decode, Encode};
|
||||
use ssz_types::{typenum::Unsigned, BitVector, FixedVector};
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryInto;
|
||||
use std::{fmt, mem};
|
||||
use std::{fmt, mem, sync::Arc};
|
||||
use superstruct::superstruct;
|
||||
use swap_or_not_shuffle::compute_shuffled_index;
|
||||
use test_random_derive::TestRandom;
|
||||
@@ -110,6 +110,10 @@ pub enum Error {
|
||||
ArithError(ArithError),
|
||||
MissingBeaconBlock(SignedBeaconBlockHash),
|
||||
MissingBeaconState(BeaconStateHash),
|
||||
SyncCommitteeNotKnown {
|
||||
current_epoch: Epoch,
|
||||
epoch: Epoch,
|
||||
},
|
||||
}
|
||||
|
||||
/// Control whether an epoch-indexed field can be indexed at the next epoch or not.
|
||||
@@ -255,9 +259,9 @@ where
|
||||
|
||||
// Light-client sync committees
|
||||
#[superstruct(only(Altair))]
|
||||
pub current_sync_committee: SyncCommittee<T>,
|
||||
pub current_sync_committee: Arc<SyncCommittee<T>>,
|
||||
#[superstruct(only(Altair))]
|
||||
pub next_sync_committee: SyncCommittee<T>,
|
||||
pub next_sync_committee: Arc<SyncCommittee<T>>,
|
||||
|
||||
// Caching (not in the spec)
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
@@ -730,6 +734,28 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
Ok(hash(&preimage))
|
||||
}
|
||||
|
||||
/// Get the already-built current or next sync committee from the state.
|
||||
pub fn get_built_sync_committee(
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<&Arc<SyncCommittee<T>>, Error> {
|
||||
let sync_committee_period = epoch.sync_committee_period(spec)?;
|
||||
let current_sync_committee_period = self.current_epoch().sync_committee_period(spec)?;
|
||||
let next_sync_committee_period = current_sync_committee_period.safe_add(1)?;
|
||||
|
||||
if sync_committee_period == current_sync_committee_period {
|
||||
self.current_sync_committee()
|
||||
} else if sync_committee_period == next_sync_committee_period {
|
||||
self.next_sync_committee()
|
||||
} else {
|
||||
Err(Error::SyncCommitteeNotKnown {
|
||||
current_epoch: self.current_epoch(),
|
||||
epoch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the validator indices of all validators from `sync_committee`.
|
||||
pub fn get_sync_committee_indices(
|
||||
&mut self,
|
||||
@@ -1514,6 +1540,28 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
(self.previous_epoch() - self.finalized_checkpoint().epoch)
|
||||
> spec.min_epochs_to_inactivity_penalty
|
||||
}
|
||||
|
||||
/// Get the `SyncCommittee` associated with the next slot. Useful because sync committees
|
||||
/// assigned to `slot` sign for `slot - 1`. This creates the exceptional logic below when
|
||||
/// transitioning between sync committee periods.
|
||||
pub fn get_sync_committee_for_next_slot(
|
||||
&self,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Arc<SyncCommittee<T>>, Error> {
|
||||
let next_slot_epoch = self
|
||||
.slot()
|
||||
.saturating_add(Slot::new(1))
|
||||
.epoch(T::slots_per_epoch());
|
||||
|
||||
let sync_committee = if self.current_epoch().sync_committee_period(spec)
|
||||
== next_slot_epoch.sync_committee_period(spec)
|
||||
{
|
||||
self.current_sync_committee()?.clone()
|
||||
} else {
|
||||
self.next_sync_committee()?.clone()
|
||||
};
|
||||
Ok(sync_committee)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RelativeEpochError> for Error {
|
||||
|
||||
@@ -17,6 +17,8 @@ pub enum Domain {
|
||||
SelectionProof,
|
||||
AggregateAndProof,
|
||||
SyncCommittee,
|
||||
ContributionAndProof,
|
||||
SyncCommitteeSelectionProof,
|
||||
}
|
||||
|
||||
/// Lighthouse's internal configuration struct.
|
||||
@@ -181,6 +183,37 @@ impl ChainSpec {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the fork version for a named fork.
|
||||
pub fn fork_version_for_name(&self, fork_name: ForkName) -> [u8; 4] {
|
||||
match fork_name {
|
||||
ForkName::Base => self.genesis_fork_version,
|
||||
ForkName::Altair => self.altair_fork_version,
|
||||
}
|
||||
}
|
||||
|
||||
/// For a given fork name, return the epoch at which it activates.
|
||||
pub fn fork_epoch(&self, fork_name: ForkName) -> Option<Epoch> {
|
||||
match fork_name {
|
||||
ForkName::Base => Some(Epoch::new(0)),
|
||||
ForkName::Altair => self.altair_fork_epoch,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a full `Fork` struct for a given epoch.
|
||||
pub fn fork_at_epoch(&self, epoch: Epoch) -> Fork {
|
||||
let current_fork_name = self.fork_name_at_epoch(epoch);
|
||||
let previous_fork_name = current_fork_name.previous_fork().unwrap_or(ForkName::Base);
|
||||
let epoch = self
|
||||
.fork_epoch(current_fork_name)
|
||||
.unwrap_or_else(|| Epoch::new(0));
|
||||
|
||||
Fork {
|
||||
previous_version: self.fork_version_for_name(previous_fork_name),
|
||||
current_version: self.fork_version_for_name(current_fork_name),
|
||||
epoch,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the domain number, unmodified by the fork.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
@@ -194,6 +227,8 @@ impl ChainSpec {
|
||||
Domain::SelectionProof => self.domain_selection_proof,
|
||||
Domain::AggregateAndProof => self.domain_aggregate_and_proof,
|
||||
Domain::SyncCommittee => self.domain_sync_committee,
|
||||
Domain::ContributionAndProof => self.domain_contribution_and_proof,
|
||||
Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,6 +710,18 @@ mod tests {
|
||||
);
|
||||
test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec);
|
||||
}
|
||||
|
||||
// Test that `fork_name_at_epoch` and `fork_epoch` are consistent.
|
||||
#[test]
|
||||
fn fork_name_at_epoch_consistency() {
|
||||
let spec = ChainSpec::mainnet();
|
||||
|
||||
for fork_name in ForkName::list_all() {
|
||||
if let Some(fork_epoch) = spec.fork_epoch(fork_name) {
|
||||
assert_eq!(spec.fork_name_at_epoch(fork_epoch), fork_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -8,6 +8,8 @@ pub mod altair {
|
||||
pub const SYNC_REWARD_WEIGHT: u64 = 2;
|
||||
pub const PROPOSER_WEIGHT: u64 = 8;
|
||||
pub const WEIGHT_DENOMINATOR: u64 = 64;
|
||||
pub const SYNC_COMMITTEE_SUBNET_COUNT: u64 = 4;
|
||||
pub const TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: u64 = 4;
|
||||
|
||||
pub const PARTICIPATION_FLAG_WEIGHTS: [u64; NUM_FLAG_INDICES] = [
|
||||
TIMELY_SOURCE_WEIGHT,
|
||||
|
||||
61
consensus/types/src/contribution_and_proof.rs
Normal file
61
consensus/types/src/contribution_and_proof.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use super::{
|
||||
ChainSpec, EthSpec, Fork, Hash256, SecretKey, Signature, SignedRoot, SyncCommitteeContribution,
|
||||
SyncSelectionProof,
|
||||
};
|
||||
use crate::test_utils::TestRandom;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// A Validators aggregate sync committee contribution and selection proof.
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct ContributionAndProof<T: EthSpec> {
|
||||
/// The index of the validator that created the sync contribution.
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub aggregator_index: u64,
|
||||
/// The aggregate contribution.
|
||||
pub contribution: SyncCommitteeContribution<T>,
|
||||
/// A proof provided by the validator that permits them to publish on the
|
||||
/// `sync_committee_contribution_and_proof` gossipsub topic.
|
||||
pub selection_proof: Signature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ContributionAndProof<T> {
|
||||
/// Produces a new `ContributionAndProof` with a `selection_proof` generated by signing
|
||||
/// `SyncAggregatorSelectionData` with `secret_key`.
|
||||
///
|
||||
/// If `selection_proof.is_none()` it will be computed locally.
|
||||
pub fn from_aggregate(
|
||||
aggregator_index: u64,
|
||||
contribution: SyncCommitteeContribution<T>,
|
||||
selection_proof: Option<SyncSelectionProof>,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let selection_proof = selection_proof
|
||||
.unwrap_or_else(|| {
|
||||
SyncSelectionProof::new::<T>(
|
||||
contribution.slot,
|
||||
contribution.subcommittee_index,
|
||||
secret_key,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
)
|
||||
})
|
||||
.into();
|
||||
|
||||
Self {
|
||||
aggregator_index,
|
||||
contribution,
|
||||
selection_proof,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SignedRoot for ContributionAndProof<T> {}
|
||||
@@ -91,6 +91,10 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
|
||||
///
|
||||
/// Must be set to `EpochsPerEth1VotingPeriod * SlotsPerEpoch`
|
||||
type SlotsPerEth1VotingPeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
/// The size of `sync_subcommittees`.
|
||||
///
|
||||
/// Must be set to `SyncCommitteeSize / SyncCommitteeSubnetCount`.
|
||||
type SyncSubcommitteeSize: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
|
||||
fn default_spec() -> ChainSpec;
|
||||
|
||||
@@ -171,6 +175,16 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq +
|
||||
fn slots_per_eth1_voting_period() -> usize {
|
||||
Self::SlotsPerEth1VotingPeriod::to_usize()
|
||||
}
|
||||
|
||||
/// Returns the `SYNC_COMMITTEE_SIZE` constant for this specification.
|
||||
fn sync_committee_size() -> usize {
|
||||
Self::SyncCommitteeSize::to_usize()
|
||||
}
|
||||
|
||||
/// Returns the `SYNC_COMMITTEE_SIZE / SyncCommitteeSubnetCount`.
|
||||
fn sync_subcommittee_size() -> usize {
|
||||
Self::SyncSubcommitteeSize::to_usize()
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro to inherit some type values from another EthSpec.
|
||||
@@ -204,6 +218,7 @@ impl EthSpec for MainnetEthSpec {
|
||||
type MaxDeposits = U16;
|
||||
type MaxVoluntaryExits = U16;
|
||||
type SyncCommitteeSize = U512;
|
||||
type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count
|
||||
type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch
|
||||
type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch
|
||||
|
||||
@@ -228,6 +243,7 @@ impl EthSpec for MinimalEthSpec {
|
||||
type EpochsPerHistoricalVector = U64;
|
||||
type EpochsPerSlashingsVector = U64;
|
||||
type SyncCommitteeSize = U32;
|
||||
type SyncSubcommitteeSize = U8; // 32 committee size / 4 sync committee subnet count
|
||||
type MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch
|
||||
type SlotsPerEth1VotingPeriod = U32; // 4 epochs * 8 slots per epoch
|
||||
|
||||
|
||||
@@ -26,6 +26,26 @@ impl ForkName {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the name of the fork immediately prior to the current one.
|
||||
///
|
||||
/// If `self` is `ForkName::Base` then `Base` is returned.
|
||||
pub fn previous_fork(self) -> Option<ForkName> {
|
||||
match self {
|
||||
ForkName::Base => None,
|
||||
ForkName::Altair => Some(ForkName::Base),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the name of the fork immediately after the current one.
|
||||
///
|
||||
/// If `self` is the last known fork and has no successor, `None` is returned.
|
||||
pub fn next_fork(self) -> Option<ForkName> {
|
||||
match self {
|
||||
ForkName::Base => Some(ForkName::Altair),
|
||||
ForkName::Altair => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ForkName {
|
||||
@@ -45,3 +65,20 @@ pub struct InconsistentFork {
|
||||
pub fork_at_slot: ForkName,
|
||||
pub object_fork: ForkName,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use itertools::Itertools;
|
||||
|
||||
#[test]
|
||||
fn previous_and_next_fork_consistent() {
|
||||
assert_eq!(ForkName::Altair.next_fork(), None);
|
||||
assert_eq!(ForkName::Base.previous_fork(), None);
|
||||
|
||||
for (prev_fork, fork) in ForkName::list_all().into_iter().tuple_windows() {
|
||||
assert_eq!(prev_fork.next_fork(), Some(fork));
|
||||
assert_eq!(fork.previous_fork(), Some(prev_fork));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ pub mod beacon_state;
|
||||
pub mod chain_spec;
|
||||
pub mod checkpoint;
|
||||
pub mod consts;
|
||||
pub mod contribution_and_proof;
|
||||
pub mod deposit;
|
||||
pub mod deposit_data;
|
||||
pub mod deposit_message;
|
||||
@@ -51,6 +52,7 @@ pub mod shuffling_id;
|
||||
pub mod signed_aggregate_and_proof;
|
||||
pub mod signed_beacon_block;
|
||||
pub mod signed_beacon_block_header;
|
||||
pub mod signed_contribution_and_proof;
|
||||
pub mod signed_voluntary_exit;
|
||||
pub mod signing_data;
|
||||
pub mod validator;
|
||||
@@ -64,9 +66,15 @@ pub mod preset;
|
||||
pub mod slot_epoch;
|
||||
pub mod subnet_id;
|
||||
pub mod sync_aggregate;
|
||||
pub mod sync_aggregator_selection_data;
|
||||
pub mod sync_committee;
|
||||
pub mod sync_committee_contribution;
|
||||
pub mod sync_committee_message;
|
||||
pub mod sync_selection_proof;
|
||||
pub mod sync_subnet_id;
|
||||
mod tree_hash_impls;
|
||||
|
||||
pub mod slot_data;
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub mod sqlite;
|
||||
|
||||
@@ -90,6 +98,7 @@ pub use crate::beacon_state::{BeaconTreeHashCache, Error as BeaconStateError, *}
|
||||
pub use crate::chain_spec::{ChainSpec, Config, Domain};
|
||||
pub use crate::checkpoint::Checkpoint;
|
||||
pub use crate::config_and_preset::ConfigAndPreset;
|
||||
pub use crate::contribution_and_proof::ContributionAndProof;
|
||||
pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH};
|
||||
pub use crate::deposit_data::DepositData;
|
||||
pub use crate::deposit_message::DepositMessage;
|
||||
@@ -115,12 +124,18 @@ pub use crate::signed_beacon_block::{
|
||||
SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockHash,
|
||||
};
|
||||
pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader;
|
||||
pub use crate::signed_contribution_and_proof::SignedContributionAndProof;
|
||||
pub use crate::signed_voluntary_exit::SignedVoluntaryExit;
|
||||
pub use crate::signing_data::{SignedRoot, SigningData};
|
||||
pub use crate::slot_epoch::{Epoch, Slot};
|
||||
pub use crate::subnet_id::SubnetId;
|
||||
pub use crate::sync_aggregate::SyncAggregate;
|
||||
pub use crate::sync_aggregator_selection_data::SyncAggregatorSelectionData;
|
||||
pub use crate::sync_committee::SyncCommittee;
|
||||
pub use crate::sync_committee_contribution::SyncCommitteeContribution;
|
||||
pub use crate::sync_committee_message::SyncCommitteeMessage;
|
||||
pub use crate::sync_selection_proof::SyncSelectionProof;
|
||||
pub use crate::sync_subnet_id::SyncSubnetId;
|
||||
pub use crate::validator::Validator;
|
||||
pub use crate::validator_subscription::ValidatorSubscription;
|
||||
pub use crate::voluntary_exit::VoluntaryExit;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
AggregateAndProof, Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey,
|
||||
SecretKey, SelectionProof, Signature, SignedRoot,
|
||||
AggregateAndProof, Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, SecretKey,
|
||||
SelectionProof, Signature, SignedRoot,
|
||||
};
|
||||
use crate::test_utils::TestRandom;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
@@ -60,41 +60,4 @@ impl<T: EthSpec> SignedAggregateAndProof<T> {
|
||||
signature: secret_key.sign(signing_message),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies the signature of the `AggregateAndProof`
|
||||
pub fn is_valid_signature(
|
||||
&self,
|
||||
validator_pubkey: &PublicKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
let target_epoch = self.message.aggregate.data.slot.epoch(T::slots_per_epoch());
|
||||
let domain = spec.get_domain(
|
||||
target_epoch,
|
||||
Domain::AggregateAndProof,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = self.message.signing_root(domain);
|
||||
self.signature.verify(validator_pubkey, message)
|
||||
}
|
||||
|
||||
/// Verifies the signature of the `AggregateAndProof` as well the underlying selection_proof in
|
||||
/// the contained `AggregateAndProof`.
|
||||
pub fn is_valid(
|
||||
&self,
|
||||
validator_pubkey: &PublicKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
self.is_valid_signature(validator_pubkey, fork, genesis_validators_root, spec)
|
||||
&& self.message.is_valid_selection_proof(
|
||||
validator_pubkey,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
61
consensus/types/src/signed_contribution_and_proof.rs
Normal file
61
consensus/types/src/signed_contribution_and_proof.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use super::{
|
||||
ChainSpec, ContributionAndProof, Domain, EthSpec, Fork, Hash256, SecretKey, Signature,
|
||||
SignedRoot, SyncCommitteeContribution, SyncSelectionProof,
|
||||
};
|
||||
use crate::test_utils::TestRandom;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// A Validators signed contribution proof to publish on the `sync_committee_contribution_and_proof`
|
||||
/// gossipsub topic.
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct SignedContributionAndProof<T: EthSpec> {
|
||||
/// The `ContributionAndProof` that was signed.
|
||||
pub message: ContributionAndProof<T>,
|
||||
/// The validator's signature of `message`.
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SignedContributionAndProof<T> {
|
||||
/// Produces a new `SignedContributionAndProof` with a `selection_proof` generated by signing
|
||||
/// `aggregate.data.slot` with `secret_key`.
|
||||
///
|
||||
/// If `selection_proof.is_none()` it will be computed locally.
|
||||
pub fn from_aggregate(
|
||||
aggregator_index: u64,
|
||||
contribution: SyncCommitteeContribution<T>,
|
||||
selection_proof: Option<SyncSelectionProof>,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let message = ContributionAndProof::from_aggregate(
|
||||
aggregator_index,
|
||||
contribution,
|
||||
selection_proof,
|
||||
secret_key,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
let epoch = message.contribution.slot.epoch(T::slots_per_epoch());
|
||||
let domain = spec.get_domain(
|
||||
epoch,
|
||||
Domain::ContributionAndProof,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let signing_message = message.signing_root(domain);
|
||||
|
||||
SignedContributionAndProof {
|
||||
message,
|
||||
signature: secret_key.sign(signing_message),
|
||||
}
|
||||
}
|
||||
}
|
||||
13
consensus/types/src/slot_data.rs
Normal file
13
consensus/types/src/slot_data.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use crate::Slot;
|
||||
|
||||
/// A trait providing a `Slot` getter for messages that are related to a single slot. Useful in
|
||||
/// making parts of attestation and sync committee processing generic.
|
||||
pub trait SlotData {
|
||||
fn get_slot(&self) -> Slot;
|
||||
}
|
||||
|
||||
impl SlotData for Slot {
|
||||
fn get_slot(&self) -> Slot {
|
||||
*self
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,10 @@
|
||||
//! may lead to programming errors which are not detected by the compiler.
|
||||
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::SignedRoot;
|
||||
use crate::{ChainSpec, SignedRoot};
|
||||
|
||||
use rand::RngCore;
|
||||
use safe_arith::SafeArith;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{ssz_encode, Decode, DecodeError, Encode};
|
||||
use std::fmt;
|
||||
@@ -90,6 +90,13 @@ impl Epoch {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the sync committee period for an epoch.
|
||||
pub fn sync_committee_period(&self, spec: &ChainSpec) -> Result<u64, ArithError> {
|
||||
Ok(self
|
||||
.safe_div(spec.epochs_per_sync_committee_period)?
|
||||
.as_u64())
|
||||
}
|
||||
|
||||
pub fn slot_iter(&self, slots_per_epoch: u64) -> SlotIter {
|
||||
SlotIter {
|
||||
current_iteration: 0,
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
use crate::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT;
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{AggregateSignature, BitVector, EthSpec};
|
||||
use crate::{AggregateSignature, BitVector, EthSpec, SyncCommitteeContribution};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
SszTypesError(ssz_types::Error),
|
||||
ArithError(ArithError),
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
fn from(e: ArithError) -> Error {
|
||||
Error::ArithError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
@@ -23,6 +37,34 @@ impl<T: EthSpec> SyncAggregate<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `SyncAggregate` from a slice of `SyncCommitteeContribution`s.
|
||||
///
|
||||
/// Equivalent to `process_sync_committee_contributions` from the spec.
|
||||
pub fn from_contributions(
|
||||
contributions: &[SyncCommitteeContribution<T>],
|
||||
) -> Result<SyncAggregate<T>, Error> {
|
||||
let mut sync_aggregate = Self::new();
|
||||
let sync_subcommittee_size =
|
||||
T::sync_committee_size().safe_div(SYNC_COMMITTEE_SUBNET_COUNT as usize)?;
|
||||
for contribution in contributions {
|
||||
for (index, participated) in contribution.aggregation_bits.iter().enumerate() {
|
||||
if participated {
|
||||
let participant_index = sync_subcommittee_size
|
||||
.safe_mul(contribution.subcommittee_index as usize)?
|
||||
.safe_add(index)?;
|
||||
sync_aggregate
|
||||
.sync_committee_bits
|
||||
.set(participant_index, true)
|
||||
.map_err(Error::SszTypesError)?;
|
||||
}
|
||||
}
|
||||
sync_aggregate
|
||||
.sync_committee_signature
|
||||
.add_assign_aggregate(&contribution.signature);
|
||||
}
|
||||
Ok(sync_aggregate)
|
||||
}
|
||||
|
||||
/// Empty aggregate to be used at genesis.
|
||||
///
|
||||
/// Contains an empty signature and should *not* be used as the starting point for aggregation,
|
||||
|
||||
25
consensus/types/src/sync_aggregator_selection_data.rs
Normal file
25
consensus/types/src/sync_aggregator_selection_data.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{SignedRoot, Slot};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(
|
||||
Debug, PartialEq, Clone, Serialize, Deserialize, Hash, Encode, Decode, TreeHash, TestRandom,
|
||||
)]
|
||||
pub struct SyncAggregatorSelectionData {
|
||||
pub slot: Slot,
|
||||
pub subcommittee_index: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(SyncAggregatorSelectionData);
|
||||
}
|
||||
|
||||
impl SignedRoot for SyncAggregatorSelectionData {}
|
||||
@@ -1,12 +1,30 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::typenum::Unsigned;
|
||||
use crate::{EthSpec, FixedVector};
|
||||
use crate::{EthSpec, FixedVector, SyncSubnetId};
|
||||
use bls::PublicKeyBytes;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::collections::HashMap;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
ArithError(ArithError),
|
||||
InvalidSubcommitteeRange {
|
||||
start_subcommittee_index: usize,
|
||||
end_subcommittee_index: usize,
|
||||
subcommittee_index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
fn from(e: ArithError) -> Error {
|
||||
Error::ArithError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
@@ -26,4 +44,44 @@ impl<T: EthSpec> SyncCommittee<T> {
|
||||
aggregate_pubkey: PublicKeyBytes::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the pubkeys in this `SyncCommittee` for the given `subcommittee_index`.
|
||||
pub fn get_subcommittee_pubkeys(
|
||||
&self,
|
||||
subcommittee_index: usize,
|
||||
) -> Result<Vec<PublicKeyBytes>, Error> {
|
||||
let start_subcommittee_index = subcommittee_index.safe_mul(T::sync_subcommittee_size())?;
|
||||
let end_subcommittee_index =
|
||||
start_subcommittee_index.safe_add(T::sync_subcommittee_size())?;
|
||||
self.pubkeys
|
||||
.get(start_subcommittee_index..end_subcommittee_index)
|
||||
.ok_or(Error::InvalidSubcommitteeRange {
|
||||
start_subcommittee_index,
|
||||
end_subcommittee_index,
|
||||
subcommittee_index,
|
||||
})
|
||||
.map(|s| s.to_vec())
|
||||
}
|
||||
|
||||
/// For a given `pubkey`, finds all subcommittees that it is included in, and maps the
|
||||
/// subcommittee index (typed as `SyncSubnetId`) to all positions this `pubkey` is associated
|
||||
/// with within the subcommittee.
|
||||
pub fn subcommittee_positions_for_public_key(
|
||||
&self,
|
||||
pubkey: &PublicKeyBytes,
|
||||
) -> Result<HashMap<SyncSubnetId, Vec<usize>>, Error> {
|
||||
let mut subnet_positions = HashMap::new();
|
||||
for (committee_index, validator_pubkey) in self.pubkeys.iter().enumerate() {
|
||||
if pubkey == validator_pubkey {
|
||||
let subcommittee_index = committee_index.safe_div(T::sync_subcommittee_size())?;
|
||||
let position_in_subcommittee =
|
||||
committee_index.safe_rem(T::sync_subcommittee_size())?;
|
||||
subnet_positions
|
||||
.entry(SyncSubnetId::new(subcommittee_index as u64))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(position_in_subcommittee);
|
||||
}
|
||||
}
|
||||
Ok(subnet_positions)
|
||||
}
|
||||
}
|
||||
|
||||
113
consensus/types/src/sync_committee_contribution.rs
Normal file
113
consensus/types/src/sync_committee_contribution.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use super::{AggregateSignature, EthSpec, SignedRoot};
|
||||
use crate::slot_data::SlotData;
|
||||
use crate::{test_utils::TestRandom, BitVector, Hash256, Slot, SyncCommitteeMessage};
|
||||
use safe_arith::ArithError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
SszTypesError(ssz_types::Error),
|
||||
AlreadySigned(usize),
|
||||
SubnetCountIsZero(ArithError),
|
||||
}
|
||||
|
||||
/// An aggregation of `SyncCommitteeMessage`s, used in creating a `SignedContributionAndProof`.
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct SyncCommitteeContribution<T: EthSpec> {
|
||||
pub slot: Slot,
|
||||
pub beacon_block_root: Hash256,
|
||||
pub subcommittee_index: u64,
|
||||
pub aggregation_bits: BitVector<T::SyncSubcommitteeSize>,
|
||||
pub signature: AggregateSignature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SyncCommitteeContribution<T> {
|
||||
/// Create a `SyncCommitteeContribution` from:
|
||||
///
|
||||
/// - `message`: A single `SyncCommitteeMessage`.
|
||||
/// - `subcommittee_index`: The subcommittee this contribution pertains to out of the broader
|
||||
/// sync committee. This can be determined from the `SyncSubnetId` of the gossip subnet
|
||||
/// this message was seen on.
|
||||
/// - `validator_sync_committee_index`: The index of the validator **within** the subcommittee.
|
||||
pub fn from_message(
|
||||
message: &SyncCommitteeMessage,
|
||||
subcommittee_index: u64,
|
||||
validator_sync_committee_index: usize,
|
||||
) -> Result<Self, Error> {
|
||||
let mut bits = BitVector::new();
|
||||
bits.set(validator_sync_committee_index, true)
|
||||
.map_err(Error::SszTypesError)?;
|
||||
Ok(Self {
|
||||
slot: message.slot,
|
||||
beacon_block_root: message.beacon_block_root,
|
||||
subcommittee_index,
|
||||
aggregation_bits: bits,
|
||||
signature: AggregateSignature::from(&message.signature),
|
||||
})
|
||||
}
|
||||
|
||||
/// Are the aggregation bitfields of these sync contribution disjoint?
|
||||
pub fn signers_disjoint_from(&self, other: &Self) -> bool {
|
||||
self.aggregation_bits
|
||||
.intersection(&other.aggregation_bits)
|
||||
.is_zero()
|
||||
}
|
||||
|
||||
/// Aggregate another `SyncCommitteeContribution` into this one.
|
||||
///
|
||||
/// The aggregation bitfields must be disjoint, and the data must be the same.
|
||||
pub fn aggregate(&mut self, other: &Self) {
|
||||
debug_assert_eq!(self.slot, other.slot);
|
||||
debug_assert_eq!(self.beacon_block_root, other.beacon_block_root);
|
||||
debug_assert_eq!(self.subcommittee_index, other.subcommittee_index);
|
||||
debug_assert!(self.signers_disjoint_from(other));
|
||||
|
||||
self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits);
|
||||
self.signature.add_assign_aggregate(&other.signature);
|
||||
}
|
||||
}
|
||||
|
||||
impl SignedRoot for Hash256 {}
|
||||
|
||||
/// This is not in the spec, but useful for determining uniqueness of sync committee contributions
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct SyncContributionData {
|
||||
slot: Slot,
|
||||
beacon_block_root: Hash256,
|
||||
subcommittee_index: u64,
|
||||
}
|
||||
|
||||
impl SyncContributionData {
|
||||
pub fn from_contribution<T: EthSpec>(signing_data: &SyncCommitteeContribution<T>) -> Self {
|
||||
Self {
|
||||
slot: signing_data.slot,
|
||||
beacon_block_root: signing_data.beacon_block_root,
|
||||
subcommittee_index: signing_data.subcommittee_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SlotData for SyncCommitteeContribution<T> {
|
||||
fn get_slot(&self) -> Slot {
|
||||
self.slot
|
||||
}
|
||||
}
|
||||
|
||||
impl SlotData for SyncContributionData {
|
||||
fn get_slot(&self) -> Slot {
|
||||
self.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(SyncCommitteeContribution<MainnetEthSpec>);
|
||||
}
|
||||
57
consensus/types/src/sync_committee_message.rs
Normal file
57
consensus/types/src/sync_committee_message.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{ChainSpec, Domain, EthSpec, Fork, Hash256, SecretKey, Signature, SignedRoot, Slot};
|
||||
|
||||
use crate::slot_data::SlotData;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// The data upon which a `SyncCommitteeContribution` is based.
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct SyncCommitteeMessage {
|
||||
pub slot: Slot,
|
||||
pub beacon_block_root: Hash256,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub validator_index: u64,
|
||||
// Signature by the validator over `beacon_block_root`.
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl SyncCommitteeMessage {
|
||||
/// Equivalent to `get_sync_committee_message` from the spec.
|
||||
pub fn new<E: EthSpec>(
|
||||
slot: Slot,
|
||||
beacon_block_root: Hash256,
|
||||
validator_index: u64,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let epoch = slot.epoch(E::slots_per_epoch());
|
||||
let domain = spec.get_domain(epoch, Domain::SyncCommittee, fork, genesis_validators_root);
|
||||
let message = beacon_block_root.signing_root(domain);
|
||||
let signature = secret_key.sign(message);
|
||||
Self {
|
||||
slot,
|
||||
beacon_block_root,
|
||||
validator_index,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SlotData for SyncCommitteeMessage {
|
||||
fn get_slot(&self) -> Slot {
|
||||
self.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(SyncCommitteeMessage);
|
||||
}
|
||||
139
consensus/types/src/sync_selection_proof.rs
Normal file
139
consensus/types/src/sync_selection_proof.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use crate::consts::altair::{
|
||||
SYNC_COMMITTEE_SUBNET_COUNT, TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE,
|
||||
};
|
||||
use crate::{
|
||||
ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, Signature, SignedRoot, Slot,
|
||||
SyncAggregatorSelectionData,
|
||||
};
|
||||
use eth2_hashing::hash;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use ssz::Encode;
|
||||
use ssz_types::typenum::Unsigned;
|
||||
use std::cmp;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct SyncSelectionProof(Signature);
|
||||
|
||||
impl SyncSelectionProof {
|
||||
pub fn new<T: EthSpec>(
|
||||
slot: Slot,
|
||||
subcommittee_index: u64,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let domain = spec.get_domain(
|
||||
slot.epoch(T::slots_per_epoch()),
|
||||
Domain::SyncCommitteeSelectionProof,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = SyncAggregatorSelectionData {
|
||||
slot,
|
||||
subcommittee_index,
|
||||
}
|
||||
.signing_root(domain);
|
||||
|
||||
Self(secret_key.sign(message))
|
||||
}
|
||||
|
||||
/// Returns the "modulo" used for determining if a `SyncSelectionProof` elects an aggregator.
|
||||
pub fn modulo<T: EthSpec>() -> Result<u64, ArithError> {
|
||||
Ok(cmp::max(
|
||||
1,
|
||||
(T::SyncCommitteeSize::to_u64())
|
||||
.safe_div(SYNC_COMMITTEE_SUBNET_COUNT)?
|
||||
.safe_div(TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn is_aggregator<T: EthSpec>(&self) -> Result<bool, ArithError> {
|
||||
self.is_aggregator_from_modulo(Self::modulo::<T>()?)
|
||||
}
|
||||
|
||||
pub fn is_aggregator_from_modulo(&self, modulo: u64) -> Result<bool, ArithError> {
|
||||
let signature_hash = hash(&self.0.as_ssz_bytes());
|
||||
let signature_hash_int = u64::from_le_bytes(
|
||||
signature_hash
|
||||
.get(0..8)
|
||||
.expect("hash is 32 bytes")
|
||||
.try_into()
|
||||
.expect("first 8 bytes of signature should always convert to fixed array"),
|
||||
);
|
||||
|
||||
signature_hash_int.safe_rem(modulo).map(|rem| rem == 0)
|
||||
}
|
||||
|
||||
pub fn verify<T: EthSpec>(
|
||||
&self,
|
||||
slot: Slot,
|
||||
subcommittee_index: u64,
|
||||
pubkey: &PublicKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
let domain = spec.get_domain(
|
||||
slot.epoch(T::slots_per_epoch()),
|
||||
Domain::SyncCommitteeSelectionProof,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = SyncAggregatorSelectionData {
|
||||
slot,
|
||||
subcommittee_index,
|
||||
}
|
||||
.signing_root(domain);
|
||||
|
||||
self.0.verify(pubkey, message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Signature> for SyncSelectionProof {
|
||||
fn into(self) -> Signature {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Signature> for SyncSelectionProof {
|
||||
fn from(sig: Signature) -> Self {
|
||||
Self(sig)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MainnetEthSpec;
|
||||
use eth2_interop_keypairs::keypair;
|
||||
|
||||
#[test]
|
||||
fn proof_sign_and_verify() {
|
||||
let slot = Slot::new(1000);
|
||||
let subcommittee_index = 12;
|
||||
let key = keypair(1);
|
||||
let fork = &Fork::default();
|
||||
let genesis_validators_root = Hash256::zero();
|
||||
let spec = &ChainSpec::mainnet();
|
||||
|
||||
let proof = SyncSelectionProof::new::<MainnetEthSpec>(
|
||||
slot,
|
||||
subcommittee_index,
|
||||
&key.sk,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
assert!(proof.verify::<MainnetEthSpec>(
|
||||
slot,
|
||||
subcommittee_index,
|
||||
&key.pk,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec
|
||||
));
|
||||
}
|
||||
}
|
||||
74
consensus/types/src/sync_subnet_id.rs
Normal file
74
consensus/types/src/sync_subnet_id.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
//! Identifies each sync committee subnet by an integer identifier.
|
||||
use crate::consts::altair::SYNC_COMMITTEE_SUBNET_COUNT;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
lazy_static! {
|
||||
static ref SYNC_SUBNET_ID_TO_STRING: Vec<String> = {
|
||||
let mut v = Vec::with_capacity(SYNC_COMMITTEE_SUBNET_COUNT as usize);
|
||||
|
||||
for i in 0..SYNC_COMMITTEE_SUBNET_COUNT {
|
||||
v.push(i.to_string());
|
||||
}
|
||||
v
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct SyncSubnetId(#[serde(with = "serde_utils::quoted_u64")] u64);
|
||||
|
||||
pub fn sync_subnet_id_to_string(i: u64) -> &'static str {
|
||||
if i < SYNC_COMMITTEE_SUBNET_COUNT {
|
||||
&SYNC_SUBNET_ID_TO_STRING
|
||||
.get(i as usize)
|
||||
.expect("index below SYNC_COMMITTEE_SUBNET_COUNT")
|
||||
} else {
|
||||
"sync subnet id out of range"
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncSubnetId {
|
||||
pub fn new(id: u64) -> Self {
|
||||
id.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SyncSubnetId {
|
||||
type Target = u64;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for SyncSubnetId {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for SyncSubnetId {
|
||||
fn from(x: u64) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u64> for SyncSubnetId {
|
||||
fn into(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u64> for &SyncSubnetId {
|
||||
fn into(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for SyncSubnetId {
|
||||
fn as_ref(&self) -> &str {
|
||||
sync_subnet_id_to_string(self.0)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use rand::RngCore;
|
||||
use rand::SeedableRng;
|
||||
use rand_xorshift::XorShiftRng;
|
||||
use ssz_types::typenum::Unsigned;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod address;
|
||||
mod aggregate_signature;
|
||||
@@ -68,6 +69,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> TestRandom for Arc<U>
|
||||
where
|
||||
U: TestRandom,
|
||||
{
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
Arc::new(U::random_for_test(rng))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> TestRandom for FixedVector<T, N>
|
||||
where
|
||||
T: TestRandom,
|
||||
|
||||
Reference in New Issue
Block a user