mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-18 21:38:31 +00:00
Directory Restructure (#1163)
* Move tests -> testing * Directory restructure * Update Cargo.toml during restructure * Update Makefile during restructure * Fix arbitrary path
This commit is contained in:
81
consensus/types/src/aggregate_and_proof.rs
Normal file
81
consensus/types/src/aggregate_and_proof.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use super::{
|
||||
Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, SelectionProof,
|
||||
Signature, SignedRoot,
|
||||
};
|
||||
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 attestation and selection proof.
|
||||
///
|
||||
/// Spec v0.10.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct AggregateAndProof<T: EthSpec> {
|
||||
/// The index of the validator that created the attestation.
|
||||
pub aggregator_index: u64,
|
||||
/// The aggregate attestation.
|
||||
pub aggregate: Attestation<T>,
|
||||
/// A proof provided by the validator that permits them to publish on the
|
||||
/// `beacon_aggregate_and_proof` gossipsub topic.
|
||||
pub selection_proof: Signature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> AggregateAndProof<T> {
|
||||
/// Produces a new `AggregateAndProof` 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,
|
||||
aggregate: Attestation<T>,
|
||||
selection_proof: Option<SelectionProof>,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let selection_proof = selection_proof
|
||||
.unwrap_or_else(|| {
|
||||
SelectionProof::new::<T>(
|
||||
aggregate.data.slot,
|
||||
secret_key,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
)
|
||||
})
|
||||
.into();
|
||||
|
||||
Self {
|
||||
aggregator_index,
|
||||
aggregate,
|
||||
selection_proof,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `validator_pubkey` signed over `self.aggregate.data.slot`.
|
||||
pub fn is_valid_selection_proof(
|
||||
&self,
|
||||
validator_pubkey: &PublicKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
let target_epoch = self.aggregate.data.slot.epoch(T::slots_per_epoch());
|
||||
let domain = spec.get_domain(
|
||||
target_epoch,
|
||||
Domain::SelectionProof,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = self.aggregate.data.slot.signing_root(domain);
|
||||
self.selection_proof
|
||||
.verify(message.as_bytes(), validator_pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SignedRoot for AggregateAndProof<T> {}
|
||||
107
consensus/types/src/attestation.rs
Normal file
107
consensus/types/src/attestation.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use super::{
|
||||
AggregateSignature, AttestationData, BitList, ChainSpec, Domain, EthSpec, Fork, SecretKey,
|
||||
Signature, SignedRoot, SubnetId,
|
||||
};
|
||||
use crate::{test_utils::TestRandom, Hash256};
|
||||
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),
|
||||
AlreadySigned(usize),
|
||||
SubnetCountIsZero(ArithError),
|
||||
}
|
||||
|
||||
/// Details an attestation that can be slashable.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct Attestation<T: EthSpec> {
|
||||
pub aggregation_bits: BitList<T::MaxValidatorsPerCommittee>,
|
||||
pub data: AttestationData,
|
||||
pub signature: AggregateSignature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> Attestation<T> {
|
||||
/// Are the aggregation bitfields of these attestations disjoint?
|
||||
pub fn signers_disjoint_from(&self, other: &Self) -> bool {
|
||||
self.aggregation_bits
|
||||
.intersection(&other.aggregation_bits)
|
||||
.is_zero()
|
||||
}
|
||||
|
||||
/// Aggregate another Attestation 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.data, other.data);
|
||||
debug_assert!(self.signers_disjoint_from(other));
|
||||
|
||||
self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits);
|
||||
self.signature.add_aggregate(&other.signature);
|
||||
}
|
||||
|
||||
/// Signs `self`, setting the `committee_position`'th bit of `aggregation_bits` to `true`.
|
||||
///
|
||||
/// Returns an `AlreadySigned` error if the `committee_position`'th bit is already `true`.
|
||||
pub fn sign(
|
||||
&mut self,
|
||||
secret_key: &SecretKey,
|
||||
committee_position: usize,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if self
|
||||
.aggregation_bits
|
||||
.get(committee_position)
|
||||
.map_err(Error::SszTypesError)?
|
||||
{
|
||||
Err(Error::AlreadySigned(committee_position))
|
||||
} else {
|
||||
self.aggregation_bits
|
||||
.set(committee_position, true)
|
||||
.map_err(Error::SszTypesError)?;
|
||||
|
||||
let domain = spec.get_domain(
|
||||
self.data.target.epoch,
|
||||
Domain::BeaconAttester,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = self.data.signing_root(domain);
|
||||
|
||||
self.signature
|
||||
.add(&Signature::new(message.as_bytes(), secret_key));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the subnet id associated with the attestation.
|
||||
///
|
||||
/// Note, this will return the subnet id for an aggregated attestation. This is done
|
||||
/// to avoid checking aggregate bits every time we wish to get an id.
|
||||
pub fn subnet_id(&self, spec: &ChainSpec) -> Result<SubnetId, Error> {
|
||||
self.data
|
||||
.index
|
||||
.safe_rem(spec.attestation_subnet_count)
|
||||
.map(SubnetId::new)
|
||||
.map_err(Error::SubnetCountIsZero)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(Attestation<MainnetEthSpec>);
|
||||
}
|
||||
46
consensus/types/src/attestation_data.rs
Normal file
46
consensus/types/src/attestation_data.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{Checkpoint, Hash256, SignedRoot, Slot};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// The data upon which an attestation is based.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Hash,
|
||||
Encode,
|
||||
Decode,
|
||||
TreeHash,
|
||||
TestRandom,
|
||||
Default,
|
||||
)]
|
||||
pub struct AttestationData {
|
||||
pub slot: Slot,
|
||||
pub index: u64,
|
||||
|
||||
// LMD GHOST vote
|
||||
pub beacon_block_root: Hash256,
|
||||
|
||||
// FFG Vote
|
||||
pub source: Checkpoint,
|
||||
pub target: Checkpoint,
|
||||
}
|
||||
|
||||
impl SignedRoot for AttestationData {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(AttestationData);
|
||||
}
|
||||
15
consensus/types/src/attestation_duty.rs
Normal file
15
consensus/types/src/attestation_duty.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use crate::*;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct AttestationDuty {
|
||||
/// The slot during which the attester must attest.
|
||||
pub slot: Slot,
|
||||
/// The index of this committee within the committees in `slot`.
|
||||
pub index: CommitteeIndex,
|
||||
/// The position of the attester within the committee.
|
||||
pub committee_position: usize,
|
||||
/// The total number of attesters in the committee.
|
||||
pub committee_len: usize,
|
||||
}
|
||||
25
consensus/types/src/attester_slashing.rs
Normal file
25
consensus/types/src/attester_slashing.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::{test_utils::TestRandom, EthSpec, IndexedAttestation};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Two conflicting attestations.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct AttesterSlashing<T: EthSpec> {
|
||||
pub attestation_1: IndexedAttestation<T>,
|
||||
pub attestation_2: IndexedAttestation<T>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(AttesterSlashing<MainnetEthSpec>);
|
||||
}
|
||||
216
consensus/types/src/beacon_block.rs
Normal file
216
consensus/types/src/beacon_block.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
use bls::Signature;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash::TreeHash;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// A block of the `BeaconChain`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct BeaconBlock<T: EthSpec> {
|
||||
pub slot: Slot,
|
||||
pub proposer_index: u64,
|
||||
pub parent_root: Hash256,
|
||||
pub state_root: Hash256,
|
||||
pub body: BeaconBlockBody<T>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SignedRoot for BeaconBlock<T> {}
|
||||
|
||||
impl<T: EthSpec> BeaconBlock<T> {
|
||||
/// Returns an empty block to be used during genesis.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn empty(spec: &ChainSpec) -> Self {
|
||||
BeaconBlock {
|
||||
slot: spec.genesis_slot,
|
||||
proposer_index: 0,
|
||||
parent_root: Hash256::zero(),
|
||||
state_root: Hash256::zero(),
|
||||
body: BeaconBlockBody {
|
||||
randao_reveal: Signature::empty_signature(),
|
||||
eth1_data: Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
deposit_count: 0,
|
||||
},
|
||||
graffiti: [0; 32],
|
||||
proposer_slashings: VariableList::empty(),
|
||||
attester_slashings: VariableList::empty(),
|
||||
attestations: VariableList::empty(),
|
||||
deposits: VariableList::empty(),
|
||||
voluntary_exits: VariableList::empty(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a block where the block has the max possible operations.
|
||||
pub fn full(spec: &ChainSpec) -> BeaconBlock<T> {
|
||||
let header = BeaconBlockHeader {
|
||||
slot: Slot::new(1),
|
||||
proposer_index: 0,
|
||||
parent_root: Hash256::zero(),
|
||||
state_root: Hash256::zero(),
|
||||
body_root: Hash256::zero(),
|
||||
};
|
||||
|
||||
let signed_header = SignedBeaconBlockHeader {
|
||||
message: header,
|
||||
signature: Signature::empty_signature(),
|
||||
};
|
||||
let indexed_attestation: IndexedAttestation<T> = IndexedAttestation {
|
||||
attesting_indices: VariableList::new(vec![
|
||||
0 as u64;
|
||||
T::MaxValidatorsPerCommittee::to_usize()
|
||||
])
|
||||
.unwrap(),
|
||||
data: AttestationData::default(),
|
||||
signature: AggregateSignature::new(),
|
||||
};
|
||||
|
||||
let deposit_data = DepositData {
|
||||
pubkey: PublicKeyBytes::empty(),
|
||||
withdrawal_credentials: Hash256::zero(),
|
||||
amount: 0,
|
||||
signature: SignatureBytes::empty(),
|
||||
};
|
||||
let proposer_slashing = ProposerSlashing {
|
||||
signed_header_1: signed_header.clone(),
|
||||
signed_header_2: signed_header.clone(),
|
||||
};
|
||||
|
||||
let attester_slashing = AttesterSlashing {
|
||||
attestation_1: indexed_attestation.clone(),
|
||||
attestation_2: indexed_attestation.clone(),
|
||||
};
|
||||
|
||||
let attestation: Attestation<T> = Attestation {
|
||||
aggregation_bits: BitList::with_capacity(T::MaxValidatorsPerCommittee::to_usize())
|
||||
.unwrap(),
|
||||
data: AttestationData::default(),
|
||||
signature: AggregateSignature::new(),
|
||||
};
|
||||
|
||||
let deposit = Deposit {
|
||||
proof: FixedVector::from_elem(Hash256::zero()),
|
||||
data: deposit_data,
|
||||
};
|
||||
|
||||
let voluntary_exit = VoluntaryExit {
|
||||
epoch: Epoch::new(1),
|
||||
validator_index: 1,
|
||||
};
|
||||
|
||||
let signed_voluntary_exit = SignedVoluntaryExit {
|
||||
message: voluntary_exit,
|
||||
signature: Signature::empty_signature(),
|
||||
};
|
||||
|
||||
let mut block: BeaconBlock<T> = BeaconBlock::empty(spec);
|
||||
for _ in 0..T::MaxProposerSlashings::to_usize() {
|
||||
block
|
||||
.body
|
||||
.proposer_slashings
|
||||
.push(proposer_slashing.clone())
|
||||
.unwrap();
|
||||
}
|
||||
for _ in 0..T::MaxDeposits::to_usize() {
|
||||
block.body.deposits.push(deposit.clone()).unwrap();
|
||||
}
|
||||
for _ in 0..T::MaxVoluntaryExits::to_usize() {
|
||||
block
|
||||
.body
|
||||
.voluntary_exits
|
||||
.push(signed_voluntary_exit.clone())
|
||||
.unwrap();
|
||||
}
|
||||
for _ in 0..T::MaxAttesterSlashings::to_usize() {
|
||||
block
|
||||
.body
|
||||
.attester_slashings
|
||||
.push(attester_slashing.clone())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
for _ in 0..T::MaxAttestations::to_usize() {
|
||||
block.body.attestations.push(attestation.clone()).unwrap();
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
/// Returns the epoch corresponding to `self.slot`.
|
||||
pub fn epoch(&self) -> Epoch {
|
||||
self.slot.epoch(T::slots_per_epoch())
|
||||
}
|
||||
|
||||
/// Returns the `tree_hash_root` of the block.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn canonical_root(&self) -> Hash256 {
|
||||
Hash256::from_slice(&self.tree_hash_root()[..])
|
||||
}
|
||||
|
||||
/// Returns a full `BeaconBlockHeader` of this block.
|
||||
///
|
||||
/// Note: This method is used instead of an `Into` impl to avoid a `Clone` of an entire block
|
||||
/// when you want to have the block _and_ the header.
|
||||
///
|
||||
/// Note: performs a full tree-hash of `self.body`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn block_header(&self) -> BeaconBlockHeader {
|
||||
BeaconBlockHeader {
|
||||
slot: self.slot,
|
||||
proposer_index: self.proposer_index,
|
||||
parent_root: self.parent_root,
|
||||
state_root: self.state_root,
|
||||
body_root: Hash256::from_slice(&self.body.tree_hash_root()[..]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a "temporary" header, where the `state_root` is `Hash256::zero()`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn temporary_block_header(&self) -> BeaconBlockHeader {
|
||||
BeaconBlockHeader {
|
||||
state_root: Hash256::zero(),
|
||||
..self.block_header()
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs `self`, producing a `SignedBeaconBlock`.
|
||||
pub fn sign(
|
||||
self,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> SignedBeaconBlock<T> {
|
||||
let domain = spec.get_domain(
|
||||
self.epoch(),
|
||||
Domain::BeaconProposer,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = self.signing_root(domain);
|
||||
let signature = Signature::new(message.as_bytes(), secret_key);
|
||||
SignedBeaconBlock {
|
||||
message: self,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(BeaconBlock<MainnetEthSpec>);
|
||||
}
|
||||
37
consensus/types/src/beacon_block_body.rs
Normal file
37
consensus/types/src/beacon_block_body.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::utils::{graffiti_from_hex_str, graffiti_to_hex_str};
|
||||
use crate::*;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use ssz_types::VariableList;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// The body of a `BeaconChain` block, containing operations.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct BeaconBlockBody<T: EthSpec> {
|
||||
pub randao_reveal: Signature,
|
||||
pub eth1_data: Eth1Data,
|
||||
#[serde(
|
||||
serialize_with = "graffiti_to_hex_str",
|
||||
deserialize_with = "graffiti_from_hex_str"
|
||||
)]
|
||||
pub graffiti: [u8; 32],
|
||||
pub proposer_slashings: VariableList<ProposerSlashing, T::MaxProposerSlashings>,
|
||||
pub attester_slashings: VariableList<AttesterSlashing<T>, T::MaxAttesterSlashings>,
|
||||
pub attestations: VariableList<Attestation<T>, T::MaxAttestations>,
|
||||
pub deposits: VariableList<Deposit, T::MaxDeposits>,
|
||||
pub voluntary_exits: VariableList<SignedVoluntaryExit, T::MaxVoluntaryExits>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(BeaconBlockBody<MainnetEthSpec>);
|
||||
}
|
||||
70
consensus/types/src/beacon_block_header.rs
Normal file
70
consensus/types/src/beacon_block_header.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash::TreeHash;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// A header of a `BeaconBlock`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct BeaconBlockHeader {
|
||||
pub slot: Slot,
|
||||
pub proposer_index: u64,
|
||||
pub parent_root: Hash256,
|
||||
pub state_root: Hash256,
|
||||
pub body_root: Hash256,
|
||||
}
|
||||
|
||||
impl SignedRoot for BeaconBlockHeader {}
|
||||
|
||||
impl BeaconBlockHeader {
|
||||
/// Returns the `tree_hash_root` of the header.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn canonical_root(&self) -> Hash256 {
|
||||
Hash256::from_slice(&self.tree_hash_root()[..])
|
||||
}
|
||||
|
||||
/// Given a `body`, consumes `self` and returns a complete `BeaconBlock`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn into_block<T: EthSpec>(self, body: BeaconBlockBody<T>) -> BeaconBlock<T> {
|
||||
BeaconBlock {
|
||||
slot: self.slot,
|
||||
proposer_index: self.proposer_index,
|
||||
parent_root: self.parent_root,
|
||||
state_root: self.state_root,
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs `self`, producing a `SignedBeaconBlockHeader`.
|
||||
pub fn sign<E: EthSpec>(
|
||||
self,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> SignedBeaconBlockHeader {
|
||||
let epoch = self.slot.epoch(E::slots_per_epoch());
|
||||
let domain = spec.get_domain(epoch, Domain::BeaconProposer, fork, genesis_validators_root);
|
||||
let message = self.signing_root(domain);
|
||||
let signature = Signature::new(message.as_bytes(), secret_key);
|
||||
SignedBeaconBlockHeader {
|
||||
message: self,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(BeaconBlockHeader);
|
||||
}
|
||||
26
consensus/types/src/beacon_committee.rs
Normal file
26
consensus/types/src/beacon_committee.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use crate::*;
|
||||
|
||||
#[derive(Default, Clone, Debug, PartialEq)]
|
||||
pub struct BeaconCommittee<'a> {
|
||||
pub slot: Slot,
|
||||
pub index: CommitteeIndex,
|
||||
pub committee: &'a [usize],
|
||||
}
|
||||
|
||||
impl<'a> BeaconCommittee<'a> {
|
||||
pub fn into_owned(self) -> OwnedBeaconCommittee {
|
||||
OwnedBeaconCommittee {
|
||||
slot: self.slot,
|
||||
index: self.index,
|
||||
committee: self.committee.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Default, Clone, Debug, PartialEq)]
|
||||
pub struct OwnedBeaconCommittee {
|
||||
pub slot: Slot,
|
||||
pub index: CommitteeIndex,
|
||||
pub committee: Vec<usize>,
|
||||
}
|
||||
1202
consensus/types/src/beacon_state.rs
Normal file
1202
consensus/types/src/beacon_state.rs
Normal file
File diff suppressed because it is too large
Load Diff
43
consensus/types/src/beacon_state/clone_config.rs
Normal file
43
consensus/types/src/beacon_state/clone_config.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
/// Configuration struct for controlling which caches of a `BeaconState` should be cloned.
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct CloneConfig {
|
||||
pub committee_caches: bool,
|
||||
pub pubkey_cache: bool,
|
||||
pub exit_cache: bool,
|
||||
pub tree_hash_cache: bool,
|
||||
}
|
||||
|
||||
impl CloneConfig {
|
||||
pub fn all() -> Self {
|
||||
Self {
|
||||
committee_caches: true,
|
||||
pubkey_cache: true,
|
||||
exit_cache: true,
|
||||
tree_hash_cache: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn none() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn committee_caches_only() -> Self {
|
||||
Self {
|
||||
committee_caches: true,
|
||||
..Self::none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sanity() {
|
||||
assert!(CloneConfig::all().pubkey_cache);
|
||||
assert!(!CloneConfig::none().tree_hash_cache);
|
||||
assert!(CloneConfig::committee_caches_only().committee_caches);
|
||||
assert!(!CloneConfig::committee_caches_only().exit_cache);
|
||||
}
|
||||
}
|
||||
286
consensus/types/src/beacon_state/committee_cache.rs
Normal file
286
consensus/types/src/beacon_state/committee_cache.rs
Normal file
@@ -0,0 +1,286 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
use super::BeaconState;
|
||||
use crate::*;
|
||||
use core::num::NonZeroUsize;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::ops::Range;
|
||||
use swap_or_not_shuffle::shuffle_list;
|
||||
|
||||
mod tests;
|
||||
|
||||
/// Computes and stores the shuffling for an epoch. Provides various getters to allow callers to
|
||||
/// read the committees for the given epoch.
|
||||
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, Encode, Decode)]
|
||||
pub struct CommitteeCache {
|
||||
initialized_epoch: Option<Epoch>,
|
||||
shuffling: Vec<usize>,
|
||||
shuffling_positions: Vec<Option<NonZeroUsize>>,
|
||||
committees_per_slot: u64,
|
||||
slots_per_epoch: u64,
|
||||
}
|
||||
|
||||
impl CommitteeCache {
|
||||
/// Return a new, fully initialized cache.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn initialized<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
epoch: Epoch,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<CommitteeCache, Error> {
|
||||
RelativeEpoch::from_epoch(state.current_epoch(), epoch)
|
||||
.map_err(|_| Error::EpochOutOfBounds)?;
|
||||
|
||||
// May cause divide-by-zero errors.
|
||||
if T::slots_per_epoch() == 0 {
|
||||
return Err(Error::ZeroSlotsPerEpoch);
|
||||
}
|
||||
|
||||
let active_validator_indices = get_active_validator_indices(&state.validators, epoch);
|
||||
|
||||
if active_validator_indices.is_empty() {
|
||||
return Err(Error::InsufficientValidators);
|
||||
}
|
||||
|
||||
let committees_per_slot =
|
||||
T::get_committee_count_per_slot(active_validator_indices.len(), spec)? as u64;
|
||||
|
||||
let seed = state.get_seed(epoch, Domain::BeaconAttester, spec)?;
|
||||
|
||||
let shuffling = shuffle_list(
|
||||
active_validator_indices,
|
||||
spec.shuffle_round_count,
|
||||
&seed[..],
|
||||
false,
|
||||
)
|
||||
.ok_or_else(|| Error::UnableToShuffle)?;
|
||||
|
||||
// The use of `NonZeroUsize` reduces the maximum number of possible validators by one.
|
||||
if state.validators.len() == usize::max_value() {
|
||||
return Err(Error::TooManyValidators);
|
||||
}
|
||||
|
||||
let mut shuffling_positions = vec![None; state.validators.len()];
|
||||
for (i, v) in shuffling.iter().enumerate() {
|
||||
shuffling_positions[*v] = NonZeroUsize::new(i + 1);
|
||||
}
|
||||
|
||||
Ok(CommitteeCache {
|
||||
initialized_epoch: Some(epoch),
|
||||
shuffling,
|
||||
shuffling_positions,
|
||||
committees_per_slot,
|
||||
slots_per_epoch: T::slots_per_epoch(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the cache has been initialized at the supplied `epoch`.
|
||||
///
|
||||
/// An non-initialized cache does not provide any useful information.
|
||||
pub fn is_initialized_at(&self, epoch: Epoch) -> bool {
|
||||
Some(epoch) == self.initialized_epoch
|
||||
}
|
||||
|
||||
/// Returns the **shuffled** list of active validator indices for the initialized epoch.
|
||||
///
|
||||
/// These indices are not in ascending order.
|
||||
///
|
||||
/// Always returns `&[]` for a non-initialized epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn active_validator_indices(&self) -> &[usize] {
|
||||
&self.shuffling
|
||||
}
|
||||
|
||||
/// Returns the shuffled list of active validator indices for the initialized epoch.
|
||||
///
|
||||
/// Always returns `&[]` for a non-initialized epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn shuffling(&self) -> &[usize] {
|
||||
&self.shuffling
|
||||
}
|
||||
|
||||
/// Get the Beacon committee for the given `slot` and `index`.
|
||||
///
|
||||
/// Return `None` if the cache is uninitialized, or the `slot` or `index` is out of range.
|
||||
pub fn get_beacon_committee(
|
||||
&self,
|
||||
slot: Slot,
|
||||
index: CommitteeIndex,
|
||||
) -> Option<BeaconCommittee> {
|
||||
if self.initialized_epoch.is_none()
|
||||
|| !self.is_initialized_at(slot.epoch(self.slots_per_epoch))
|
||||
|| index >= self.committees_per_slot
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let committee_index =
|
||||
(slot.as_u64() % self.slots_per_epoch) * self.committees_per_slot + index;
|
||||
let committee = self.compute_committee(committee_index as usize)?;
|
||||
|
||||
Some(BeaconCommittee {
|
||||
slot,
|
||||
index,
|
||||
committee,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get all the Beacon committees at a given `slot`.
|
||||
pub fn get_beacon_committees_at_slot(&self, slot: Slot) -> Result<Vec<BeaconCommittee>, Error> {
|
||||
if self.initialized_epoch.is_none() {
|
||||
return Err(Error::CommitteeCacheUninitialized(None));
|
||||
}
|
||||
|
||||
(0..self.committees_per_slot())
|
||||
.map(|index| {
|
||||
self.get_beacon_committee(slot, index)
|
||||
.ok_or(Error::NoCommittee { slot, index })
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns all committees for `self.initialized_epoch`.
|
||||
pub fn get_all_beacon_committees(&self) -> Result<Vec<BeaconCommittee>, Error> {
|
||||
let initialized_epoch = self
|
||||
.initialized_epoch
|
||||
.ok_or_else(|| Error::CommitteeCacheUninitialized(None))?;
|
||||
|
||||
initialized_epoch.slot_iter(self.slots_per_epoch).try_fold(
|
||||
Vec::with_capacity(self.slots_per_epoch as usize),
|
||||
|mut vec, slot| {
|
||||
vec.append(&mut self.get_beacon_committees_at_slot(slot)?);
|
||||
Ok(vec)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the `AttestationDuty` for the given `validator_index`.
|
||||
///
|
||||
/// Returns `None` if the `validator_index` does not exist, does not have duties or `Self` is
|
||||
/// non-initialized.
|
||||
pub fn get_attestation_duties(&self, validator_index: usize) -> Option<AttestationDuty> {
|
||||
let i = self.shuffled_position(validator_index)?;
|
||||
|
||||
(0..self.epoch_committee_count())
|
||||
.map(|nth_committee| (nth_committee, self.compute_committee_range(nth_committee)))
|
||||
.find(|(_, range)| {
|
||||
if let Some(range) = range {
|
||||
range.start <= i && range.end > i
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.and_then(|(nth_committee, range)| {
|
||||
let (slot, index) = self.convert_to_slot_and_index(nth_committee as u64)?;
|
||||
let range = range?;
|
||||
let committee_position = i - range.start;
|
||||
let committee_len = range.end - range.start;
|
||||
|
||||
Some(AttestationDuty {
|
||||
slot,
|
||||
index,
|
||||
committee_position,
|
||||
committee_len,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert an index addressing the list of all epoch committees into a slot and per-slot index.
|
||||
fn convert_to_slot_and_index(
|
||||
&self,
|
||||
global_committee_index: u64,
|
||||
) -> Option<(Slot, CommitteeIndex)> {
|
||||
let epoch_start_slot = self.initialized_epoch?.start_slot(self.slots_per_epoch);
|
||||
let slot_offset = global_committee_index / self.committees_per_slot;
|
||||
let index = global_committee_index % self.committees_per_slot;
|
||||
Some((epoch_start_slot + slot_offset, index))
|
||||
}
|
||||
|
||||
/// Returns the number of active validators in the initialized epoch.
|
||||
///
|
||||
/// Always returns `usize::default()` for a non-initialized epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn active_validator_count(&self) -> usize {
|
||||
self.shuffling.len()
|
||||
}
|
||||
|
||||
/// Returns the total number of committees in the initialized epoch.
|
||||
///
|
||||
/// Always returns `usize::default()` for a non-initialized epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn epoch_committee_count(&self) -> usize {
|
||||
self.committees_per_slot as usize * self.slots_per_epoch as usize
|
||||
}
|
||||
|
||||
/// Returns the number of committees per slot for this cache's epoch.
|
||||
pub fn committees_per_slot(&self) -> u64 {
|
||||
self.committees_per_slot
|
||||
}
|
||||
|
||||
/// Returns a slice of `self.shuffling` that represents the `index`'th committee in the epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn compute_committee(&self, index: usize) -> Option<&[usize]> {
|
||||
Some(&self.shuffling[self.compute_committee_range(index)?])
|
||||
}
|
||||
|
||||
/// Returns a range of `self.shuffling` that represents the `index`'th committee in the epoch.
|
||||
///
|
||||
/// To avoid a divide-by-zero, returns `None` if `self.committee_count` is zero.
|
||||
///
|
||||
/// Will also return `None` if the index is out of bounds.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn compute_committee_range(&self, index: usize) -> Option<Range<usize>> {
|
||||
let count = self.epoch_committee_count();
|
||||
if count == 0 || index >= count {
|
||||
return None;
|
||||
}
|
||||
|
||||
let num_validators = self.shuffling.len();
|
||||
let start = (num_validators * index) / count;
|
||||
let end = (num_validators * (index + 1)) / count;
|
||||
|
||||
Some(start..end)
|
||||
}
|
||||
|
||||
/// Returns the index of some validator in `self.shuffling`.
|
||||
///
|
||||
/// Always returns `None` for a non-initialized epoch.
|
||||
fn shuffled_position(&self, validator_index: usize) -> Option<usize> {
|
||||
self.shuffling_positions
|
||||
.get(validator_index)?
|
||||
.and_then(|p| Some(p.get() - 1))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of all `validators` indices where the validator is active at the given
|
||||
/// `epoch`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec<usize> {
|
||||
let mut active = Vec::with_capacity(validators.len());
|
||||
|
||||
for (index, validator) in validators.iter().enumerate() {
|
||||
if validator.is_active_at(epoch) {
|
||||
active.push(index)
|
||||
}
|
||||
}
|
||||
|
||||
active.shrink_to_fit();
|
||||
|
||||
active
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary-fuzz")]
|
||||
impl arbitrary::Arbitrary for CommitteeCache {
|
||||
fn arbitrary(_u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
120
consensus/types/src/beacon_state/committee_cache/tests.rs
Normal file
120
consensus/types/src/beacon_state/committee_cache/tests.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
#![cfg(test)]
|
||||
use super::*;
|
||||
use crate::{test_utils::*, *};
|
||||
|
||||
#[test]
|
||||
fn default_values() {
|
||||
let cache = CommitteeCache::default();
|
||||
|
||||
assert_eq!(cache.is_initialized_at(Epoch::new(0)), false);
|
||||
assert!(&cache.active_validator_indices().is_empty());
|
||||
assert_eq!(cache.get_beacon_committee(Slot::new(0), 0), None);
|
||||
assert_eq!(cache.get_attestation_duties(0), None);
|
||||
assert_eq!(cache.active_validator_count(), 0);
|
||||
assert_eq!(cache.epoch_committee_count(), 0);
|
||||
assert!(cache.get_beacon_committees_at_slot(Slot::new(0)).is_err());
|
||||
}
|
||||
|
||||
fn new_state<T: EthSpec>(validator_count: usize, slot: Slot) -> BeaconState<T> {
|
||||
let spec = &T::default_spec();
|
||||
|
||||
let mut builder =
|
||||
TestingBeaconStateBuilder::from_single_keypair(validator_count, &Keypair::random(), spec);
|
||||
|
||||
builder.teleport_to_slot(slot);
|
||||
|
||||
let (state, _keypairs) = builder.build();
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_without_validators() {
|
||||
let state = new_state::<MinimalEthSpec>(0, Slot::new(0));
|
||||
let spec = &MinimalEthSpec::default_spec();
|
||||
|
||||
assert_eq!(
|
||||
CommitteeCache::initialized(&state, state.current_epoch(), &spec),
|
||||
Err(BeaconStateError::InsufficientValidators)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initializes_with_the_right_epoch() {
|
||||
let state = new_state::<MinimalEthSpec>(16, Slot::new(0));
|
||||
let spec = &MinimalEthSpec::default_spec();
|
||||
|
||||
let cache = CommitteeCache::default();
|
||||
assert_eq!(cache.initialized_epoch, None);
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.current_epoch(), &spec).unwrap();
|
||||
assert_eq!(cache.initialized_epoch, Some(state.current_epoch()));
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), &spec).unwrap();
|
||||
assert_eq!(cache.initialized_epoch, Some(state.previous_epoch()));
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.next_epoch(), &spec).unwrap();
|
||||
assert_eq!(cache.initialized_epoch, Some(state.next_epoch()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shuffles_for_the_right_epoch() {
|
||||
use crate::EthSpec;
|
||||
|
||||
let num_validators = MinimalEthSpec::minimum_validator_count() * 2;
|
||||
let epoch = Epoch::new(100_000_000);
|
||||
let slot = epoch.start_slot(MinimalEthSpec::slots_per_epoch());
|
||||
|
||||
let mut state = new_state::<MinimalEthSpec>(num_validators, slot);
|
||||
let spec = &MinimalEthSpec::default_spec();
|
||||
|
||||
let distinct_hashes: Vec<Hash256> = (0..MinimalEthSpec::epochs_per_historical_vector())
|
||||
.map(|i| Hash256::from_low_u64_be(i as u64))
|
||||
.collect();
|
||||
|
||||
state.randao_mixes = FixedVector::from(distinct_hashes);
|
||||
|
||||
let previous_seed = state
|
||||
.get_seed(state.previous_epoch(), Domain::BeaconAttester, spec)
|
||||
.unwrap();
|
||||
let current_seed = state
|
||||
.get_seed(state.current_epoch(), Domain::BeaconAttester, spec)
|
||||
.unwrap();
|
||||
let next_seed = state
|
||||
.get_seed(state.next_epoch(), Domain::BeaconAttester, spec)
|
||||
.unwrap();
|
||||
|
||||
assert!((previous_seed != current_seed) && (current_seed != next_seed));
|
||||
|
||||
let shuffling_with_seed = |seed: Hash256| {
|
||||
shuffle_list(
|
||||
(0..num_validators).collect(),
|
||||
spec.shuffle_round_count,
|
||||
&seed[..],
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let assert_shuffling_positions_accurate = |cache: &CommitteeCache| {
|
||||
for (i, v) in cache.shuffling.iter().enumerate() {
|
||||
assert_eq!(
|
||||
cache.shuffling_positions[*v].unwrap().get() - 1,
|
||||
i,
|
||||
"Shuffling position inaccurate"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.current_epoch(), spec).unwrap();
|
||||
assert_eq!(cache.shuffling, shuffling_with_seed(current_seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), spec).unwrap();
|
||||
assert_eq!(cache.shuffling, shuffling_with_seed(previous_seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.next_epoch(), spec).unwrap();
|
||||
assert_eq!(cache.shuffling, shuffling_with_seed(next_seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
}
|
||||
73
consensus/types/src/beacon_state/exit_cache.rs
Normal file
73
consensus/types/src/beacon_state/exit_cache.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use super::{BeaconStateError, ChainSpec, Epoch, Validator};
|
||||
use safe_arith::SafeArith;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Map from exit epoch to the number of validators with that exit epoch.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ExitCache {
|
||||
initialized: bool,
|
||||
exit_epoch_counts: HashMap<Epoch, u64>,
|
||||
}
|
||||
|
||||
impl ExitCache {
|
||||
/// Build the cache if not initialized.
|
||||
pub fn build(
|
||||
&mut self,
|
||||
validators: &[Validator],
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
if self.initialized {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.initialized = true;
|
||||
// Add all validators with a non-default exit epoch to the cache.
|
||||
validators
|
||||
.iter()
|
||||
.filter(|validator| validator.exit_epoch != spec.far_future_epoch)
|
||||
.try_for_each(|validator| self.record_validator_exit(validator.exit_epoch))
|
||||
}
|
||||
|
||||
/// Check that the cache is initialized and return an error if it is not.
|
||||
pub fn check_initialized(&self) -> Result<(), BeaconStateError> {
|
||||
if self.initialized {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(BeaconStateError::ExitCacheUninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
/// Record the exit epoch of a validator. Must be called only once per exiting validator.
|
||||
pub fn record_validator_exit(&mut self, exit_epoch: Epoch) -> Result<(), BeaconStateError> {
|
||||
self.check_initialized()?;
|
||||
self.exit_epoch_counts
|
||||
.entry(exit_epoch)
|
||||
.or_insert(0)
|
||||
.increment()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the largest exit epoch with a non-zero exit epoch count.
|
||||
pub fn max_epoch(&self) -> Result<Option<Epoch>, BeaconStateError> {
|
||||
self.check_initialized()?;
|
||||
Ok(self.exit_epoch_counts.keys().max().cloned())
|
||||
}
|
||||
|
||||
/// Get number of validators with the given exit epoch. (Return 0 for the default exit epoch.)
|
||||
pub fn get_churn_at(&self, exit_epoch: Epoch) -> Result<u64, BeaconStateError> {
|
||||
self.check_initialized()?;
|
||||
Ok(self
|
||||
.exit_epoch_counts
|
||||
.get(&exit_epoch)
|
||||
.cloned()
|
||||
.unwrap_or(0))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary-fuzz")]
|
||||
impl arbitrary::Arbitrary for ExitCache {
|
||||
fn arbitrary(_u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
48
consensus/types/src/beacon_state/pubkey_cache.rs
Normal file
48
consensus/types/src/beacon_state/pubkey_cache.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use crate::*;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
type ValidatorIndex = usize;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct PubkeyCache {
|
||||
/// Maintain the number of keys added to the map. It is not sufficient to just use the HashMap
|
||||
/// len, as it does not increase when duplicate keys are added. Duplicate keys are used during
|
||||
/// testing.
|
||||
len: usize,
|
||||
map: HashMap<PublicKeyBytes, ValidatorIndex>,
|
||||
}
|
||||
|
||||
impl PubkeyCache {
|
||||
/// Returns the number of validator indices added to the map so far.
|
||||
pub fn len(&self) -> ValidatorIndex {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Inserts a validator index into the map.
|
||||
///
|
||||
/// The added index must equal the number of validators already added to the map. This ensures
|
||||
/// that an index is never skipped.
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
pub fn insert(&mut self, pubkey: PublicKeyBytes, index: ValidatorIndex) -> bool {
|
||||
if index == self.len {
|
||||
self.map.insert(pubkey, index);
|
||||
self.len += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Looks up a validator index's by their public key.
|
||||
pub fn get(&self, pubkey: &PublicKeyBytes) -> Option<ValidatorIndex> {
|
||||
self.map.get(pubkey).copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary-fuzz")]
|
||||
impl arbitrary::Arbitrary for PubkeyCache {
|
||||
fn arbitrary(_u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
408
consensus/types/src/beacon_state/tests.rs
Normal file
408
consensus/types/src/beacon_state/tests.rs
Normal file
@@ -0,0 +1,408 @@
|
||||
#![cfg(test)]
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(FoundationBeaconState);
|
||||
|
||||
fn test_beacon_proposer_index<T: EthSpec>() {
|
||||
let spec = T::default_spec();
|
||||
let relative_epoch = RelativeEpoch::Current;
|
||||
|
||||
// Build a state for testing.
|
||||
let build_state = |validator_count: usize| -> BeaconState<T> {
|
||||
let builder: TestingBeaconStateBuilder<T> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
state.build_committee_cache(relative_epoch, &spec).unwrap();
|
||||
|
||||
state
|
||||
};
|
||||
|
||||
// Get the i'th candidate proposer for the given state and slot
|
||||
let ith_candidate = |state: &BeaconState<T>, slot: Slot, i: usize| {
|
||||
let epoch = slot.epoch(T::slots_per_epoch());
|
||||
let seed = state.get_beacon_proposer_seed(slot, &spec).unwrap();
|
||||
let active_validators = state.get_active_validator_indices(epoch);
|
||||
active_validators[compute_shuffled_index(
|
||||
i,
|
||||
active_validators.len(),
|
||||
&seed,
|
||||
spec.shuffle_round_count,
|
||||
)
|
||||
.unwrap()]
|
||||
};
|
||||
|
||||
// Run a test on the state.
|
||||
let test = |state: &BeaconState<T>, slot: Slot, candidate_index: usize| {
|
||||
assert_eq!(
|
||||
state.get_beacon_proposer_index(slot, &spec),
|
||||
Ok(ith_candidate(state, slot, candidate_index))
|
||||
);
|
||||
};
|
||||
|
||||
// Test where we have one validator per slot.
|
||||
// 0th candidate should be chosen every time.
|
||||
let state = build_state(T::slots_per_epoch() as usize);
|
||||
for i in 0..T::slots_per_epoch() {
|
||||
test(&state, Slot::from(i), 0);
|
||||
}
|
||||
|
||||
// Test where we have two validators per slot.
|
||||
// 0th candidate should be chosen every time.
|
||||
let state = build_state(T::slots_per_epoch() as usize * 2);
|
||||
for i in 0..T::slots_per_epoch() {
|
||||
test(&state, Slot::from(i), 0);
|
||||
}
|
||||
|
||||
// Test with two validators per slot, first validator has zero balance.
|
||||
let mut state = build_state(T::slots_per_epoch() as usize * 2);
|
||||
let slot0_candidate0 = ith_candidate(&state, Slot::new(0), 0);
|
||||
state.validators[slot0_candidate0].effective_balance = 0;
|
||||
test(&state, Slot::new(0), 1);
|
||||
for i in 1..T::slots_per_epoch() {
|
||||
test(&state, Slot::from(i), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_proposer_index() {
|
||||
test_beacon_proposer_index::<MinimalEthSpec>();
|
||||
}
|
||||
|
||||
/// Test that
|
||||
///
|
||||
/// 1. Using the cache before it's built fails.
|
||||
/// 2. Using the cache after it's build passes.
|
||||
/// 3. Using the cache after it's dropped fails.
|
||||
fn test_cache_initialization<'a, T: EthSpec>(
|
||||
state: &'a mut BeaconState<T>,
|
||||
relative_epoch: RelativeEpoch,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let slot = relative_epoch
|
||||
.into_epoch(state.slot.epoch(T::slots_per_epoch()))
|
||||
.start_slot(T::slots_per_epoch());
|
||||
|
||||
// Assuming the cache isn't already built, assert that a call to a cache-using function fails.
|
||||
assert_eq!(
|
||||
state.get_attestation_duties(0, relative_epoch),
|
||||
Err(BeaconStateError::CommitteeCacheUninitialized(Some(
|
||||
relative_epoch
|
||||
)))
|
||||
);
|
||||
|
||||
// Build the cache.
|
||||
state.build_committee_cache(relative_epoch, spec).unwrap();
|
||||
|
||||
// Assert a call to a cache-using function passes.
|
||||
let _ = state.get_beacon_proposer_index(slot, spec).unwrap();
|
||||
|
||||
// Drop the cache.
|
||||
state.drop_committee_cache(relative_epoch);
|
||||
|
||||
// Assert a call to a cache-using function fail.
|
||||
assert_eq!(
|
||||
state.get_beacon_committee(slot, 0),
|
||||
Err(BeaconStateError::CommitteeCacheUninitialized(Some(
|
||||
relative_epoch
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_initialization() {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
|
||||
state.slot =
|
||||
(MinimalEthSpec::genesis_epoch() + 1).start_slot(MinimalEthSpec::slots_per_epoch());
|
||||
|
||||
test_cache_initialization(&mut state, RelativeEpoch::Previous, &spec);
|
||||
test_cache_initialization(&mut state, RelativeEpoch::Current, &spec);
|
||||
test_cache_initialization(&mut state, RelativeEpoch::Next, &spec);
|
||||
}
|
||||
|
||||
fn test_clone_config<E: EthSpec>(base_state: &BeaconState<E>, clone_config: CloneConfig) {
|
||||
let state = base_state.clone_with(clone_config.clone());
|
||||
if clone_config.committee_caches {
|
||||
state
|
||||
.committee_cache(RelativeEpoch::Previous)
|
||||
.expect("committee cache exists");
|
||||
state
|
||||
.committee_cache(RelativeEpoch::Current)
|
||||
.expect("committee cache exists");
|
||||
state
|
||||
.committee_cache(RelativeEpoch::Next)
|
||||
.expect("committee cache exists");
|
||||
} else {
|
||||
state
|
||||
.committee_cache(RelativeEpoch::Previous)
|
||||
.expect_err("shouldn't exist");
|
||||
state
|
||||
.committee_cache(RelativeEpoch::Current)
|
||||
.expect_err("shouldn't exist");
|
||||
state
|
||||
.committee_cache(RelativeEpoch::Next)
|
||||
.expect_err("shouldn't exist");
|
||||
}
|
||||
if clone_config.pubkey_cache {
|
||||
assert_ne!(state.pubkey_cache.len(), 0);
|
||||
} else {
|
||||
assert_eq!(state.pubkey_cache.len(), 0);
|
||||
}
|
||||
if clone_config.exit_cache {
|
||||
state
|
||||
.exit_cache
|
||||
.check_initialized()
|
||||
.expect("exit cache exists");
|
||||
} else {
|
||||
state
|
||||
.exit_cache
|
||||
.check_initialized()
|
||||
.expect_err("exit cache doesn't exist");
|
||||
}
|
||||
if clone_config.tree_hash_cache {
|
||||
assert!(state.tree_hash_cache.is_some());
|
||||
} else {
|
||||
assert!(state.tree_hash_cache.is_none(), "{:?}", clone_config);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_config() {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
|
||||
state.build_all_caches(&spec).unwrap();
|
||||
|
||||
let num_caches = 4;
|
||||
let all_configs = (0..2u8.pow(num_caches)).map(|i| CloneConfig {
|
||||
committee_caches: (i & 1) != 0,
|
||||
pubkey_cache: ((i >> 1) & 1) != 0,
|
||||
exit_cache: ((i >> 2) & 1) != 0,
|
||||
tree_hash_cache: ((i >> 3) & 1) != 0,
|
||||
});
|
||||
|
||||
for config in all_configs {
|
||||
test_clone_config(&state, config);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_hash_cache() {
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let mut state: FoundationBeaconState = BeaconState::random_for_test(&mut rng);
|
||||
|
||||
let root = state.update_tree_hash_cache().unwrap();
|
||||
|
||||
assert_eq!(root.as_bytes(), &state.tree_hash_root()[..]);
|
||||
|
||||
state.slot += 1;
|
||||
|
||||
let root = state.update_tree_hash_cache().unwrap();
|
||||
assert_eq!(root.as_bytes(), &state.tree_hash_root()[..]);
|
||||
}
|
||||
|
||||
/// Tests committee-specific components
|
||||
#[cfg(test)]
|
||||
mod committees {
|
||||
use super::*;
|
||||
use crate::beacon_state::MinimalEthSpec;
|
||||
use swap_or_not_shuffle::shuffle_list;
|
||||
|
||||
fn execute_committee_consistency_test<T: EthSpec>(
|
||||
state: BeaconState<T>,
|
||||
epoch: Epoch,
|
||||
validator_count: usize,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let active_indices: Vec<usize> = (0..validator_count).collect();
|
||||
let seed = state.get_seed(epoch, Domain::BeaconAttester, spec).unwrap();
|
||||
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), epoch).unwrap();
|
||||
|
||||
let mut ordered_indices = state
|
||||
.get_cached_active_validator_indices(relative_epoch)
|
||||
.unwrap()
|
||||
.to_vec();
|
||||
ordered_indices.sort_unstable();
|
||||
assert_eq!(
|
||||
active_indices, ordered_indices,
|
||||
"Validator indices mismatch"
|
||||
);
|
||||
|
||||
let shuffling =
|
||||
shuffle_list(active_indices, spec.shuffle_round_count, &seed[..], false).unwrap();
|
||||
|
||||
let mut expected_indices_iter = shuffling.iter();
|
||||
|
||||
// Loop through all slots in the epoch being tested.
|
||||
for slot in epoch.slot_iter(T::slots_per_epoch()) {
|
||||
let beacon_committees = state.get_beacon_committees_at_slot(slot).unwrap();
|
||||
|
||||
// Assert that the number of committees in this slot is consistent with the reported number
|
||||
// of committees in an epoch.
|
||||
assert_eq!(
|
||||
beacon_committees.len() as u64,
|
||||
state.get_epoch_committee_count(relative_epoch).unwrap() / T::slots_per_epoch()
|
||||
);
|
||||
|
||||
for (committee_index, bc) in beacon_committees.iter().enumerate() {
|
||||
// Assert that indices are assigned sequentially across committees.
|
||||
assert_eq!(committee_index as u64, bc.index);
|
||||
// Assert that a committee lookup via slot is identical to a committee lookup via
|
||||
// index.
|
||||
assert_eq!(state.get_beacon_committee(bc.slot, bc.index).unwrap(), *bc);
|
||||
|
||||
// Loop through each validator in the committee.
|
||||
for (committee_i, validator_i) in bc.committee.iter().enumerate() {
|
||||
// Assert the validators are assigned contiguously across committees.
|
||||
assert_eq!(
|
||||
*validator_i,
|
||||
*expected_indices_iter.next().unwrap(),
|
||||
"Non-sequential validators."
|
||||
);
|
||||
// Assert a call to `get_attestation_duties` is consistent with a call to
|
||||
// `get_beacon_committees_at_slot`
|
||||
let attestation_duty = state
|
||||
.get_attestation_duties(*validator_i, relative_epoch)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(attestation_duty.slot, slot);
|
||||
assert_eq!(attestation_duty.index, bc.index);
|
||||
assert_eq!(attestation_duty.committee_position, committee_i);
|
||||
assert_eq!(attestation_duty.committee_len, bc.committee.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that all validators were assigned to a committee.
|
||||
assert!(expected_indices_iter.next().is_none());
|
||||
}
|
||||
|
||||
fn committee_consistency_test<T: EthSpec>(
|
||||
validator_count: usize,
|
||||
state_epoch: Epoch,
|
||||
cache_epoch: RelativeEpoch,
|
||||
) {
|
||||
let spec = &T::default_spec();
|
||||
|
||||
let mut builder = TestingBeaconStateBuilder::from_single_keypair(
|
||||
validator_count,
|
||||
&Keypair::random(),
|
||||
spec,
|
||||
);
|
||||
|
||||
let slot = state_epoch.start_slot(T::slots_per_epoch());
|
||||
builder.teleport_to_slot(slot);
|
||||
|
||||
let (mut state, _keypairs): (BeaconState<T>, _) = builder.build();
|
||||
|
||||
let distinct_hashes: Vec<Hash256> = (0..T::epochs_per_historical_vector())
|
||||
.map(|i| Hash256::from_low_u64_be(i as u64))
|
||||
.collect();
|
||||
state.randao_mixes = FixedVector::from(distinct_hashes);
|
||||
|
||||
state
|
||||
.build_committee_cache(RelativeEpoch::Previous, spec)
|
||||
.unwrap();
|
||||
state
|
||||
.build_committee_cache(RelativeEpoch::Current, spec)
|
||||
.unwrap();
|
||||
state
|
||||
.build_committee_cache(RelativeEpoch::Next, spec)
|
||||
.unwrap();
|
||||
|
||||
let cache_epoch = cache_epoch.into_epoch(state_epoch);
|
||||
|
||||
execute_committee_consistency_test(state, cache_epoch, validator_count as usize, &spec);
|
||||
}
|
||||
|
||||
fn committee_consistency_test_suite<T: EthSpec>(cached_epoch: RelativeEpoch) {
|
||||
let spec = T::default_spec();
|
||||
|
||||
let validator_count = spec.max_committees_per_slot
|
||||
* T::slots_per_epoch() as usize
|
||||
* spec.target_committee_size
|
||||
+ 1;
|
||||
|
||||
committee_consistency_test::<T>(validator_count as usize, Epoch::new(0), cached_epoch);
|
||||
|
||||
committee_consistency_test::<T>(
|
||||
validator_count as usize,
|
||||
T::genesis_epoch() + 4,
|
||||
cached_epoch,
|
||||
);
|
||||
|
||||
committee_consistency_test::<T>(
|
||||
validator_count as usize,
|
||||
T::genesis_epoch() + T::slots_per_historical_root() as u64 * T::slots_per_epoch() * 4,
|
||||
cached_epoch,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Current);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn previous_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Previous);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Next);
|
||||
}
|
||||
}
|
||||
|
||||
mod get_outstanding_deposit_len {
|
||||
use super::*;
|
||||
use crate::test_utils::TestingBeaconStateBuilder;
|
||||
use crate::MinimalEthSpec;
|
||||
|
||||
fn state() -> BeaconState<MinimalEthSpec> {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
let builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec);
|
||||
let (state, _keypairs) = builder.build();
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_ok() {
|
||||
let mut state = state();
|
||||
assert_eq!(state.get_outstanding_deposit_len(), Ok(0));
|
||||
|
||||
state.eth1_data.deposit_count = 17;
|
||||
state.eth1_deposit_index = 16;
|
||||
assert_eq!(state.get_outstanding_deposit_len(), Ok(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_err_if_the_state_is_invalid() {
|
||||
let mut state = state();
|
||||
// The state is invalid, deposit count is lower than deposit index.
|
||||
state.eth1_data.deposit_count = 16;
|
||||
state.eth1_deposit_index = 17;
|
||||
|
||||
assert_eq!(
|
||||
state.get_outstanding_deposit_len(),
|
||||
Err(BeaconStateError::InvalidDepositState {
|
||||
deposit_count: 16,
|
||||
deposit_index: 17,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
330
consensus/types/src/beacon_state/tree_hash_cache.rs
Normal file
330
consensus/types/src/beacon_state/tree_hash_cache.rs
Normal file
@@ -0,0 +1,330 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
use super::Error;
|
||||
use crate::{BeaconState, EthSpec, Hash256, Unsigned, Validator};
|
||||
use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, TreeHashCache};
|
||||
use rayon::prelude::*;
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use tree_hash::{mix_in_length, MerkleHasher, TreeHash};
|
||||
|
||||
/// The number of fields on a beacon state.
|
||||
const NUM_BEACON_STATE_HASHING_FIELDS: usize = 20;
|
||||
|
||||
/// The number of nodes in the Merkle tree of a validator record.
|
||||
const NODES_PER_VALIDATOR: usize = 15;
|
||||
|
||||
/// The number of validator record tree hash caches stored in each arena.
|
||||
///
|
||||
/// This is primarily used for concurrency; if we have 16 validators and set `VALIDATORS_PER_ARENA
|
||||
/// == 8` then it is possible to do a 2-core concurrent hash.
|
||||
///
|
||||
/// Do not set to 0.
|
||||
const VALIDATORS_PER_ARENA: usize = 4_096;
|
||||
|
||||
/// A cache that performs a caching tree hash of the entire `BeaconState` struct.
|
||||
#[derive(Debug, PartialEq, Clone, Default, Encode, Decode)]
|
||||
pub struct BeaconTreeHashCache {
|
||||
// Validators cache
|
||||
validators: ValidatorsListTreeHashCache,
|
||||
// Arenas
|
||||
fixed_arena: CacheArena,
|
||||
balances_arena: CacheArena,
|
||||
slashings_arena: CacheArena,
|
||||
// Caches
|
||||
block_roots: TreeHashCache,
|
||||
state_roots: TreeHashCache,
|
||||
historical_roots: TreeHashCache,
|
||||
balances: TreeHashCache,
|
||||
randao_mixes: TreeHashCache,
|
||||
slashings: TreeHashCache,
|
||||
}
|
||||
|
||||
impl BeaconTreeHashCache {
|
||||
/// Instantiates a new cache.
|
||||
///
|
||||
/// Allocates the necessary memory to store all of the cached Merkle trees but does perform any
|
||||
/// hashing.
|
||||
pub fn new<T: EthSpec>(state: &BeaconState<T>) -> Self {
|
||||
let mut fixed_arena = CacheArena::default();
|
||||
let block_roots = state.block_roots.new_tree_hash_cache(&mut fixed_arena);
|
||||
let state_roots = state.state_roots.new_tree_hash_cache(&mut fixed_arena);
|
||||
let historical_roots = state.historical_roots.new_tree_hash_cache(&mut fixed_arena);
|
||||
let randao_mixes = state.randao_mixes.new_tree_hash_cache(&mut fixed_arena);
|
||||
|
||||
let validators = ValidatorsListTreeHashCache::new::<T>(&state.validators[..]);
|
||||
|
||||
let mut balances_arena = CacheArena::default();
|
||||
let balances = state.balances.new_tree_hash_cache(&mut balances_arena);
|
||||
|
||||
let mut slashings_arena = CacheArena::default();
|
||||
let slashings = state.slashings.new_tree_hash_cache(&mut slashings_arena);
|
||||
|
||||
Self {
|
||||
validators,
|
||||
fixed_arena,
|
||||
balances_arena,
|
||||
slashings_arena,
|
||||
block_roots,
|
||||
state_roots,
|
||||
historical_roots,
|
||||
balances,
|
||||
randao_mixes,
|
||||
slashings,
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the cache and returns the tree hash root for the given `state`.
|
||||
///
|
||||
/// The provided `state` should be a descendant of the last `state` given to this function, or
|
||||
/// the `Self::new` function.
|
||||
pub fn recalculate_tree_hash_root<T: EthSpec>(
|
||||
&mut self,
|
||||
state: &BeaconState<T>,
|
||||
) -> Result<Hash256, Error> {
|
||||
let mut hasher = MerkleHasher::with_leaves(NUM_BEACON_STATE_HASHING_FIELDS);
|
||||
|
||||
hasher.write(state.genesis_time.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.genesis_validators_root.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.slot.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.fork.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.latest_block_header.tree_hash_root().as_bytes())?;
|
||||
hasher.write(
|
||||
state
|
||||
.block_roots
|
||||
.recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.block_roots)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.state_roots
|
||||
.recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.state_roots)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.historical_roots
|
||||
.recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.historical_roots)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(state.eth1_data.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.eth1_data_votes.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.eth1_deposit_index.tree_hash_root().as_bytes())?;
|
||||
hasher.write(
|
||||
self.validators
|
||||
.recalculate_tree_hash_root(&state.validators[..])?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.balances
|
||||
.recalculate_tree_hash_root(&mut self.balances_arena, &mut self.balances)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.randao_mixes
|
||||
.recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.randao_mixes)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.slashings
|
||||
.recalculate_tree_hash_root(&mut self.slashings_arena, &mut self.slashings)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.previous_epoch_attestations
|
||||
.tree_hash_root()
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(state.current_epoch_attestations.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.justification_bits.tree_hash_root().as_bytes())?;
|
||||
hasher.write(
|
||||
state
|
||||
.previous_justified_checkpoint
|
||||
.tree_hash_root()
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.current_justified_checkpoint
|
||||
.tree_hash_root()
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(state.finalized_checkpoint.tree_hash_root().as_bytes())?;
|
||||
|
||||
hasher.finish().map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Updates the cache and provides the root of the given `validators`.
|
||||
pub fn recalculate_validators_tree_hash_root(
|
||||
&mut self,
|
||||
validators: &[Validator],
|
||||
) -> Result<Hash256, Error> {
|
||||
self.validators.recalculate_tree_hash_root(validators)
|
||||
}
|
||||
}
|
||||
|
||||
/// A specialized cache for computing the tree hash root of `state.validators`.
|
||||
#[derive(Debug, PartialEq, Clone, Default, Encode, Decode)]
|
||||
struct ValidatorsListTreeHashCache {
|
||||
list_arena: CacheArena,
|
||||
list_cache: TreeHashCache,
|
||||
values: ParallelValidatorTreeHash,
|
||||
}
|
||||
|
||||
impl ValidatorsListTreeHashCache {
|
||||
/// Instantiates a new cache.
|
||||
///
|
||||
/// Allocates the necessary memory to store all of the cached Merkle trees but does perform any
|
||||
/// hashing.
|
||||
fn new<E: EthSpec>(validators: &[Validator]) -> Self {
|
||||
let mut list_arena = CacheArena::default();
|
||||
Self {
|
||||
list_cache: TreeHashCache::new(
|
||||
&mut list_arena,
|
||||
int_log(E::ValidatorRegistryLimit::to_usize()),
|
||||
validators.len(),
|
||||
),
|
||||
list_arena,
|
||||
values: ParallelValidatorTreeHash::new::<E>(validators),
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the cache and returns the tree hash root for the given `state`.
|
||||
///
|
||||
/// This function makes assumptions that the `validators` list will only change in accordance
|
||||
/// with valid per-block/per-slot state transitions.
|
||||
fn recalculate_tree_hash_root(&mut self, validators: &[Validator]) -> Result<Hash256, Error> {
|
||||
let mut list_arena = std::mem::take(&mut self.list_arena);
|
||||
|
||||
let leaves = self
|
||||
.values
|
||||
.leaves(validators)?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|h| h.to_fixed_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let list_root = self
|
||||
.list_cache
|
||||
.recalculate_merkle_root(&mut list_arena, leaves.into_iter())?;
|
||||
|
||||
std::mem::replace(&mut self.list_arena, list_arena);
|
||||
|
||||
Ok(mix_in_length(&list_root, validators.len()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a cache for each of the `Validator` objects in `state.validators` and computes the
|
||||
/// roots of these using Rayon parallelization.
|
||||
#[derive(Debug, PartialEq, Clone, Default, Encode, Decode)]
|
||||
pub struct ParallelValidatorTreeHash {
|
||||
/// Each arena and its associated sub-trees.
|
||||
arenas: Vec<(CacheArena, Vec<TreeHashCache>)>,
|
||||
}
|
||||
|
||||
impl ParallelValidatorTreeHash {
|
||||
/// Instantiates a new cache.
|
||||
///
|
||||
/// Allocates the necessary memory to store all of the cached Merkle trees but does perform any
|
||||
/// hashing.
|
||||
fn new<E: EthSpec>(validators: &[Validator]) -> Self {
|
||||
let num_arenas = std::cmp::max(
|
||||
1,
|
||||
(validators.len() + VALIDATORS_PER_ARENA - 1) / VALIDATORS_PER_ARENA,
|
||||
);
|
||||
|
||||
let mut arenas = (1..=num_arenas)
|
||||
.map(|i| {
|
||||
let num_validators = if i == num_arenas {
|
||||
validators.len() % VALIDATORS_PER_ARENA
|
||||
} else {
|
||||
VALIDATORS_PER_ARENA
|
||||
};
|
||||
NODES_PER_VALIDATOR * num_validators
|
||||
})
|
||||
.map(|capacity| (CacheArena::with_capacity(capacity), vec![]))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
validators.iter().enumerate().for_each(|(i, v)| {
|
||||
let (arena, caches) = &mut arenas[i / VALIDATORS_PER_ARENA];
|
||||
caches.push(v.new_tree_hash_cache(arena))
|
||||
});
|
||||
|
||||
Self { arenas }
|
||||
}
|
||||
|
||||
/// Returns the number of validators stored in self.
|
||||
fn len(&self) -> usize {
|
||||
self.arenas.last().map_or(0, |last| {
|
||||
// Subtraction cannot underflow because `.last()` ensures the `.len() > 0`.
|
||||
(self.arenas.len() - 1) * VALIDATORS_PER_ARENA + last.1.len()
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates the caches for each `Validator` in `validators` and returns a list that maps 1:1
|
||||
/// with `validators` to the hash of each validator.
|
||||
///
|
||||
/// This function makes assumptions that the `validators` list will only change in accordance
|
||||
/// with valid per-block/per-slot state transitions.
|
||||
fn leaves(&mut self, validators: &[Validator]) -> Result<Vec<Vec<Hash256>>, Error> {
|
||||
if self.len() < validators.len() {
|
||||
validators.iter().skip(self.len()).for_each(|v| {
|
||||
if self
|
||||
.arenas
|
||||
.last()
|
||||
.map_or(true, |last| last.1.len() >= VALIDATORS_PER_ARENA)
|
||||
{
|
||||
let mut arena = CacheArena::default();
|
||||
let cache = v.new_tree_hash_cache(&mut arena);
|
||||
self.arenas.push((arena, vec![cache]))
|
||||
} else {
|
||||
let (arena, caches) = &mut self
|
||||
.arenas
|
||||
.last_mut()
|
||||
.expect("Cannot reach this block if arenas is empty.");
|
||||
caches.push(v.new_tree_hash_cache(arena))
|
||||
}
|
||||
})
|
||||
} else if validators.len() < self.len() {
|
||||
return Err(Error::ValidatorRegistryShrunk);
|
||||
}
|
||||
|
||||
self.arenas
|
||||
.par_iter_mut()
|
||||
.enumerate()
|
||||
.map(|(arena_index, (arena, caches))| {
|
||||
caches
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.map(move |(cache_index, cache)| {
|
||||
let val_index = (arena_index * VALIDATORS_PER_ARENA) + cache_index;
|
||||
|
||||
let validator = validators
|
||||
.get(val_index)
|
||||
.ok_or_else(|| Error::TreeHashCacheInconsistent)?;
|
||||
|
||||
validator
|
||||
.recalculate_tree_hash_root(arena, cache)
|
||||
.map_err(Error::CachedTreeHashError)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn validator_node_count() {
|
||||
let mut arena = CacheArena::default();
|
||||
let v = Validator::default();
|
||||
let _cache = v.new_tree_hash_cache(&mut arena);
|
||||
assert_eq!(arena.backing_len(), NODES_PER_VALIDATOR);
|
||||
}
|
||||
}
|
||||
782
consensus/types/src/chain_spec.rs
Normal file
782
consensus/types/src/chain_spec.rs
Normal file
@@ -0,0 +1,782 @@
|
||||
use crate::*;
|
||||
use int_to_bytes::int_to_bytes4;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use tree_hash::TreeHash;
|
||||
use utils::{
|
||||
fork_from_hex_str, fork_to_hex_str, u32_from_hex_str, u32_to_hex_str, u8_from_hex_str,
|
||||
u8_to_hex_str,
|
||||
};
|
||||
|
||||
/// Each of the BLS signature domains.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum Domain {
|
||||
BeaconProposer,
|
||||
BeaconAttester,
|
||||
Randao,
|
||||
Deposit,
|
||||
VoluntaryExit,
|
||||
SelectionProof,
|
||||
AggregateAndProof,
|
||||
}
|
||||
|
||||
/// Holds all the "constants" for a BeaconChain.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ChainSpec {
|
||||
/*
|
||||
* Constants
|
||||
*/
|
||||
pub genesis_slot: Slot,
|
||||
#[serde(skip_serializing)] // skipped because Serde TOML has trouble with u64::max
|
||||
pub far_future_epoch: Epoch,
|
||||
pub base_rewards_per_epoch: u64,
|
||||
pub deposit_contract_tree_depth: u64,
|
||||
|
||||
/*
|
||||
* Misc
|
||||
*/
|
||||
pub max_committees_per_slot: usize,
|
||||
pub target_committee_size: usize,
|
||||
pub min_per_epoch_churn_limit: u64,
|
||||
pub churn_limit_quotient: u64,
|
||||
pub shuffle_round_count: u8,
|
||||
pub min_genesis_active_validator_count: u64,
|
||||
pub min_genesis_time: u64,
|
||||
pub hysteresis_quotient: u64,
|
||||
pub hysteresis_downward_multiplier: u64,
|
||||
pub hysteresis_upward_multiplier: u64,
|
||||
|
||||
/*
|
||||
* Gwei values
|
||||
*/
|
||||
pub min_deposit_amount: u64,
|
||||
pub max_effective_balance: u64,
|
||||
pub ejection_balance: u64,
|
||||
pub effective_balance_increment: u64,
|
||||
|
||||
/*
|
||||
* Initial Values
|
||||
*/
|
||||
#[serde(
|
||||
serialize_with = "fork_to_hex_str",
|
||||
deserialize_with = "fork_from_hex_str"
|
||||
)]
|
||||
pub genesis_fork_version: [u8; 4],
|
||||
#[serde(deserialize_with = "u8_from_hex_str", serialize_with = "u8_to_hex_str")]
|
||||
pub bls_withdrawal_prefix_byte: u8,
|
||||
|
||||
/*
|
||||
* Time parameters
|
||||
*/
|
||||
pub min_genesis_delay: u64,
|
||||
pub milliseconds_per_slot: u64,
|
||||
pub min_attestation_inclusion_delay: u64,
|
||||
pub min_seed_lookahead: Epoch,
|
||||
pub max_seed_lookahead: Epoch,
|
||||
pub min_epochs_to_inactivity_penalty: u64,
|
||||
pub min_validator_withdrawability_delay: Epoch,
|
||||
pub persistent_committee_period: u64,
|
||||
|
||||
/*
|
||||
* Reward and penalty quotients
|
||||
*/
|
||||
pub base_reward_factor: u64,
|
||||
pub whistleblower_reward_quotient: u64,
|
||||
pub proposer_reward_quotient: u64,
|
||||
pub inactivity_penalty_quotient: u64,
|
||||
pub min_slashing_penalty_quotient: u64,
|
||||
|
||||
/*
|
||||
* Signature domains
|
||||
*/
|
||||
domain_beacon_proposer: u32,
|
||||
domain_beacon_attester: u32,
|
||||
domain_randao: u32,
|
||||
domain_deposit: u32,
|
||||
domain_voluntary_exit: u32,
|
||||
domain_selection_proof: u32,
|
||||
domain_aggregate_and_proof: u32,
|
||||
|
||||
/*
|
||||
* Fork choice
|
||||
*/
|
||||
pub safe_slots_to_update_justified: u64,
|
||||
|
||||
/*
|
||||
* Eth1
|
||||
*/
|
||||
pub eth1_follow_distance: u64,
|
||||
pub seconds_per_eth1_block: u64,
|
||||
|
||||
/*
|
||||
* Networking
|
||||
*/
|
||||
pub boot_nodes: Vec<String>,
|
||||
pub network_id: u8,
|
||||
pub attestation_propagation_slot_range: u64,
|
||||
pub maximum_gossip_clock_disparity_millis: u64,
|
||||
pub target_aggregators_per_committee: u64,
|
||||
pub attestation_subnet_count: u64,
|
||||
pub random_subnets_per_validator: u64,
|
||||
pub epochs_per_random_subnet_subscription: u64,
|
||||
}
|
||||
|
||||
impl ChainSpec {
|
||||
/// Returns an `EnrForkId` for the given `slot`.
|
||||
///
|
||||
/// Presently, we don't have any forks so we just ignore the slot. In the future this function
|
||||
/// may return something different based upon the slot.
|
||||
pub fn enr_fork_id(&self, _slot: Slot, genesis_validators_root: Hash256) -> EnrForkId {
|
||||
EnrForkId {
|
||||
fork_digest: Self::compute_fork_digest(
|
||||
self.genesis_fork_version,
|
||||
genesis_validators_root,
|
||||
),
|
||||
next_fork_version: self.genesis_fork_version,
|
||||
next_fork_epoch: self.far_future_epoch,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the epoch of the next scheduled change in the `fork.current_version`.
|
||||
///
|
||||
/// There are no future forks scheduled so this function always returns `None`. This may not
|
||||
/// always be the case in the future, though.
|
||||
pub fn next_fork_epoch(&self) -> Option<Epoch> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the domain number, unmodified by the fork.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn get_domain_constant(&self, domain: Domain) -> u32 {
|
||||
match domain {
|
||||
Domain::BeaconProposer => self.domain_beacon_proposer,
|
||||
Domain::BeaconAttester => self.domain_beacon_attester,
|
||||
Domain::Randao => self.domain_randao,
|
||||
Domain::Deposit => self.domain_deposit,
|
||||
Domain::VoluntaryExit => self.domain_voluntary_exit,
|
||||
Domain::SelectionProof => self.domain_selection_proof,
|
||||
Domain::AggregateAndProof => self.domain_aggregate_and_proof,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the domain that represents the fork meta and signature domain.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn get_domain(
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
domain: Domain,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
) -> Hash256 {
|
||||
let fork_version = fork.get_fork_version(epoch);
|
||||
self.compute_domain(domain, fork_version, genesis_validators_root)
|
||||
}
|
||||
|
||||
/// Get the domain for a deposit signature.
|
||||
///
|
||||
/// Deposits are valid across forks, thus the deposit domain is computed
|
||||
/// with the genesis fork version.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn get_deposit_domain(&self) -> Hash256 {
|
||||
self.compute_domain(Domain::Deposit, self.genesis_fork_version, Hash256::zero())
|
||||
}
|
||||
|
||||
/// Return the 32-byte fork data root for the `current_version` and `genesis_validators_root`.
|
||||
///
|
||||
/// This is used primarily in signature domains to avoid collisions across forks/chains.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn compute_fork_data_root(
|
||||
current_version: [u8; 4],
|
||||
genesis_validators_root: Hash256,
|
||||
) -> Hash256 {
|
||||
ForkData {
|
||||
current_version,
|
||||
genesis_validators_root,
|
||||
}
|
||||
.tree_hash_root()
|
||||
}
|
||||
|
||||
/// Return the 4-byte fork digest for the `current_version` and `genesis_validators_root`.
|
||||
///
|
||||
/// This is a digest primarily used for domain separation on the p2p layer.
|
||||
/// 4-bytes suffices for practical separation of forks/chains.
|
||||
pub fn compute_fork_digest(
|
||||
current_version: [u8; 4],
|
||||
genesis_validators_root: Hash256,
|
||||
) -> [u8; 4] {
|
||||
let mut result = [0; 4];
|
||||
let root = Self::compute_fork_data_root(current_version, genesis_validators_root);
|
||||
result.copy_from_slice(&root.as_bytes()[0..4]);
|
||||
result
|
||||
}
|
||||
|
||||
/// Compute a domain by applying the given `fork_version`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn compute_domain(
|
||||
&self,
|
||||
domain: Domain,
|
||||
fork_version: [u8; 4],
|
||||
genesis_validators_root: Hash256,
|
||||
) -> Hash256 {
|
||||
let domain_constant = self.get_domain_constant(domain);
|
||||
|
||||
let mut domain = [0; 32];
|
||||
domain[0..4].copy_from_slice(&int_to_bytes4(domain_constant));
|
||||
domain[4..].copy_from_slice(
|
||||
&Self::compute_fork_data_root(fork_version, genesis_validators_root)[..28],
|
||||
);
|
||||
|
||||
Hash256::from(domain)
|
||||
}
|
||||
|
||||
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn mainnet() -> Self {
|
||||
Self {
|
||||
/*
|
||||
* Constants
|
||||
*/
|
||||
genesis_slot: Slot::new(0),
|
||||
far_future_epoch: Epoch::new(u64::max_value()),
|
||||
base_rewards_per_epoch: 4,
|
||||
deposit_contract_tree_depth: 32,
|
||||
|
||||
/*
|
||||
* Misc
|
||||
*/
|
||||
max_committees_per_slot: 64,
|
||||
target_committee_size: 128,
|
||||
min_per_epoch_churn_limit: 4,
|
||||
churn_limit_quotient: 65_536,
|
||||
shuffle_round_count: 90,
|
||||
min_genesis_active_validator_count: 16_384,
|
||||
min_genesis_time: 1_578_009_600, // Jan 3, 2020
|
||||
hysteresis_quotient: 4,
|
||||
hysteresis_downward_multiplier: 1,
|
||||
hysteresis_upward_multiplier: 5,
|
||||
|
||||
/*
|
||||
* Gwei values
|
||||
*/
|
||||
min_deposit_amount: u64::pow(2, 0).saturating_mul(u64::pow(10, 9)),
|
||||
max_effective_balance: u64::pow(2, 5).saturating_mul(u64::pow(10, 9)),
|
||||
ejection_balance: u64::pow(2, 4).saturating_mul(u64::pow(10, 9)),
|
||||
effective_balance_increment: u64::pow(2, 0).saturating_mul(u64::pow(10, 9)),
|
||||
|
||||
/*
|
||||
* Initial Values
|
||||
*/
|
||||
genesis_fork_version: [0; 4],
|
||||
bls_withdrawal_prefix_byte: 0,
|
||||
|
||||
/*
|
||||
* Time parameters
|
||||
*/
|
||||
min_genesis_delay: 86400, // 1 day
|
||||
milliseconds_per_slot: 12_000,
|
||||
min_attestation_inclusion_delay: 1,
|
||||
min_seed_lookahead: Epoch::new(1),
|
||||
max_seed_lookahead: Epoch::new(4),
|
||||
min_epochs_to_inactivity_penalty: 4,
|
||||
min_validator_withdrawability_delay: Epoch::new(256),
|
||||
persistent_committee_period: 2_048,
|
||||
|
||||
/*
|
||||
* Reward and penalty quotients
|
||||
*/
|
||||
base_reward_factor: 64,
|
||||
whistleblower_reward_quotient: 512,
|
||||
proposer_reward_quotient: 8,
|
||||
inactivity_penalty_quotient: 33_554_432,
|
||||
min_slashing_penalty_quotient: 32,
|
||||
|
||||
/*
|
||||
* Signature domains
|
||||
*/
|
||||
domain_beacon_proposer: 0,
|
||||
domain_beacon_attester: 1,
|
||||
domain_randao: 2,
|
||||
domain_deposit: 3,
|
||||
domain_voluntary_exit: 4,
|
||||
domain_selection_proof: 5,
|
||||
domain_aggregate_and_proof: 6,
|
||||
|
||||
/*
|
||||
* Fork choice
|
||||
*/
|
||||
safe_slots_to_update_justified: 8,
|
||||
|
||||
/*
|
||||
* Eth1
|
||||
*/
|
||||
eth1_follow_distance: 1_024,
|
||||
seconds_per_eth1_block: 14,
|
||||
|
||||
/*
|
||||
* Network specific
|
||||
*/
|
||||
boot_nodes: vec![],
|
||||
network_id: 1, // mainnet network id
|
||||
attestation_propagation_slot_range: 32,
|
||||
attestation_subnet_count: 64,
|
||||
random_subnets_per_validator: 1,
|
||||
maximum_gossip_clock_disparity_millis: 500,
|
||||
target_aggregators_per_committee: 16,
|
||||
epochs_per_random_subnet_subscription: 256,
|
||||
}
|
||||
}
|
||||
|
||||
/// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn minimal() -> Self {
|
||||
// Note: bootnodes to be updated when static nodes exist.
|
||||
let boot_nodes = vec![];
|
||||
|
||||
Self {
|
||||
max_committees_per_slot: 4,
|
||||
target_committee_size: 4,
|
||||
shuffle_round_count: 10,
|
||||
min_genesis_active_validator_count: 64,
|
||||
eth1_follow_distance: 16,
|
||||
genesis_fork_version: [0x00, 0x00, 0x00, 0x01],
|
||||
persistent_committee_period: 128,
|
||||
min_genesis_delay: 300,
|
||||
milliseconds_per_slot: 6_000,
|
||||
safe_slots_to_update_justified: 2,
|
||||
network_id: 2, // lighthouse testnet network id
|
||||
boot_nodes,
|
||||
..ChainSpec::mainnet()
|
||||
}
|
||||
}
|
||||
|
||||
/// Interop testing spec
|
||||
///
|
||||
/// This allows us to customize a chain spec for interop testing.
|
||||
pub fn interop() -> Self {
|
||||
let boot_nodes = vec![];
|
||||
|
||||
Self {
|
||||
milliseconds_per_slot: 12_000,
|
||||
target_committee_size: 4,
|
||||
shuffle_round_count: 10,
|
||||
network_id: 13,
|
||||
boot_nodes,
|
||||
..ChainSpec::mainnet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChainSpec {
|
||||
fn default() -> Self {
|
||||
Self::mainnet()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mainnet_spec_can_be_constructed() {
|
||||
let _ = ChainSpec::mainnet();
|
||||
}
|
||||
|
||||
fn test_domain(domain_type: Domain, raw_domain: u32, spec: &ChainSpec) {
|
||||
let previous_version = [0, 0, 0, 1];
|
||||
let current_version = [0, 0, 0, 2];
|
||||
let genesis_validators_root = Hash256::from_low_u64_le(77);
|
||||
let fork_epoch = Epoch::new(1024);
|
||||
let fork = Fork {
|
||||
previous_version,
|
||||
current_version,
|
||||
epoch: fork_epoch,
|
||||
};
|
||||
|
||||
for (epoch, version) in vec![
|
||||
(fork_epoch - 1, previous_version),
|
||||
(fork_epoch, current_version),
|
||||
(fork_epoch + 1, current_version),
|
||||
] {
|
||||
let domain1 = spec.get_domain(epoch, domain_type, &fork, genesis_validators_root);
|
||||
let domain2 = spec.compute_domain(domain_type, version, genesis_validators_root);
|
||||
|
||||
assert_eq!(domain1, domain2);
|
||||
assert_eq!(&domain1.as_bytes()[0..4], &int_to_bytes4(raw_domain)[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_domain() {
|
||||
let spec = ChainSpec::mainnet();
|
||||
|
||||
test_domain(Domain::BeaconProposer, spec.domain_beacon_proposer, &spec);
|
||||
test_domain(Domain::BeaconAttester, spec.domain_beacon_attester, &spec);
|
||||
test_domain(Domain::Randao, spec.domain_randao, &spec);
|
||||
test_domain(Domain::Deposit, spec.domain_deposit, &spec);
|
||||
test_domain(Domain::VoluntaryExit, spec.domain_voluntary_exit, &spec);
|
||||
test_domain(Domain::SelectionProof, spec.domain_selection_proof, &spec);
|
||||
test_domain(
|
||||
Domain::AggregateAndProof,
|
||||
spec.domain_aggregate_and_proof,
|
||||
&spec,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Union of a ChainSpec struct and an EthSpec struct that holds constants used for the configs
|
||||
/// from the Ethereum 2 specs repo (https://github.com/ethereum/eth2.0-specs/tree/dev/configs)
|
||||
///
|
||||
/// Doesn't include fields of the YAML that we don't need yet (e.g. Phase 1 stuff).
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
// Yaml Config is declared here in order to access domain fields of ChainSpec which are private.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
#[serde(default)]
|
||||
pub struct YamlConfig {
|
||||
// ChainSpec
|
||||
far_future_epoch: u64,
|
||||
base_rewards_per_epoch: u64,
|
||||
deposit_contract_tree_depth: u64,
|
||||
max_committees_per_slot: usize,
|
||||
target_committee_size: usize,
|
||||
min_per_epoch_churn_limit: u64,
|
||||
churn_limit_quotient: u64,
|
||||
shuffle_round_count: u8,
|
||||
min_genesis_active_validator_count: u64,
|
||||
min_genesis_time: u64,
|
||||
min_genesis_delay: u64,
|
||||
min_deposit_amount: u64,
|
||||
max_effective_balance: u64,
|
||||
ejection_balance: u64,
|
||||
effective_balance_increment: u64,
|
||||
hysteresis_quotient: u64,
|
||||
hysteresis_downward_multiplier: u64,
|
||||
hysteresis_upward_multiplier: u64,
|
||||
genesis_slot: u64,
|
||||
#[serde(
|
||||
serialize_with = "fork_to_hex_str",
|
||||
deserialize_with = "fork_from_hex_str"
|
||||
)]
|
||||
genesis_fork_version: [u8; 4],
|
||||
#[serde(deserialize_with = "u8_from_hex_str", serialize_with = "u8_to_hex_str")]
|
||||
bls_withdrawal_prefix: u8,
|
||||
seconds_per_slot: u64,
|
||||
min_attestation_inclusion_delay: u64,
|
||||
min_seed_lookahead: u64,
|
||||
max_seed_lookahead: u64,
|
||||
min_epochs_to_inactivity_penalty: u64,
|
||||
min_validator_withdrawability_delay: u64,
|
||||
persistent_committee_period: u64,
|
||||
base_reward_factor: u64,
|
||||
whistleblower_reward_quotient: u64,
|
||||
proposer_reward_quotient: u64,
|
||||
inactivity_penalty_quotient: u64,
|
||||
min_slashing_penalty_quotient: u64,
|
||||
safe_slots_to_update_justified: u64,
|
||||
|
||||
#[serde(
|
||||
deserialize_with = "u32_from_hex_str",
|
||||
serialize_with = "u32_to_hex_str"
|
||||
)]
|
||||
domain_beacon_proposer: u32,
|
||||
#[serde(
|
||||
deserialize_with = "u32_from_hex_str",
|
||||
serialize_with = "u32_to_hex_str"
|
||||
)]
|
||||
domain_beacon_attester: u32,
|
||||
#[serde(
|
||||
deserialize_with = "u32_from_hex_str",
|
||||
serialize_with = "u32_to_hex_str"
|
||||
)]
|
||||
domain_randao: u32,
|
||||
#[serde(
|
||||
deserialize_with = "u32_from_hex_str",
|
||||
serialize_with = "u32_to_hex_str"
|
||||
)]
|
||||
domain_deposit: u32,
|
||||
#[serde(
|
||||
deserialize_with = "u32_from_hex_str",
|
||||
serialize_with = "u32_to_hex_str"
|
||||
)]
|
||||
domain_voluntary_exit: u32,
|
||||
#[serde(
|
||||
deserialize_with = "u32_from_hex_str",
|
||||
serialize_with = "u32_to_hex_str"
|
||||
)]
|
||||
domain_selection_proof: u32,
|
||||
#[serde(
|
||||
deserialize_with = "u32_from_hex_str",
|
||||
serialize_with = "u32_to_hex_str"
|
||||
)]
|
||||
domain_aggregate_and_proof: u32,
|
||||
#[serde(
|
||||
deserialize_with = "u32_from_hex_str",
|
||||
serialize_with = "u32_to_hex_str"
|
||||
)]
|
||||
// EthSpec
|
||||
justification_bits_length: u32,
|
||||
max_validators_per_committee: u32,
|
||||
genesis_epoch: Epoch,
|
||||
slots_per_epoch: u64,
|
||||
epochs_per_eth1_voting_period: u64,
|
||||
slots_per_historical_root: usize,
|
||||
epochs_per_historical_vector: usize,
|
||||
epochs_per_slashings_vector: usize,
|
||||
historical_roots_limit: u64,
|
||||
validator_registry_limit: u64,
|
||||
max_proposer_slashings: u32,
|
||||
max_attester_slashings: u32,
|
||||
max_attestations: u32,
|
||||
max_deposits: u32,
|
||||
max_voluntary_exits: u32,
|
||||
|
||||
// Validator
|
||||
eth1_follow_distance: u64,
|
||||
target_aggregators_per_committee: u64,
|
||||
random_subnets_per_validator: u64,
|
||||
epochs_per_random_subnet_subscription: u64,
|
||||
seconds_per_eth1_block: u64,
|
||||
}
|
||||
|
||||
impl Default for YamlConfig {
|
||||
fn default() -> Self {
|
||||
let chain_spec = MainnetEthSpec::default_spec();
|
||||
YamlConfig::from_spec::<MainnetEthSpec>(&chain_spec)
|
||||
}
|
||||
}
|
||||
|
||||
/// Spec v0.11.1
|
||||
impl YamlConfig {
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
pub fn from_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
// ChainSpec
|
||||
far_future_epoch: spec.far_future_epoch.into(),
|
||||
base_rewards_per_epoch: spec.base_rewards_per_epoch,
|
||||
deposit_contract_tree_depth: spec.deposit_contract_tree_depth,
|
||||
max_committees_per_slot: spec.max_committees_per_slot,
|
||||
target_committee_size: spec.target_committee_size,
|
||||
min_per_epoch_churn_limit: spec.min_per_epoch_churn_limit,
|
||||
churn_limit_quotient: spec.churn_limit_quotient,
|
||||
shuffle_round_count: spec.shuffle_round_count,
|
||||
min_genesis_active_validator_count: spec.min_genesis_active_validator_count,
|
||||
min_genesis_time: spec.min_genesis_time,
|
||||
min_genesis_delay: spec.min_genesis_delay,
|
||||
min_deposit_amount: spec.min_deposit_amount,
|
||||
max_effective_balance: spec.max_effective_balance,
|
||||
ejection_balance: spec.ejection_balance,
|
||||
effective_balance_increment: spec.effective_balance_increment,
|
||||
hysteresis_quotient: spec.hysteresis_quotient,
|
||||
hysteresis_downward_multiplier: spec.hysteresis_downward_multiplier,
|
||||
hysteresis_upward_multiplier: spec.hysteresis_upward_multiplier,
|
||||
genesis_slot: spec.genesis_slot.into(),
|
||||
bls_withdrawal_prefix: spec.bls_withdrawal_prefix_byte,
|
||||
seconds_per_slot: spec.milliseconds_per_slot / 1000,
|
||||
min_attestation_inclusion_delay: spec.min_attestation_inclusion_delay,
|
||||
min_seed_lookahead: spec.min_seed_lookahead.into(),
|
||||
max_seed_lookahead: spec.max_seed_lookahead.into(),
|
||||
min_validator_withdrawability_delay: spec.min_validator_withdrawability_delay.into(),
|
||||
persistent_committee_period: spec.persistent_committee_period,
|
||||
min_epochs_to_inactivity_penalty: spec.min_epochs_to_inactivity_penalty,
|
||||
base_reward_factor: spec.base_reward_factor,
|
||||
whistleblower_reward_quotient: spec.whistleblower_reward_quotient,
|
||||
proposer_reward_quotient: spec.proposer_reward_quotient,
|
||||
inactivity_penalty_quotient: spec.inactivity_penalty_quotient,
|
||||
min_slashing_penalty_quotient: spec.min_slashing_penalty_quotient,
|
||||
genesis_fork_version: spec.genesis_fork_version,
|
||||
safe_slots_to_update_justified: spec.safe_slots_to_update_justified,
|
||||
domain_beacon_proposer: spec.domain_beacon_proposer,
|
||||
domain_beacon_attester: spec.domain_beacon_attester,
|
||||
domain_randao: spec.domain_randao,
|
||||
domain_deposit: spec.domain_deposit,
|
||||
domain_voluntary_exit: spec.domain_voluntary_exit,
|
||||
domain_selection_proof: spec.domain_selection_proof,
|
||||
domain_aggregate_and_proof: spec.domain_aggregate_and_proof,
|
||||
|
||||
// EthSpec
|
||||
justification_bits_length: T::JustificationBitsLength::to_u32(),
|
||||
max_validators_per_committee: T::MaxValidatorsPerCommittee::to_u32(),
|
||||
genesis_epoch: T::genesis_epoch(),
|
||||
slots_per_epoch: T::slots_per_epoch(),
|
||||
epochs_per_eth1_voting_period: T::EpochsPerEth1VotingPeriod::to_u64(),
|
||||
slots_per_historical_root: T::slots_per_historical_root(),
|
||||
epochs_per_historical_vector: T::epochs_per_historical_vector(),
|
||||
epochs_per_slashings_vector: T::EpochsPerSlashingsVector::to_usize(),
|
||||
historical_roots_limit: T::HistoricalRootsLimit::to_u64(),
|
||||
validator_registry_limit: T::ValidatorRegistryLimit::to_u64(),
|
||||
max_proposer_slashings: T::MaxProposerSlashings::to_u32(),
|
||||
max_attester_slashings: T::MaxAttesterSlashings::to_u32(),
|
||||
max_attestations: T::MaxAttestations::to_u32(),
|
||||
max_deposits: T::MaxDeposits::to_u32(),
|
||||
max_voluntary_exits: T::MaxVoluntaryExits::to_u32(),
|
||||
|
||||
// Validator
|
||||
eth1_follow_distance: spec.eth1_follow_distance,
|
||||
target_aggregators_per_committee: spec.target_aggregators_per_committee,
|
||||
random_subnets_per_validator: spec.random_subnets_per_validator,
|
||||
epochs_per_random_subnet_subscription: spec.epochs_per_random_subnet_subscription,
|
||||
seconds_per_eth1_block: spec.seconds_per_eth1_block,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_file(filename: &Path) -> Result<Self, String> {
|
||||
let f = File::open(filename)
|
||||
.map_err(|e| format!("Error opening spec at {}: {:?}", filename.display(), e))?;
|
||||
serde_yaml::from_reader(f)
|
||||
.map_err(|e| format!("Error parsing spec at {}: {:?}", filename.display(), e))
|
||||
}
|
||||
|
||||
pub fn apply_to_chain_spec<T: EthSpec>(&self, chain_spec: &ChainSpec) -> Option<ChainSpec> {
|
||||
// Checking for EthSpec constants
|
||||
if self.justification_bits_length != T::JustificationBitsLength::to_u32()
|
||||
|| self.max_validators_per_committee != T::MaxValidatorsPerCommittee::to_u32()
|
||||
|| self.genesis_epoch != T::genesis_epoch()
|
||||
|| self.slots_per_epoch != T::slots_per_epoch()
|
||||
|| self.epochs_per_eth1_voting_period != T::EpochsPerEth1VotingPeriod::to_u64()
|
||||
|| self.slots_per_historical_root != T::slots_per_historical_root()
|
||||
|| self.epochs_per_historical_vector != T::epochs_per_historical_vector()
|
||||
|| self.epochs_per_slashings_vector != T::EpochsPerSlashingsVector::to_usize()
|
||||
|| self.historical_roots_limit != T::HistoricalRootsLimit::to_u64()
|
||||
|| self.validator_registry_limit != T::ValidatorRegistryLimit::to_u64()
|
||||
|| self.max_proposer_slashings != T::MaxProposerSlashings::to_u32()
|
||||
|| self.max_attester_slashings != T::MaxAttesterSlashings::to_u32()
|
||||
|| self.max_attestations != T::MaxAttestations::to_u32()
|
||||
|| self.max_deposits != T::MaxDeposits::to_u32()
|
||||
|| self.max_voluntary_exits != T::MaxVoluntaryExits::to_u32()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create a ChainSpec from the yaml config
|
||||
Some(ChainSpec {
|
||||
far_future_epoch: Epoch::from(self.far_future_epoch),
|
||||
base_rewards_per_epoch: self.base_rewards_per_epoch,
|
||||
deposit_contract_tree_depth: self.deposit_contract_tree_depth,
|
||||
target_committee_size: self.target_committee_size,
|
||||
min_per_epoch_churn_limit: self.min_per_epoch_churn_limit,
|
||||
churn_limit_quotient: self.churn_limit_quotient,
|
||||
shuffle_round_count: self.shuffle_round_count,
|
||||
min_genesis_active_validator_count: self.min_genesis_active_validator_count,
|
||||
min_genesis_time: self.min_genesis_time,
|
||||
min_deposit_amount: self.min_deposit_amount,
|
||||
min_genesis_delay: self.min_genesis_delay,
|
||||
max_effective_balance: self.max_effective_balance,
|
||||
hysteresis_quotient: self.hysteresis_quotient,
|
||||
hysteresis_downward_multiplier: self.hysteresis_downward_multiplier,
|
||||
hysteresis_upward_multiplier: self.hysteresis_upward_multiplier,
|
||||
ejection_balance: self.ejection_balance,
|
||||
effective_balance_increment: self.effective_balance_increment,
|
||||
genesis_slot: Slot::from(self.genesis_slot),
|
||||
bls_withdrawal_prefix_byte: self.bls_withdrawal_prefix,
|
||||
milliseconds_per_slot: self.seconds_per_slot.saturating_mul(1000),
|
||||
min_attestation_inclusion_delay: self.min_attestation_inclusion_delay,
|
||||
min_seed_lookahead: Epoch::from(self.min_seed_lookahead),
|
||||
max_seed_lookahead: Epoch::from(self.max_seed_lookahead),
|
||||
min_validator_withdrawability_delay: Epoch::from(
|
||||
self.min_validator_withdrawability_delay,
|
||||
),
|
||||
persistent_committee_period: self.persistent_committee_period,
|
||||
min_epochs_to_inactivity_penalty: self.min_epochs_to_inactivity_penalty,
|
||||
base_reward_factor: self.base_reward_factor,
|
||||
whistleblower_reward_quotient: self.whistleblower_reward_quotient,
|
||||
proposer_reward_quotient: self.proposer_reward_quotient,
|
||||
inactivity_penalty_quotient: self.inactivity_penalty_quotient,
|
||||
min_slashing_penalty_quotient: self.min_slashing_penalty_quotient,
|
||||
domain_beacon_proposer: self.domain_beacon_proposer,
|
||||
domain_beacon_attester: self.domain_beacon_attester,
|
||||
domain_randao: self.domain_randao,
|
||||
domain_deposit: self.domain_deposit,
|
||||
domain_voluntary_exit: self.domain_voluntary_exit,
|
||||
boot_nodes: chain_spec.boot_nodes.clone(),
|
||||
genesis_fork_version: self.genesis_fork_version,
|
||||
eth1_follow_distance: self.eth1_follow_distance,
|
||||
..*chain_spec
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod yaml_tests {
|
||||
use super::*;
|
||||
use std::fs::OpenOptions;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn minimal_round_trip() {
|
||||
// create temp file
|
||||
let tmp_file = NamedTempFile::new().expect("failed to create temp file");
|
||||
let writer = OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.open(tmp_file.as_ref())
|
||||
.expect("error opening file");
|
||||
let minimal_spec = ChainSpec::minimal();
|
||||
|
||||
let yamlconfig = YamlConfig::from_spec::<MinimalEthSpec>(&minimal_spec);
|
||||
// write fresh minimal config to file
|
||||
serde_yaml::to_writer(writer, &yamlconfig).expect("failed to write or serialize");
|
||||
|
||||
let reader = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(false)
|
||||
.open(tmp_file.as_ref())
|
||||
.expect("error while opening the file");
|
||||
// deserialize minimal config from file
|
||||
let from: YamlConfig = serde_yaml::from_reader(reader).expect("error while deserializing");
|
||||
assert_eq!(from, yamlconfig);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mainnet_round_trip() {
|
||||
let tmp_file = NamedTempFile::new().expect("failed to create temp file");
|
||||
let writer = OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.open(tmp_file.as_ref())
|
||||
.expect("error opening file");
|
||||
let mainnet_spec = ChainSpec::mainnet();
|
||||
let yamlconfig = YamlConfig::from_spec::<MainnetEthSpec>(&mainnet_spec);
|
||||
serde_yaml::to_writer(writer, &yamlconfig).expect("failed to write or serialize");
|
||||
|
||||
let reader = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(false)
|
||||
.open(tmp_file.as_ref())
|
||||
.expect("error while opening the file");
|
||||
let from: YamlConfig = serde_yaml::from_reader(reader).expect("error while deserializing");
|
||||
assert_eq!(from, yamlconfig);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_to_spec() {
|
||||
let mut spec = ChainSpec::minimal();
|
||||
let yamlconfig = YamlConfig::from_spec::<MinimalEthSpec>(&spec);
|
||||
|
||||
// modifying the original spec
|
||||
spec.deposit_contract_tree_depth += 1;
|
||||
// Applying a yaml config with incorrect EthSpec should fail
|
||||
let res = yamlconfig.apply_to_chain_spec::<MainnetEthSpec>(&spec);
|
||||
assert_eq!(res, None);
|
||||
|
||||
// Applying a yaml config with correct EthSpec should NOT fail
|
||||
let new_spec = yamlconfig
|
||||
.apply_to_chain_spec::<MinimalEthSpec>(&spec)
|
||||
.expect("should have applied spec");
|
||||
assert_eq!(new_spec, ChainSpec::minimal());
|
||||
}
|
||||
}
|
||||
36
consensus/types/src/checkpoint.rs
Normal file
36
consensus/types/src/checkpoint.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{Epoch, Hash256};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Casper FFG checkpoint, used in attestations.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Default,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Encode,
|
||||
Decode,
|
||||
TreeHash,
|
||||
TestRandom,
|
||||
)]
|
||||
pub struct Checkpoint {
|
||||
pub epoch: Epoch,
|
||||
pub root: Hash256,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(Checkpoint);
|
||||
}
|
||||
26
consensus/types/src/deposit.rs
Normal file
26
consensus/types/src/deposit.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use ssz_types::typenum::U33;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
pub const DEPOSIT_TREE_DEPTH: usize = 32;
|
||||
|
||||
/// A deposit to potentially become a beacon chain validator.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct Deposit {
|
||||
pub proof: FixedVector<Hash256, U33>,
|
||||
pub data: DepositData,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(Deposit);
|
||||
}
|
||||
50
consensus/types/src/deposit_data.rs
Normal file
50
consensus/types/src/deposit_data.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
|
||||
use bls::{PublicKeyBytes, SignatureBytes};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// The data supplied by the user to the deposit contract.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct DepositData {
|
||||
pub pubkey: PublicKeyBytes,
|
||||
pub withdrawal_credentials: Hash256,
|
||||
pub amount: u64,
|
||||
pub signature: SignatureBytes,
|
||||
}
|
||||
|
||||
impl DepositData {
|
||||
/// Create a `DepositMessage` corresponding to this `DepositData`, for signature verification.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn as_deposit_message(&self) -> DepositMessage {
|
||||
DepositMessage {
|
||||
pubkey: self.pubkey.clone(),
|
||||
withdrawal_credentials: self.withdrawal_credentials,
|
||||
amount: self.amount,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the signature for a given DepositData details.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn create_signature(&self, secret_key: &SecretKey, spec: &ChainSpec) -> SignatureBytes {
|
||||
let domain = spec.get_deposit_domain();
|
||||
let msg = self.as_deposit_message().signing_root(domain);
|
||||
|
||||
SignatureBytes::from(Signature::new(msg.as_bytes(), secret_key))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(DepositData);
|
||||
}
|
||||
28
consensus/types/src/deposit_message.rs
Normal file
28
consensus/types/src/deposit_message.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
|
||||
use bls::PublicKeyBytes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// The data supplied by the user to the deposit contract.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct DepositMessage {
|
||||
pub pubkey: PublicKeyBytes,
|
||||
pub withdrawal_credentials: Hash256,
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
impl SignedRoot for DepositMessage {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(DepositMessage);
|
||||
}
|
||||
37
consensus/types/src/enr_fork_id.rs
Normal file
37
consensus/types/src/enr_fork_id.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::utils::{fork_from_hex_str, fork_to_hex_str};
|
||||
use crate::Epoch;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Specifies a fork which allows nodes to identify each other on the network. This fork is used in
|
||||
/// a nodes local ENR.
|
||||
///
|
||||
/// Spec v0.11
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom,
|
||||
)]
|
||||
pub struct EnrForkId {
|
||||
#[serde(
|
||||
serialize_with = "fork_to_hex_str",
|
||||
deserialize_with = "fork_from_hex_str"
|
||||
)]
|
||||
pub fork_digest: [u8; 4],
|
||||
#[serde(
|
||||
serialize_with = "fork_to_hex_str",
|
||||
deserialize_with = "fork_from_hex_str"
|
||||
)]
|
||||
pub next_fork_version: [u8; 4],
|
||||
pub next_fork_epoch: Epoch,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(EnrForkId);
|
||||
}
|
||||
38
consensus/types/src/eth1_data.rs
Normal file
38
consensus/types/src/eth1_data.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use super::Hash256;
|
||||
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;
|
||||
|
||||
/// Contains data obtained from the Eth1 chain.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
Clone,
|
||||
Default,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Encode,
|
||||
Decode,
|
||||
TreeHash,
|
||||
TestRandom,
|
||||
)]
|
||||
pub struct Eth1Data {
|
||||
pub deposit_root: Hash256,
|
||||
pub deposit_count: u64,
|
||||
pub block_hash: Hash256,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(Eth1Data);
|
||||
}
|
||||
236
consensus/types/src/eth_spec.rs
Normal file
236
consensus/types/src/eth_spec.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
use crate::*;
|
||||
|
||||
use safe_arith::SafeArith;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_types::typenum::{
|
||||
Unsigned, U0, U1, U1024, U1099511627776, U128, U16, U16777216, U2, U2048, U32, U4, U4096, U64,
|
||||
U65536, U8, U8192,
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq {
|
||||
/*
|
||||
* Constants
|
||||
*/
|
||||
type GenesisEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type JustificationBitsLength: Unsigned + Clone + Sync + Send + Debug + PartialEq + Default;
|
||||
type SubnetBitfieldLength: Unsigned + Clone + Sync + Send + Debug + PartialEq + Default;
|
||||
/*
|
||||
* Misc
|
||||
*/
|
||||
type MaxValidatorsPerCommittee: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
/*
|
||||
* Time parameters
|
||||
*/
|
||||
type SlotsPerEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type EpochsPerEth1VotingPeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type SlotsPerHistoricalRoot: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
/*
|
||||
* State list lengths
|
||||
*/
|
||||
type EpochsPerHistoricalVector: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type EpochsPerSlashingsVector: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type HistoricalRootsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type ValidatorRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
/*
|
||||
* Max operations per block
|
||||
*/
|
||||
type MaxProposerSlashings: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type MaxAttesterSlashings: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type MaxAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type MaxDeposits: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
type MaxVoluntaryExits: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
/*
|
||||
* Derived values (set these CAREFULLY)
|
||||
*/
|
||||
/// The length of the `{previous,current}_epoch_attestations` lists.
|
||||
///
|
||||
/// Must be set to `MaxAttestations * SlotsPerEpoch`
|
||||
// NOTE: we could safely instantiate these by using type-level arithmetic, but doing
|
||||
// so adds ~25s to the time required to type-check this crate
|
||||
type MaxPendingAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
/// The length of `eth1_data_votes`.
|
||||
///
|
||||
/// Must be set to `EpochsPerEth1VotingPeriod * SlotsPerEpoch`
|
||||
type SlotsPerEth1VotingPeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
|
||||
fn default_spec() -> ChainSpec;
|
||||
|
||||
fn genesis_epoch() -> Epoch {
|
||||
Epoch::new(Self::GenesisEpoch::to_u64())
|
||||
}
|
||||
|
||||
/// Return the number of committees per slot.
|
||||
///
|
||||
/// Note: the number of committees per slot is constant in each epoch, and depends only on
|
||||
/// the `active_validator_count` during the slot's epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn get_committee_count_per_slot(
|
||||
active_validator_count: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<usize, Error> {
|
||||
let slots_per_epoch = Self::SlotsPerEpoch::to_usize();
|
||||
|
||||
Ok(std::cmp::max(
|
||||
1,
|
||||
std::cmp::min(
|
||||
spec.max_committees_per_slot,
|
||||
active_validator_count
|
||||
.safe_div(slots_per_epoch)?
|
||||
.safe_div(spec.target_committee_size)?,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the minimum number of validators required for this spec.
|
||||
///
|
||||
/// This is the _absolute_ minimum, the number required to make the chain operate in the most
|
||||
/// basic sense. This count is not required to provide any security guarantees regarding
|
||||
/// decentralization, entropy, etc.
|
||||
fn minimum_validator_count() -> usize {
|
||||
Self::SlotsPerEpoch::to_usize()
|
||||
}
|
||||
|
||||
/// Returns the `SLOTS_PER_EPOCH` constant for this specification.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn slots_per_epoch() -> u64 {
|
||||
Self::SlotsPerEpoch::to_u64()
|
||||
}
|
||||
|
||||
/// Returns the `SLOTS_PER_HISTORICAL_ROOT` constant for this specification.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn slots_per_historical_root() -> usize {
|
||||
Self::SlotsPerHistoricalRoot::to_usize()
|
||||
}
|
||||
|
||||
/// Returns the `EPOCHS_PER_HISTORICAL_VECTOR` constant for this specification.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn epochs_per_historical_vector() -> usize {
|
||||
Self::EpochsPerHistoricalVector::to_usize()
|
||||
}
|
||||
|
||||
/// Returns the `SLOTS_PER_ETH1_VOTING_PERIOD` constant for this specification.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
fn slots_per_eth1_voting_period() -> usize {
|
||||
Self::SlotsPerEth1VotingPeriod::to_usize()
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro to inherit some type values from another EthSpec.
|
||||
#[macro_export]
|
||||
macro_rules! params_from_eth_spec {
|
||||
($spec_ty:ty { $($ty_name:ident),+ }) => {
|
||||
$(type $ty_name = <$spec_ty as EthSpec>::$ty_name;)+
|
||||
}
|
||||
}
|
||||
|
||||
/// Ethereum Foundation specifications.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct MainnetEthSpec;
|
||||
|
||||
impl EthSpec for MainnetEthSpec {
|
||||
type JustificationBitsLength = U4;
|
||||
type SubnetBitfieldLength = U64;
|
||||
type MaxValidatorsPerCommittee = U2048;
|
||||
type GenesisEpoch = U0;
|
||||
type SlotsPerEpoch = U32;
|
||||
type EpochsPerEth1VotingPeriod = U32;
|
||||
type SlotsPerHistoricalRoot = U8192;
|
||||
type EpochsPerHistoricalVector = U65536;
|
||||
type EpochsPerSlashingsVector = U8192;
|
||||
type HistoricalRootsLimit = U16777216;
|
||||
type ValidatorRegistryLimit = U1099511627776;
|
||||
type MaxProposerSlashings = U16;
|
||||
type MaxAttesterSlashings = U1;
|
||||
type MaxAttestations = U128;
|
||||
type MaxDeposits = U16;
|
||||
type MaxVoluntaryExits = U16;
|
||||
type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch
|
||||
type SlotsPerEth1VotingPeriod = U1024; // 32 epochs * 32 slots per epoch
|
||||
|
||||
fn default_spec() -> ChainSpec {
|
||||
ChainSpec::mainnet()
|
||||
}
|
||||
}
|
||||
|
||||
pub type FoundationBeaconState = BeaconState<MainnetEthSpec>;
|
||||
|
||||
/// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct MinimalEthSpec;
|
||||
|
||||
impl EthSpec for MinimalEthSpec {
|
||||
type SlotsPerEpoch = U8;
|
||||
type EpochsPerEth1VotingPeriod = U2;
|
||||
type SlotsPerHistoricalRoot = U64;
|
||||
type EpochsPerHistoricalVector = U64;
|
||||
type EpochsPerSlashingsVector = U64;
|
||||
type MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch
|
||||
type SlotsPerEth1VotingPeriod = U16; // 2 epochs * 8 slots per epoch
|
||||
|
||||
params_from_eth_spec!(MainnetEthSpec {
|
||||
JustificationBitsLength,
|
||||
SubnetBitfieldLength,
|
||||
MaxValidatorsPerCommittee,
|
||||
GenesisEpoch,
|
||||
HistoricalRootsLimit,
|
||||
ValidatorRegistryLimit,
|
||||
MaxProposerSlashings,
|
||||
MaxAttesterSlashings,
|
||||
MaxAttestations,
|
||||
MaxDeposits,
|
||||
MaxVoluntaryExits
|
||||
});
|
||||
|
||||
fn default_spec() -> ChainSpec {
|
||||
ChainSpec::minimal()
|
||||
}
|
||||
}
|
||||
|
||||
pub type MinimalBeaconState = BeaconState<MinimalEthSpec>;
|
||||
|
||||
/// Interop testnet spec
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct InteropEthSpec;
|
||||
|
||||
impl EthSpec for InteropEthSpec {
|
||||
type SlotsPerEpoch = U8;
|
||||
type EpochsPerEth1VotingPeriod = U2;
|
||||
type SlotsPerHistoricalRoot = U64;
|
||||
type EpochsPerHistoricalVector = U64;
|
||||
type EpochsPerSlashingsVector = U64;
|
||||
type MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch
|
||||
type SlotsPerEth1VotingPeriod = U16; // 2 epochs * 8 slots per epoch
|
||||
|
||||
params_from_eth_spec!(MainnetEthSpec {
|
||||
JustificationBitsLength,
|
||||
SubnetBitfieldLength,
|
||||
MaxValidatorsPerCommittee,
|
||||
GenesisEpoch,
|
||||
HistoricalRootsLimit,
|
||||
ValidatorRegistryLimit,
|
||||
MaxProposerSlashings,
|
||||
MaxAttesterSlashings,
|
||||
MaxAttestations,
|
||||
MaxDeposits,
|
||||
MaxVoluntaryExits
|
||||
});
|
||||
|
||||
fn default_spec() -> ChainSpec {
|
||||
ChainSpec::interop()
|
||||
}
|
||||
}
|
||||
|
||||
pub type InteropBeaconState = BeaconState<InteropEthSpec>;
|
||||
65
consensus/types/src/fork.rs
Normal file
65
consensus/types/src/fork.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::utils::{fork_from_hex_str, fork_to_hex_str};
|
||||
use crate::Epoch;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Specifies a fork of the `BeaconChain`, to prevent replay attacks.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom,
|
||||
)]
|
||||
pub struct Fork {
|
||||
#[serde(
|
||||
serialize_with = "fork_to_hex_str",
|
||||
deserialize_with = "fork_from_hex_str"
|
||||
)]
|
||||
pub previous_version: [u8; 4],
|
||||
#[serde(
|
||||
serialize_with = "fork_to_hex_str",
|
||||
deserialize_with = "fork_from_hex_str"
|
||||
)]
|
||||
pub current_version: [u8; 4],
|
||||
pub epoch: Epoch,
|
||||
}
|
||||
|
||||
impl Fork {
|
||||
/// Return the fork version of the given ``epoch``.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn get_fork_version(&self, epoch: Epoch) -> [u8; 4] {
|
||||
if epoch < self.epoch {
|
||||
return self.previous_version;
|
||||
}
|
||||
self.current_version
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(Fork);
|
||||
|
||||
#[test]
|
||||
fn get_fork_version() {
|
||||
let previous_version = [1; 4];
|
||||
let current_version = [2; 4];
|
||||
let epoch = Epoch::new(10);
|
||||
|
||||
let fork = Fork {
|
||||
previous_version,
|
||||
current_version,
|
||||
epoch,
|
||||
};
|
||||
|
||||
assert_eq!(fork.get_fork_version(epoch - 1), previous_version);
|
||||
assert_eq!(fork.get_fork_version(epoch), current_version);
|
||||
assert_eq!(fork.get_fork_version(epoch + 1), current_version);
|
||||
}
|
||||
}
|
||||
33
consensus/types/src/fork_data.rs
Normal file
33
consensus/types/src/fork_data.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::utils::{fork_from_hex_str, fork_to_hex_str};
|
||||
use crate::{Hash256, SignedRoot};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Specifies a fork of the `BeaconChain`, to prevent replay attacks.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom,
|
||||
)]
|
||||
pub struct ForkData {
|
||||
#[serde(
|
||||
serialize_with = "fork_to_hex_str",
|
||||
deserialize_with = "fork_from_hex_str"
|
||||
)]
|
||||
pub current_version: [u8; 4],
|
||||
pub genesis_validators_root: Hash256,
|
||||
}
|
||||
|
||||
impl SignedRoot for ForkData {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(ForkData);
|
||||
}
|
||||
13
consensus/types/src/free_attestation.rs
Normal file
13
consensus/types/src/free_attestation.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
/// Note: this object does not actually exist in the spec.
|
||||
///
|
||||
/// We use it for managing attestations that have not been aggregated.
|
||||
use super::{AttestationData, Signature};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct FreeAttestation {
|
||||
pub data: AttestationData,
|
||||
pub signature: Signature,
|
||||
pub validator_index: u64,
|
||||
}
|
||||
27
consensus/types/src/historical_batch.rs
Normal file
27
consensus/types/src/historical_batch.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use ssz_types::FixedVector;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Historical block and state roots.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct HistoricalBatch<T: EthSpec> {
|
||||
pub block_roots: FixedVector<Hash256, T::SlotsPerHistoricalRoot>,
|
||||
pub state_roots: FixedVector<Hash256, T::SlotsPerHistoricalRoot>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
pub type FoundationHistoricalBatch = HistoricalBatch<MainnetEthSpec>;
|
||||
|
||||
ssz_and_tree_hash_tests!(FoundationHistoricalBatch);
|
||||
}
|
||||
126
consensus/types/src/indexed_attestation.rs
Normal file
126
consensus/types/src/indexed_attestation.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, EthSpec, VariableList};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Details an attestation that can be slashable.
|
||||
///
|
||||
/// To be included in an `AttesterSlashing`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct IndexedAttestation<T: EthSpec> {
|
||||
/// Lists validator registry indices, not committee indices.
|
||||
pub attesting_indices: VariableList<u64, T::MaxValidatorsPerCommittee>,
|
||||
pub data: AttestationData,
|
||||
pub signature: AggregateSignature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> IndexedAttestation<T> {
|
||||
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn is_double_vote(&self, other: &Self) -> bool {
|
||||
self.data.target.epoch == other.data.target.epoch && self.data != other.data
|
||||
}
|
||||
|
||||
/// Check if ``attestation_data_1`` surrounds ``attestation_data_2``.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn is_surround_vote(&self, other: &Self) -> bool {
|
||||
self.data.source.epoch < other.data.source.epoch
|
||||
&& other.data.target.epoch < self.data.target.epoch
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::slot_epoch::Epoch;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use crate::MainnetEthSpec;
|
||||
|
||||
#[test]
|
||||
pub fn test_is_double_vote_true() {
|
||||
let indexed_vote_first = create_indexed_attestation(3, 1);
|
||||
let indexed_vote_second = create_indexed_attestation(3, 2);
|
||||
|
||||
assert_eq!(
|
||||
indexed_vote_first.is_double_vote(&indexed_vote_second),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_is_double_vote_false() {
|
||||
let indexed_vote_first = create_indexed_attestation(1, 1);
|
||||
let indexed_vote_second = create_indexed_attestation(2, 1);
|
||||
|
||||
assert_eq!(
|
||||
indexed_vote_first.is_double_vote(&indexed_vote_second),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_is_surround_vote_true() {
|
||||
let indexed_vote_first = create_indexed_attestation(2, 1);
|
||||
let indexed_vote_second = create_indexed_attestation(1, 2);
|
||||
|
||||
assert_eq!(
|
||||
indexed_vote_first.is_surround_vote(&indexed_vote_second),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_is_surround_vote_true_realistic() {
|
||||
let indexed_vote_first = create_indexed_attestation(4, 1);
|
||||
let indexed_vote_second = create_indexed_attestation(3, 2);
|
||||
|
||||
assert_eq!(
|
||||
indexed_vote_first.is_surround_vote(&indexed_vote_second),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_is_surround_vote_false_source_epoch_fails() {
|
||||
let indexed_vote_first = create_indexed_attestation(2, 2);
|
||||
let indexed_vote_second = create_indexed_attestation(1, 1);
|
||||
|
||||
assert_eq!(
|
||||
indexed_vote_first.is_surround_vote(&indexed_vote_second),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_is_surround_vote_false_target_epoch_fails() {
|
||||
let indexed_vote_first = create_indexed_attestation(1, 1);
|
||||
let indexed_vote_second = create_indexed_attestation(2, 2);
|
||||
|
||||
assert_eq!(
|
||||
indexed_vote_first.is_surround_vote(&indexed_vote_second),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
ssz_and_tree_hash_tests!(IndexedAttestation<MainnetEthSpec>);
|
||||
|
||||
fn create_indexed_attestation(
|
||||
target_epoch: u64,
|
||||
source_epoch: u64,
|
||||
) -> IndexedAttestation<MainnetEthSpec> {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let mut indexed_vote = IndexedAttestation::random_for_test(&mut rng);
|
||||
|
||||
indexed_vote.data.source.epoch = Epoch::new(source_epoch);
|
||||
indexed_vote.data.target.epoch = Epoch::new(target_epoch);
|
||||
indexed_vote
|
||||
}
|
||||
}
|
||||
101
consensus/types/src/lib.rs
Normal file
101
consensus/types/src/lib.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
//! Ethereum 2.0 types
|
||||
|
||||
// Required for big type-level numbers
|
||||
#![recursion_limit = "128"]
|
||||
// Clippy lint set up
|
||||
#![deny(clippy::integer_arithmetic)]
|
||||
|
||||
#[macro_use]
|
||||
pub mod test_utils;
|
||||
|
||||
pub mod aggregate_and_proof;
|
||||
pub mod attestation;
|
||||
pub mod attestation_data;
|
||||
pub mod attestation_duty;
|
||||
pub mod attester_slashing;
|
||||
pub mod beacon_block;
|
||||
pub mod beacon_block_body;
|
||||
pub mod beacon_block_header;
|
||||
pub mod beacon_committee;
|
||||
pub mod beacon_state;
|
||||
pub mod chain_spec;
|
||||
pub mod checkpoint;
|
||||
pub mod deposit;
|
||||
pub mod deposit_data;
|
||||
pub mod deposit_message;
|
||||
pub mod enr_fork_id;
|
||||
pub mod eth1_data;
|
||||
pub mod eth_spec;
|
||||
pub mod fork;
|
||||
pub mod fork_data;
|
||||
pub mod free_attestation;
|
||||
pub mod historical_batch;
|
||||
pub mod indexed_attestation;
|
||||
pub mod pending_attestation;
|
||||
pub mod proposer_slashing;
|
||||
pub mod relative_epoch;
|
||||
pub mod selection_proof;
|
||||
pub mod signed_aggregate_and_proof;
|
||||
pub mod signed_beacon_block;
|
||||
pub mod signed_beacon_block_header;
|
||||
pub mod signed_voluntary_exit;
|
||||
pub mod signing_root;
|
||||
pub mod utils;
|
||||
pub mod validator;
|
||||
pub mod voluntary_exit;
|
||||
#[macro_use]
|
||||
pub mod slot_epoch_macros;
|
||||
pub mod slot_epoch;
|
||||
pub mod subnet_id;
|
||||
mod tree_hash_impls;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub mod sqlite;
|
||||
|
||||
use ethereum_types::{H160, H256};
|
||||
|
||||
pub use crate::aggregate_and_proof::AggregateAndProof;
|
||||
pub use crate::attestation::{Attestation, Error as AttestationError};
|
||||
pub use crate::attestation_data::AttestationData;
|
||||
pub use crate::attestation_duty::AttestationDuty;
|
||||
pub use crate::attester_slashing::AttesterSlashing;
|
||||
pub use crate::beacon_block::BeaconBlock;
|
||||
pub use crate::beacon_block_body::BeaconBlockBody;
|
||||
pub use crate::beacon_block_header::BeaconBlockHeader;
|
||||
pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee};
|
||||
pub use crate::beacon_state::{BeaconTreeHashCache, Error as BeaconStateError, *};
|
||||
pub use crate::chain_spec::{ChainSpec, Domain, YamlConfig};
|
||||
pub use crate::checkpoint::Checkpoint;
|
||||
pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH};
|
||||
pub use crate::deposit_data::DepositData;
|
||||
pub use crate::deposit_message::DepositMessage;
|
||||
pub use crate::enr_fork_id::EnrForkId;
|
||||
pub use crate::eth1_data::Eth1Data;
|
||||
pub use crate::fork::Fork;
|
||||
pub use crate::fork_data::ForkData;
|
||||
pub use crate::free_attestation::FreeAttestation;
|
||||
pub use crate::historical_batch::HistoricalBatch;
|
||||
pub use crate::indexed_attestation::IndexedAttestation;
|
||||
pub use crate::pending_attestation::PendingAttestation;
|
||||
pub use crate::proposer_slashing::ProposerSlashing;
|
||||
pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch};
|
||||
pub use crate::selection_proof::SelectionProof;
|
||||
pub use crate::signed_aggregate_and_proof::SignedAggregateAndProof;
|
||||
pub use crate::signed_beacon_block::{SignedBeaconBlock, SignedBeaconBlockHash};
|
||||
pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader;
|
||||
pub use crate::signed_voluntary_exit::SignedVoluntaryExit;
|
||||
pub use crate::signing_root::{SignedRoot, SigningRoot};
|
||||
pub use crate::slot_epoch::{Epoch, Slot};
|
||||
pub use crate::subnet_id::SubnetId;
|
||||
pub use crate::validator::Validator;
|
||||
pub use crate::voluntary_exit::VoluntaryExit;
|
||||
|
||||
pub type CommitteeIndex = u64;
|
||||
pub type Hash256 = H256;
|
||||
pub type Address = H160;
|
||||
|
||||
pub use bls::{
|
||||
AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey,
|
||||
Signature, SignatureBytes,
|
||||
};
|
||||
pub use ssz_types::{typenum, typenum::Unsigned, BitList, BitVector, FixedVector, VariableList};
|
||||
38
consensus/types/src/pending_attestation.rs
Normal file
38
consensus/types/src/pending_attestation.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{AttestationData, BitList, EthSpec};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// An attestation that has been included in the state but not yet fully processed.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct PendingAttestation<T: EthSpec> {
|
||||
pub aggregation_bits: BitList<T::MaxValidatorsPerCommittee>,
|
||||
pub data: AttestationData,
|
||||
pub inclusion_delay: u64,
|
||||
pub proposer_index: u64,
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary-fuzz")]
|
||||
impl<T: EthSpec> arbitrary::Arbitrary for PendingAttestation<T> {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
|
||||
Ok(Self {
|
||||
aggregation_bits: <BitList<T::MaxValidatorsPerCommittee>>::arbitrary(u)?,
|
||||
data: AttestationData::arbitrary(u)?,
|
||||
inclusion_delay: u64::arbitrary(u)?,
|
||||
proposer_index: u64::arbitrary(u)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(PendingAttestation<MainnetEthSpec>);
|
||||
}
|
||||
24
consensus/types/src/proposer_slashing.rs
Normal file
24
consensus/types/src/proposer_slashing.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::SignedBeaconBlockHeader;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Two conflicting proposals from the same proposer (validator).
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct ProposerSlashing {
|
||||
pub signed_header_1: SignedBeaconBlockHeader,
|
||||
pub signed_header_2: SignedBeaconBlockHeader,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(ProposerSlashing);
|
||||
}
|
||||
118
consensus/types/src/relative_epoch.rs
Normal file
118
consensus/types/src/relative_epoch.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum Error {
|
||||
EpochTooLow { base: Epoch, other: Epoch },
|
||||
EpochTooHigh { base: Epoch, other: Epoch },
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary-fuzz")]
|
||||
use arbitrary::Arbitrary;
|
||||
|
||||
/// Defines the epochs relative to some epoch. Most useful when referring to the committees prior
|
||||
/// to and following some epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum RelativeEpoch {
|
||||
/// The prior epoch.
|
||||
Previous,
|
||||
/// The current epoch.
|
||||
Current,
|
||||
/// The next epoch.
|
||||
Next,
|
||||
}
|
||||
|
||||
impl RelativeEpoch {
|
||||
/// Returns the `epoch` that `self` refers to, with respect to the `base` epoch.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn into_epoch(self, base: Epoch) -> Epoch {
|
||||
match self {
|
||||
// Due to saturating nature of epoch, check for current first.
|
||||
RelativeEpoch::Current => base,
|
||||
RelativeEpoch::Previous => base - 1,
|
||||
RelativeEpoch::Next => base + 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the `other` epoch into a `RelativeEpoch`, with respect to `base`
|
||||
///
|
||||
/// ## Errors
|
||||
/// Returns an error when:
|
||||
/// - `EpochTooLow` when `other` is more than 1 prior to `base`.
|
||||
/// - `EpochTooHigh` when `other` is more than 1 after `base`.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn from_epoch(base: Epoch, other: Epoch) -> Result<Self, Error> {
|
||||
// Due to saturating nature of epoch, check for current first.
|
||||
if other == base {
|
||||
Ok(RelativeEpoch::Current)
|
||||
} else if other == base - 1 {
|
||||
Ok(RelativeEpoch::Previous)
|
||||
} else if other == base + 1 {
|
||||
Ok(RelativeEpoch::Next)
|
||||
} else if other < base {
|
||||
Err(Error::EpochTooLow { base, other })
|
||||
} else {
|
||||
Err(Error::EpochTooHigh { base, other })
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function for `Self::from_epoch` where both slots are converted into epochs.
|
||||
pub fn from_slot(base: Slot, other: Slot, slots_per_epoch: u64) -> Result<Self, Error> {
|
||||
Self::from_epoch(base.epoch(slots_per_epoch), other.epoch(slots_per_epoch))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_into_epoch() {
|
||||
let base = Epoch::new(10);
|
||||
|
||||
assert_eq!(RelativeEpoch::Current.into_epoch(base), base);
|
||||
assert_eq!(RelativeEpoch::Previous.into_epoch(base), base - 1);
|
||||
assert_eq!(RelativeEpoch::Next.into_epoch(base), base + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_epoch() {
|
||||
let base = Epoch::new(10);
|
||||
|
||||
assert_eq!(
|
||||
RelativeEpoch::from_epoch(base, base - 1),
|
||||
Ok(RelativeEpoch::Previous)
|
||||
);
|
||||
assert_eq!(
|
||||
RelativeEpoch::from_epoch(base, base),
|
||||
Ok(RelativeEpoch::Current)
|
||||
);
|
||||
assert_eq!(
|
||||
RelativeEpoch::from_epoch(base, base + 1),
|
||||
Ok(RelativeEpoch::Next)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_slot() {
|
||||
let slots_per_epoch: u64 = 64;
|
||||
let base = Slot::new(10 * slots_per_epoch);
|
||||
|
||||
assert_eq!(
|
||||
RelativeEpoch::from_slot(base, base - 1, slots_per_epoch),
|
||||
Ok(RelativeEpoch::Previous)
|
||||
);
|
||||
assert_eq!(
|
||||
RelativeEpoch::from_slot(base, base, slots_per_epoch),
|
||||
Ok(RelativeEpoch::Current)
|
||||
);
|
||||
assert_eq!(
|
||||
RelativeEpoch::from_slot(base, base + slots_per_epoch, slots_per_epoch),
|
||||
Ok(RelativeEpoch::Next)
|
||||
);
|
||||
}
|
||||
}
|
||||
90
consensus/types/src/selection_proof.rs
Normal file
90
consensus/types/src/selection_proof.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use crate::{
|
||||
ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey, SecretKey, Signature, SignedRoot, Slot,
|
||||
};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use std::cmp;
|
||||
use std::convert::TryInto;
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct SelectionProof(Signature);
|
||||
|
||||
impl SelectionProof {
|
||||
pub fn new<T: EthSpec>(
|
||||
slot: Slot,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let domain = spec.get_domain(
|
||||
slot.epoch(T::slots_per_epoch()),
|
||||
Domain::SelectionProof,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = slot.signing_root(domain);
|
||||
|
||||
Self(Signature::new(message.as_bytes(), secret_key))
|
||||
}
|
||||
|
||||
/// Returns the "modulo" used for determining if a `SelectionProof` elects an aggregator.
|
||||
pub fn modulo(committee_len: usize, spec: &ChainSpec) -> Result<u64, ArithError> {
|
||||
Ok(cmp::max(
|
||||
1,
|
||||
(committee_len as u64).safe_div(spec.target_aggregators_per_committee)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn is_aggregator(
|
||||
&self,
|
||||
committee_len: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<bool, ArithError> {
|
||||
self.is_aggregator_from_modulo(Self::modulo(committee_len, spec)?)
|
||||
}
|
||||
|
||||
pub fn is_aggregator_from_modulo(&self, modulo: u64) -> Result<bool, ArithError> {
|
||||
let signature_hash = self.0.tree_hash_root();
|
||||
let signature_hash_int = u64::from_le_bytes(
|
||||
signature_hash[0..8]
|
||||
.as_ref()
|
||||
.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,
|
||||
pubkey: &PublicKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
let domain = spec.get_domain(
|
||||
slot.epoch(T::slots_per_epoch()),
|
||||
Domain::SelectionProof,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = slot.signing_root(domain);
|
||||
|
||||
self.0.verify(message.as_bytes(), pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Signature> for SelectionProof {
|
||||
fn into(self) -> Signature {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Signature> for SelectionProof {
|
||||
fn from(sig: Signature) -> Self {
|
||||
Self(sig)
|
||||
}
|
||||
}
|
||||
100
consensus/types/src/signed_aggregate_and_proof.rs
Normal file
100
consensus/types/src/signed_aggregate_and_proof.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use super::{
|
||||
AggregateAndProof, Attestation, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey,
|
||||
SecretKey, SelectionProof, Signature, SignedRoot,
|
||||
};
|
||||
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 aggregate proof to publish on the `beacon_aggregate_and_proof`
|
||||
/// gossipsub topic.
|
||||
///
|
||||
/// Spec v0.10.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct SignedAggregateAndProof<T: EthSpec> {
|
||||
/// The `AggregateAndProof` that was signed.
|
||||
pub message: AggregateAndProof<T>,
|
||||
/// The aggregate attestation.
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SignedAggregateAndProof<T> {
|
||||
/// Produces a new `SignedAggregateAndProof` 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,
|
||||
aggregate: Attestation<T>,
|
||||
selection_proof: Option<SelectionProof>,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let message = AggregateAndProof::from_aggregate(
|
||||
aggregator_index,
|
||||
aggregate,
|
||||
selection_proof,
|
||||
secret_key,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
let target_epoch = message.aggregate.data.slot.epoch(T::slots_per_epoch());
|
||||
let domain = spec.get_domain(
|
||||
target_epoch,
|
||||
Domain::AggregateAndProof,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let signing_message = message.signing_root(domain);
|
||||
|
||||
SignedAggregateAndProof {
|
||||
message,
|
||||
signature: Signature::new(signing_message.as_bytes(), &secret_key),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(message.as_bytes(), validator_pubkey)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
113
consensus/types/src/signed_beacon_block.rs
Normal file
113
consensus/types/src/signed_beacon_block.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use crate::{
|
||||
test_utils::TestRandom, BeaconBlock, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey,
|
||||
SignedRoot, SigningRoot, Slot,
|
||||
};
|
||||
use bls::Signature;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::fmt;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub struct SignedBeaconBlockHash(Hash256);
|
||||
|
||||
impl fmt::Debug for SignedBeaconBlockHash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "SignedBeaconBlockHash({:?})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SignedBeaconBlockHash {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hash256> for SignedBeaconBlockHash {
|
||||
fn from(hash: Hash256) -> SignedBeaconBlockHash {
|
||||
SignedBeaconBlockHash(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignedBeaconBlockHash> for Hash256 {
|
||||
fn from(signed_beacon_block_hash: SignedBeaconBlockHash) -> Hash256 {
|
||||
signed_beacon_block_hash.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A `BeaconBlock` and a signature from its proposer.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TestRandom)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
pub struct SignedBeaconBlock<E: EthSpec> {
|
||||
pub message: BeaconBlock<E>,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> SignedBeaconBlock<E> {
|
||||
/// Verify `self.signature`.
|
||||
///
|
||||
/// If the root of `block.message` is already known it can be passed in via `object_root_opt`.
|
||||
/// Otherwise, it will be computed locally.
|
||||
pub fn verify_signature(
|
||||
&self,
|
||||
object_root_opt: Option<Hash256>,
|
||||
pubkey: &PublicKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
let domain = spec.get_domain(
|
||||
self.message.slot.epoch(E::slots_per_epoch()),
|
||||
Domain::BeaconProposer,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
|
||||
let message = if let Some(object_root) = object_root_opt {
|
||||
SigningRoot {
|
||||
object_root,
|
||||
domain,
|
||||
}
|
||||
.tree_hash_root()
|
||||
} else {
|
||||
self.message.signing_root(domain)
|
||||
};
|
||||
|
||||
self.signature.verify(message.as_bytes(), pubkey)
|
||||
}
|
||||
|
||||
/// Convenience accessor for the block's slot.
|
||||
pub fn slot(&self) -> Slot {
|
||||
self.message.slot
|
||||
}
|
||||
|
||||
/// Convenience accessor for the block's parent root.
|
||||
pub fn parent_root(&self) -> Hash256 {
|
||||
self.message.parent_root
|
||||
}
|
||||
|
||||
/// Convenience accessor for the block's state root.
|
||||
pub fn state_root(&self) -> Hash256 {
|
||||
self.message.state_root
|
||||
}
|
||||
|
||||
/// Returns the `tree_hash_root` of the block.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn canonical_root(&self) -> Hash256 {
|
||||
Hash256::from_slice(&self.message.tree_hash_root()[..])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::MainnetEthSpec;
|
||||
|
||||
ssz_tests!(SignedBeaconBlock<MainnetEthSpec>);
|
||||
}
|
||||
24
consensus/types/src/signed_beacon_block_header.rs
Normal file
24
consensus/types/src/signed_beacon_block_header.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use crate::{test_utils::TestRandom, BeaconBlockHeader};
|
||||
use bls::Signature;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// An exit voluntarily submitted a validator who wishes to withdraw.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct SignedBeaconBlockHeader {
|
||||
pub message: BeaconBlockHeader,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(SignedBeaconBlockHeader);
|
||||
}
|
||||
24
consensus/types/src/signed_voluntary_exit.rs
Normal file
24
consensus/types/src/signed_voluntary_exit.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use crate::{test_utils::TestRandom, VoluntaryExit};
|
||||
use bls::Signature;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// An exit voluntarily submitted a validator who wishes to withdraw.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct SignedVoluntaryExit {
|
||||
pub message: VoluntaryExit,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(SignedVoluntaryExit);
|
||||
}
|
||||
25
consensus/types/src/signing_root.rs
Normal file
25
consensus/types/src/signing_root.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::Hash256;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash::TreeHash;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct SigningRoot {
|
||||
pub object_root: Hash256,
|
||||
pub domain: Hash256,
|
||||
}
|
||||
|
||||
pub trait SignedRoot: TreeHash {
|
||||
fn signing_root(&self, domain: Hash256) -> Hash256 {
|
||||
SigningRoot {
|
||||
object_root: self.tree_hash_root(),
|
||||
domain,
|
||||
}
|
||||
.tree_hash_root()
|
||||
}
|
||||
}
|
||||
195
consensus/types/src/slot_epoch.rs
Normal file
195
consensus/types/src/slot_epoch.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
//! The `Slot` and `Epoch` types are defined as new types over u64 to enforce type-safety between
|
||||
//! the two types.
|
||||
//!
|
||||
//! `Slot` and `Epoch` have implementations which permit conversion, comparison and math operations
|
||||
//! between each and `u64`, however specifically not between each other.
|
||||
//!
|
||||
//! All math operations on `Slot` and `Epoch` are saturating, they never wrap.
|
||||
//!
|
||||
//! It would be easy to define `PartialOrd` and other traits generically across all types which
|
||||
//! implement `Into<u64>`, however this would allow operations between `Slots` and `Epochs` which
|
||||
//! may lead to programming errors which are not detected by the compiler.
|
||||
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::SignedRoot;
|
||||
|
||||
use rand::RngCore;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{ssz_encode, Decode, DecodeError, Encode};
|
||||
use std::cmp::{Ord, Ordering};
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter::Iterator;
|
||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign};
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Eq, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Slot(u64);
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Eq, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct Epoch(u64);
|
||||
|
||||
impl_common!(Slot);
|
||||
impl_common!(Epoch);
|
||||
|
||||
impl Slot {
|
||||
pub const fn new(slot: u64) -> Slot {
|
||||
Slot(slot)
|
||||
}
|
||||
|
||||
pub fn epoch(self, slots_per_epoch: u64) -> Epoch {
|
||||
Epoch::from(self.0) / Epoch::from(slots_per_epoch)
|
||||
}
|
||||
|
||||
pub fn max_value() -> Slot {
|
||||
Slot(u64::max_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl Epoch {
|
||||
pub const fn new(slot: u64) -> Epoch {
|
||||
Epoch(slot)
|
||||
}
|
||||
|
||||
pub fn max_value() -> Epoch {
|
||||
Epoch(u64::max_value())
|
||||
}
|
||||
|
||||
/// The first slot in the epoch.
|
||||
pub fn start_slot(self, slots_per_epoch: u64) -> Slot {
|
||||
Slot::from(self.0.saturating_mul(slots_per_epoch))
|
||||
}
|
||||
|
||||
/// The last slot in the epoch.
|
||||
pub fn end_slot(self, slots_per_epoch: u64) -> Slot {
|
||||
Slot::from(
|
||||
self.0
|
||||
.saturating_add(1)
|
||||
.saturating_mul(slots_per_epoch)
|
||||
.saturating_sub(1),
|
||||
)
|
||||
}
|
||||
|
||||
/// Position of some slot inside an epoch, if any.
|
||||
///
|
||||
/// E.g., the first `slot` in `epoch` is at position `0`.
|
||||
pub fn position(self, slot: Slot, slots_per_epoch: u64) -> Option<usize> {
|
||||
let start = self.start_slot(slots_per_epoch);
|
||||
let end = self.end_slot(slots_per_epoch);
|
||||
|
||||
if slot >= start && slot <= end {
|
||||
slot.as_usize().checked_sub(start.as_usize())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slot_iter(&self, slots_per_epoch: u64) -> SlotIter {
|
||||
SlotIter {
|
||||
current_iteration: 0,
|
||||
epoch: self,
|
||||
slots_per_epoch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SignedRoot for Epoch {}
|
||||
impl SignedRoot for Slot {}
|
||||
|
||||
pub struct SlotIter<'a> {
|
||||
current_iteration: u64,
|
||||
epoch: &'a Epoch,
|
||||
slots_per_epoch: u64,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SlotIter<'a> {
|
||||
type Item = Slot;
|
||||
|
||||
fn next(&mut self) -> Option<Slot> {
|
||||
if self.current_iteration >= self.slots_per_epoch {
|
||||
None
|
||||
} else {
|
||||
let start_slot = self.epoch.start_slot(self.slots_per_epoch);
|
||||
let previous = self.current_iteration;
|
||||
self.current_iteration = self.current_iteration.checked_add(1)?;
|
||||
Some(start_slot + previous)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod slot_tests {
|
||||
use super::*;
|
||||
|
||||
all_tests!(Slot);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod epoch_tests {
|
||||
use super::*;
|
||||
|
||||
all_tests!(Epoch);
|
||||
|
||||
#[test]
|
||||
fn epoch_start_end() {
|
||||
let slots_per_epoch = 8;
|
||||
|
||||
let epoch = Epoch::new(0);
|
||||
|
||||
assert_eq!(epoch.start_slot(slots_per_epoch), Slot::new(0));
|
||||
assert_eq!(epoch.end_slot(slots_per_epoch), Slot::new(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position() {
|
||||
let slots_per_epoch = 8;
|
||||
|
||||
let epoch = Epoch::new(0);
|
||||
assert_eq!(epoch.position(Slot::new(0), slots_per_epoch), Some(0));
|
||||
assert_eq!(epoch.position(Slot::new(1), slots_per_epoch), Some(1));
|
||||
assert_eq!(epoch.position(Slot::new(2), slots_per_epoch), Some(2));
|
||||
assert_eq!(epoch.position(Slot::new(3), slots_per_epoch), Some(3));
|
||||
assert_eq!(epoch.position(Slot::new(4), slots_per_epoch), Some(4));
|
||||
assert_eq!(epoch.position(Slot::new(5), slots_per_epoch), Some(5));
|
||||
assert_eq!(epoch.position(Slot::new(6), slots_per_epoch), Some(6));
|
||||
assert_eq!(epoch.position(Slot::new(7), slots_per_epoch), Some(7));
|
||||
assert_eq!(epoch.position(Slot::new(8), slots_per_epoch), None);
|
||||
|
||||
let epoch = Epoch::new(1);
|
||||
assert_eq!(epoch.position(Slot::new(7), slots_per_epoch), None);
|
||||
assert_eq!(epoch.position(Slot::new(8), slots_per_epoch), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slot_iter() {
|
||||
let slots_per_epoch = 8;
|
||||
|
||||
let epoch = Epoch::new(0);
|
||||
|
||||
let mut slots = vec![];
|
||||
for slot in epoch.slot_iter(slots_per_epoch) {
|
||||
slots.push(slot);
|
||||
}
|
||||
|
||||
assert_eq!(slots.len(), slots_per_epoch as usize);
|
||||
|
||||
for i in 0..slots_per_epoch {
|
||||
assert_eq!(Slot::from(i), slots[i as usize])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_epoch_ssz() {
|
||||
let max_epoch = Epoch::max_value();
|
||||
assert_eq!(
|
||||
&max_epoch.as_ssz_bytes(),
|
||||
&[255, 255, 255, 255, 255, 255, 255, 255]
|
||||
);
|
||||
assert_eq!(
|
||||
max_epoch,
|
||||
Epoch::from_ssz_bytes(&max_epoch.as_ssz_bytes()).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
620
consensus/types/src/slot_epoch_macros.rs
Normal file
620
consensus/types/src/slot_epoch_macros.rs
Normal file
@@ -0,0 +1,620 @@
|
||||
macro_rules! impl_from_into_u64 {
|
||||
($main: ident) => {
|
||||
impl From<u64> for $main {
|
||||
fn from(n: u64) -> $main {
|
||||
$main(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u64> for $main {
|
||||
fn into(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl $main {
|
||||
pub fn as_u64(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_from_into_usize {
|
||||
($main: ident) => {
|
||||
impl From<usize> for $main {
|
||||
fn from(n: usize) -> $main {
|
||||
$main(n as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<usize> for $main {
|
||||
fn into(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl $main {
|
||||
pub fn as_usize(&self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_math_between {
|
||||
($main: ident, $other: ident) => {
|
||||
impl PartialOrd<$other> for $main {
|
||||
/// Utilizes `partial_cmp` on the underlying `u64`.
|
||||
fn partial_cmp(&self, other: &$other) -> Option<Ordering> {
|
||||
Some(self.0.cmp(&(*other).into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<$other> for $main {
|
||||
fn eq(&self, other: &$other) -> bool {
|
||||
let other: u64 = (*other).into();
|
||||
self.0 == other
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn add(self, other: $other) -> $main {
|
||||
$main::from(self.0.saturating_add(other.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<$other> for $main {
|
||||
fn add_assign(&mut self, other: $other) {
|
||||
self.0 = self.0.saturating_add(other.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn sub(self, other: $other) -> $main {
|
||||
$main::from(self.0.saturating_sub(other.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<$other> for $main {
|
||||
fn sub_assign(&mut self, other: $other) {
|
||||
self.0 = self.0.saturating_sub(other.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn mul(self, rhs: $other) -> $main {
|
||||
let rhs: u64 = rhs.into();
|
||||
$main::from(self.0.saturating_mul(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<$other> for $main {
|
||||
fn mul_assign(&mut self, rhs: $other) {
|
||||
let rhs: u64 = rhs.into();
|
||||
self.0 = self.0.saturating_mul(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn div(self, rhs: $other) -> $main {
|
||||
let rhs: u64 = rhs.into();
|
||||
$main::from(
|
||||
self.0
|
||||
.checked_div(rhs)
|
||||
.expect("Cannot divide by zero-valued Slot/Epoch"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<$other> for $main {
|
||||
fn div_assign(&mut self, rhs: $other) {
|
||||
let rhs: u64 = rhs.into();
|
||||
self.0 = self
|
||||
.0
|
||||
.checked_div(rhs)
|
||||
.expect("Cannot divide by zero-valued Slot/Epoch");
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn rem(self, modulus: $other) -> $main {
|
||||
let modulus: u64 = modulus.into();
|
||||
$main::from(
|
||||
self.0
|
||||
.checked_rem(modulus)
|
||||
.expect("Cannot divide by zero-valued Slot/Epoch"),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_math {
|
||||
($type: ident) => {
|
||||
impl $type {
|
||||
pub fn saturating_sub<T: Into<$type>>(&self, other: T) -> $type {
|
||||
*self - other.into()
|
||||
}
|
||||
|
||||
pub fn saturating_add<T: Into<$type>>(&self, other: T) -> $type {
|
||||
*self + other.into()
|
||||
}
|
||||
|
||||
pub fn checked_div<T: Into<$type>>(&self, rhs: T) -> Option<$type> {
|
||||
let rhs: $type = rhs.into();
|
||||
if rhs == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(*self / rhs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_power_of_two(&self) -> bool {
|
||||
self.0.is_power_of_two()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for $type {
|
||||
fn cmp(&self, other: &$type) -> Ordering {
|
||||
let other: u64 = (*other).into();
|
||||
self.0.cmp(&other)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_display {
|
||||
($type: ident) => {
|
||||
impl fmt::Display for $type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl slog::Value for $type {
|
||||
fn serialize(
|
||||
&self,
|
||||
record: &slog::Record,
|
||||
key: slog::Key,
|
||||
serializer: &mut dyn slog::Serializer,
|
||||
) -> slog::Result {
|
||||
slog::Value::serialize(&self.0, record, key, serializer)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_debug {
|
||||
($type: ident) => {
|
||||
impl fmt::Debug for $type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}({:?})", stringify!($type), self.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_ssz {
|
||||
($type: ident) => {
|
||||
impl Encode for $type {
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
<u64 as Encode>::is_ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_fixed_len() -> usize {
|
||||
<u64 as Encode>::ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_bytes_len(&self) -> usize {
|
||||
0_u64.ssz_bytes_len()
|
||||
}
|
||||
|
||||
fn ssz_append(&self, buf: &mut Vec<u8>) {
|
||||
self.0.ssz_append(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for $type {
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
<u64 as Decode>::is_ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_fixed_len() -> usize {
|
||||
<u64 as Decode>::ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
|
||||
Ok($type(u64::from_ssz_bytes(bytes)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl tree_hash::TreeHash for $type {
|
||||
fn tree_hash_type() -> tree_hash::TreeHashType {
|
||||
tree_hash::TreeHashType::Basic
|
||||
}
|
||||
|
||||
fn tree_hash_packed_encoding(&self) -> Vec<u8> {
|
||||
ssz_encode(self)
|
||||
}
|
||||
|
||||
fn tree_hash_packing_factor() -> usize {
|
||||
32usize.wrapping_div(8)
|
||||
}
|
||||
|
||||
fn tree_hash_root(&self) -> tree_hash::Hash256 {
|
||||
tree_hash::Hash256::from_slice(&int_to_bytes::int_to_fixed_bytes32(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRandom for $type {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
$type::from(u64::random_for_test(rng))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_hash {
|
||||
($type: ident) => {
|
||||
// Implemented to stop clippy lint:
|
||||
// https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
|
||||
impl Hash for $type {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
ssz_encode(self).hash(state)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_common {
|
||||
($type: ident) => {
|
||||
impl_from_into_u64!($type);
|
||||
impl_from_into_usize!($type);
|
||||
impl_math_between!($type, $type);
|
||||
impl_math_between!($type, u64);
|
||||
impl_math!($type);
|
||||
impl_display!($type);
|
||||
impl_debug!($type);
|
||||
impl_ssz!($type);
|
||||
impl_hash!($type);
|
||||
};
|
||||
}
|
||||
|
||||
// test macros
|
||||
#[cfg(test)]
|
||||
macro_rules! new_tests {
|
||||
($type: ident) => {
|
||||
#[test]
|
||||
fn new() {
|
||||
assert_eq!($type(0), $type::new(0));
|
||||
assert_eq!($type(3), $type::new(3));
|
||||
assert_eq!($type(u64::max_value()), $type::new(u64::max_value()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! from_into_tests {
|
||||
($type: ident, $other: ident) => {
|
||||
#[test]
|
||||
fn into() {
|
||||
let x: $other = $type(0).into();
|
||||
assert_eq!(x, 0);
|
||||
|
||||
let x: $other = $type(3).into();
|
||||
assert_eq!(x, 3);
|
||||
|
||||
let x: $other = $type(u64::max_value()).into();
|
||||
// Note: this will fail on 32 bit systems. This is expected as we don't have a proper
|
||||
// 32-bit system strategy in place.
|
||||
assert_eq!(x, $other::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from() {
|
||||
assert_eq!($type(0), $type::from(0_u64));
|
||||
assert_eq!($type(3), $type::from(3_u64));
|
||||
assert_eq!($type(u64::max_value()), $type::from($other::max_value()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! math_between_tests {
|
||||
($type: ident, $other: ident) => {
|
||||
#[test]
|
||||
fn partial_ord() {
|
||||
let assert_partial_ord = |a: u64, partial_ord: Ordering, b: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a).partial_cmp(&other), Some(partial_ord));
|
||||
};
|
||||
|
||||
assert_partial_ord(1, Ordering::Less, 2);
|
||||
assert_partial_ord(2, Ordering::Greater, 1);
|
||||
assert_partial_ord(0, Ordering::Less, u64::max_value());
|
||||
assert_partial_ord(u64::max_value(), Ordering::Greater, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq() {
|
||||
let assert_partial_eq = |a: u64, b: u64, is_equal: bool| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a).eq(&other), is_equal);
|
||||
};
|
||||
|
||||
assert_partial_eq(0, 0, true);
|
||||
assert_partial_eq(0, 1, false);
|
||||
assert_partial_eq(1, 0, false);
|
||||
assert_partial_eq(1, 1, true);
|
||||
|
||||
assert_partial_eq(u64::max_value(), u64::max_value(), true);
|
||||
assert_partial_eq(0, u64::max_value(), false);
|
||||
assert_partial_eq(u64::max_value(), 0, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_and_add_assign() {
|
||||
let assert_add = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) + other, $type(result));
|
||||
|
||||
let mut add_assigned = $type(a);
|
||||
add_assigned += other;
|
||||
|
||||
assert_eq!(add_assigned, $type(result));
|
||||
};
|
||||
|
||||
assert_add(0, 1, 1);
|
||||
assert_add(1, 0, 1);
|
||||
assert_add(1, 2, 3);
|
||||
assert_add(2, 1, 3);
|
||||
assert_add(7, 7, 14);
|
||||
|
||||
// Addition should be saturating.
|
||||
assert_add(u64::max_value(), 1, u64::max_value());
|
||||
assert_add(u64::max_value(), u64::max_value(), u64::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_and_sub_assign() {
|
||||
let assert_sub = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) - other, $type(result));
|
||||
|
||||
let mut sub_assigned = $type(a);
|
||||
sub_assigned -= other;
|
||||
|
||||
assert_eq!(sub_assigned, $type(result));
|
||||
};
|
||||
|
||||
assert_sub(1, 0, 1);
|
||||
assert_sub(2, 1, 1);
|
||||
assert_sub(14, 7, 7);
|
||||
assert_sub(u64::max_value(), 1, u64::max_value() - 1);
|
||||
assert_sub(u64::max_value(), u64::max_value(), 0);
|
||||
|
||||
// Subtraction should be saturating
|
||||
assert_sub(0, 1, 0);
|
||||
assert_sub(1, 2, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_and_mul_assign() {
|
||||
let assert_mul = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) * other, $type(result));
|
||||
|
||||
let mut mul_assigned = $type(a);
|
||||
mul_assigned *= other;
|
||||
|
||||
assert_eq!(mul_assigned, $type(result));
|
||||
};
|
||||
|
||||
assert_mul(2, 2, 4);
|
||||
assert_mul(1, 2, 2);
|
||||
assert_mul(0, 2, 0);
|
||||
|
||||
// Multiplication should be saturating.
|
||||
assert_mul(u64::max_value(), 2, u64::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_and_div_assign() {
|
||||
let assert_div = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) / other, $type(result));
|
||||
|
||||
let mut div_assigned = $type(a);
|
||||
div_assigned /= other;
|
||||
|
||||
assert_eq!(div_assigned, $type(result));
|
||||
};
|
||||
|
||||
assert_div(0, 2, 0);
|
||||
assert_div(2, 2, 1);
|
||||
assert_div(100, 50, 2);
|
||||
assert_div(128, 2, 64);
|
||||
assert_div(u64::max_value(), 2, 2_u64.pow(63) - 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn div_panics_with_divide_by_zero() {
|
||||
let other: $other = $type(0).into();
|
||||
let _ = $type(2) / other;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn div_assign_panics_with_divide_by_zero() {
|
||||
let other: $other = $type(0).into();
|
||||
let mut assigned = $type(2);
|
||||
assigned /= other;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rem() {
|
||||
let assert_rem = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) % other, $type(result));
|
||||
};
|
||||
|
||||
assert_rem(3, 2, 1);
|
||||
assert_rem(40, 2, 0);
|
||||
assert_rem(10, 100, 10);
|
||||
assert_rem(302042, 3293, 2379);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! math_tests {
|
||||
($type: ident) => {
|
||||
#[test]
|
||||
fn saturating_sub() {
|
||||
let assert_saturating_sub = |a: u64, b: u64, result: u64| {
|
||||
assert_eq!($type(a).saturating_sub($type(b)), $type(result));
|
||||
};
|
||||
|
||||
assert_saturating_sub(1, 0, 1);
|
||||
assert_saturating_sub(2, 1, 1);
|
||||
assert_saturating_sub(14, 7, 7);
|
||||
assert_saturating_sub(u64::max_value(), 1, u64::max_value() - 1);
|
||||
assert_saturating_sub(u64::max_value(), u64::max_value(), 0);
|
||||
|
||||
// Subtraction should be saturating
|
||||
assert_saturating_sub(0, 1, 0);
|
||||
assert_saturating_sub(1, 2, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saturating_add() {
|
||||
let assert_saturating_add = |a: u64, b: u64, result: u64| {
|
||||
assert_eq!($type(a).saturating_add($type(b)), $type(result));
|
||||
};
|
||||
|
||||
assert_saturating_add(0, 1, 1);
|
||||
assert_saturating_add(1, 0, 1);
|
||||
assert_saturating_add(1, 2, 3);
|
||||
assert_saturating_add(2, 1, 3);
|
||||
assert_saturating_add(7, 7, 14);
|
||||
|
||||
// Addition should be saturating.
|
||||
assert_saturating_add(u64::max_value(), 1, u64::max_value());
|
||||
assert_saturating_add(u64::max_value(), u64::max_value(), u64::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_div() {
|
||||
let assert_checked_div = |a: u64, b: u64, result: Option<u64>| {
|
||||
let division_result_as_u64 = match $type(a).checked_div($type(b)) {
|
||||
None => None,
|
||||
Some(val) => Some(val.as_u64()),
|
||||
};
|
||||
assert_eq!(division_result_as_u64, result);
|
||||
};
|
||||
|
||||
assert_checked_div(0, 2, Some(0));
|
||||
assert_checked_div(2, 2, Some(1));
|
||||
assert_checked_div(100, 50, Some(2));
|
||||
assert_checked_div(128, 2, Some(64));
|
||||
assert_checked_div(u64::max_value(), 2, Some(2_u64.pow(63) - 1));
|
||||
|
||||
assert_checked_div(2, 0, None);
|
||||
assert_checked_div(0, 0, None);
|
||||
assert_checked_div(u64::max_value(), 0, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_power_of_two() {
|
||||
let assert_is_power_of_two = |a: u64, result: bool| {
|
||||
assert_eq!(
|
||||
$type(a).is_power_of_two(),
|
||||
result,
|
||||
"{}.is_power_of_two() != {}",
|
||||
a,
|
||||
result
|
||||
);
|
||||
};
|
||||
|
||||
assert_is_power_of_two(0, false);
|
||||
assert_is_power_of_two(1, true);
|
||||
assert_is_power_of_two(2, true);
|
||||
assert_is_power_of_two(3, false);
|
||||
assert_is_power_of_two(4, true);
|
||||
|
||||
assert_is_power_of_two(2_u64.pow(4), true);
|
||||
assert_is_power_of_two(u64::max_value(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ord() {
|
||||
let assert_ord = |a: u64, ord: Ordering, b: u64| {
|
||||
assert_eq!($type(a).cmp(&$type(b)), ord);
|
||||
};
|
||||
|
||||
assert_ord(1, Ordering::Less, 2);
|
||||
assert_ord(2, Ordering::Greater, 1);
|
||||
assert_ord(0, Ordering::Less, u64::max_value());
|
||||
assert_ord(u64::max_value(), Ordering::Greater, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
macro_rules! all_tests {
|
||||
($type: ident) => {
|
||||
new_tests!($type);
|
||||
math_between_tests!($type, $type);
|
||||
math_tests!($type);
|
||||
ssz_and_tree_hash_tests!($type);
|
||||
|
||||
mod u64_tests {
|
||||
use super::*;
|
||||
|
||||
from_into_tests!($type, u64);
|
||||
math_between_tests!($type, u64);
|
||||
|
||||
#[test]
|
||||
pub fn as_64() {
|
||||
let x = $type(0).as_u64();
|
||||
assert_eq!(x, 0);
|
||||
|
||||
let x = $type(3).as_u64();
|
||||
assert_eq!(x, 3);
|
||||
|
||||
let x = $type(u64::max_value()).as_u64();
|
||||
assert_eq!(x, u64::max_value());
|
||||
}
|
||||
}
|
||||
|
||||
mod usize_tests {
|
||||
use super::*;
|
||||
|
||||
from_into_tests!($type, usize);
|
||||
|
||||
#[test]
|
||||
pub fn as_usize() {
|
||||
let x = $type(0).as_usize();
|
||||
assert_eq!(x, 0);
|
||||
|
||||
let x = $type(3).as_usize();
|
||||
assert_eq!(x, 3);
|
||||
|
||||
let x = $type(u64::max_value()).as_usize();
|
||||
assert_eq!(x, usize::max_value());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
30
consensus/types/src/sqlite.rs
Normal file
30
consensus/types/src/sqlite.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
//! Implementations of SQLite compatibility traits.
|
||||
use crate::{Epoch, Slot};
|
||||
use rusqlite::{
|
||||
types::{FromSql, FromSqlError, ToSql, ToSqlOutput, ValueRef},
|
||||
Error,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
macro_rules! impl_to_from_sql {
|
||||
($type:ty) => {
|
||||
impl ToSql for $type {
|
||||
fn to_sql(&self) -> Result<ToSqlOutput, Error> {
|
||||
let val_i64 = i64::try_from(self.as_u64())
|
||||
.map_err(|e| Error::ToSqlConversionFailure(Box::new(e)))?;
|
||||
Ok(ToSqlOutput::from(val_i64))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for $type {
|
||||
fn column_result(value: ValueRef) -> Result<Self, FromSqlError> {
|
||||
let val_u64 = u64::try_from(i64::column_result(value)?)
|
||||
.map_err(|e| FromSqlError::Other(Box::new(e)))?;
|
||||
Ok(Self::new(val_u64))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_to_from_sql!(Slot);
|
||||
impl_to_from_sql!(Epoch);
|
||||
27
consensus/types/src/subnet_id.rs
Normal file
27
consensus/types/src/subnet_id.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! Identifies each shard by an integer identifier.
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct SubnetId(u64);
|
||||
|
||||
impl SubnetId {
|
||||
pub fn new(id: u64) -> Self {
|
||||
SubnetId(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SubnetId {
|
||||
type Target = u64;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for SubnetId {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
19
consensus/types/src/test_utils/builders.rs
Normal file
19
consensus/types/src/test_utils/builders.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
mod testing_attestation_builder;
|
||||
mod testing_attestation_data_builder;
|
||||
mod testing_attester_slashing_builder;
|
||||
mod testing_beacon_block_builder;
|
||||
mod testing_beacon_state_builder;
|
||||
mod testing_deposit_builder;
|
||||
mod testing_pending_attestation_builder;
|
||||
mod testing_proposer_slashing_builder;
|
||||
mod testing_voluntary_exit_builder;
|
||||
|
||||
pub use testing_attestation_builder::*;
|
||||
pub use testing_attestation_data_builder::*;
|
||||
pub use testing_attester_slashing_builder::*;
|
||||
pub use testing_beacon_block_builder::*;
|
||||
pub use testing_beacon_state_builder::*;
|
||||
pub use testing_deposit_builder::*;
|
||||
pub use testing_pending_attestation_builder::*;
|
||||
pub use testing_proposer_slashing_builder::*;
|
||||
pub use testing_voluntary_exit_builder::*;
|
||||
@@ -0,0 +1,112 @@
|
||||
use crate::test_utils::{AttestationTestTask, TestingAttestationDataBuilder};
|
||||
use crate::*;
|
||||
|
||||
/// Builds an attestation to be used for testing purposes.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingAttestationBuilder<T: EthSpec> {
|
||||
committee: Vec<usize>,
|
||||
attestation: Attestation<T>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TestingAttestationBuilder<T> {
|
||||
/// Create a new attestation builder.
|
||||
pub fn new(
|
||||
test_task: AttestationTestTask,
|
||||
state: &BeaconState<T>,
|
||||
committee: &[usize],
|
||||
slot: Slot,
|
||||
index: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let data_builder = TestingAttestationDataBuilder::new(test_task, state, index, slot, spec);
|
||||
|
||||
let mut aggregation_bits_len = committee.len();
|
||||
|
||||
if test_task == AttestationTestTask::BadAggregationBitfieldLen {
|
||||
aggregation_bits_len += 1
|
||||
}
|
||||
|
||||
let mut aggregation_bits = BitList::with_capacity(aggregation_bits_len).unwrap();
|
||||
|
||||
for i in 0..committee.len() {
|
||||
aggregation_bits.set(i, false).unwrap();
|
||||
}
|
||||
|
||||
let attestation = Attestation {
|
||||
aggregation_bits,
|
||||
data: data_builder.build(),
|
||||
signature: AggregateSignature::new(),
|
||||
};
|
||||
|
||||
Self {
|
||||
attestation,
|
||||
committee: committee.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs the attestation with a subset (or all) committee members.
|
||||
///
|
||||
/// `secret_keys` must be supplied in the same order as `signing_validators`. I.e., the first
|
||||
/// keypair must be that of the first signing validator.
|
||||
pub fn sign(
|
||||
&mut self,
|
||||
test_task: AttestationTestTask,
|
||||
signing_validators: &[usize],
|
||||
secret_keys: &[&SecretKey],
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> &mut Self {
|
||||
assert_eq!(
|
||||
signing_validators.len(),
|
||||
secret_keys.len(),
|
||||
"Must be a key for each validator"
|
||||
);
|
||||
|
||||
for (key_index, validator_index) in signing_validators.iter().enumerate() {
|
||||
let committee_index = self
|
||||
.committee
|
||||
.iter()
|
||||
.position(|v| *v == *validator_index)
|
||||
.expect("Signing validator not in attestation committee");
|
||||
|
||||
let index = if test_task == AttestationTestTask::BadSignature {
|
||||
0
|
||||
} else {
|
||||
key_index
|
||||
};
|
||||
|
||||
self.attestation
|
||||
.sign(
|
||||
secret_keys[index],
|
||||
committee_index,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
)
|
||||
.expect("can sign attestation");
|
||||
|
||||
self.attestation
|
||||
.aggregation_bits
|
||||
.set(committee_index, true)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if test_task == AttestationTestTask::BadIndexedAttestationBadSignature {
|
||||
// Flip an aggregation bit, to make the aggregate invalid
|
||||
// (We also want to avoid making it completely empty)
|
||||
self.attestation
|
||||
.aggregation_bits
|
||||
.set(0, !self.attestation.aggregation_bits.get(0).unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Consume the builder and return the attestation.
|
||||
pub fn build(self) -> Attestation<T> {
|
||||
self.attestation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use crate::test_utils::AttestationTestTask;
|
||||
use crate::*;
|
||||
|
||||
/// Builds an `AttestationData` to be used for testing purposes.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingAttestationDataBuilder {
|
||||
data: AttestationData,
|
||||
}
|
||||
|
||||
impl TestingAttestationDataBuilder {
|
||||
/// Configures a new `AttestationData` which attests to all of the same parameters as the
|
||||
/// state.
|
||||
pub fn new<T: EthSpec>(
|
||||
test_task: AttestationTestTask,
|
||||
state: &BeaconState<T>,
|
||||
index: u64,
|
||||
mut slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let current_epoch = state.current_epoch();
|
||||
let previous_epoch = state.previous_epoch();
|
||||
|
||||
let is_previous_epoch = slot.epoch(T::slots_per_epoch()) != current_epoch;
|
||||
|
||||
let mut source = if is_previous_epoch {
|
||||
state.previous_justified_checkpoint.clone()
|
||||
} else {
|
||||
state.current_justified_checkpoint.clone()
|
||||
};
|
||||
|
||||
let mut target = if is_previous_epoch {
|
||||
Checkpoint {
|
||||
epoch: previous_epoch,
|
||||
root: *state
|
||||
.get_block_root(previous_epoch.start_slot(T::slots_per_epoch()))
|
||||
.unwrap(),
|
||||
}
|
||||
} else {
|
||||
Checkpoint {
|
||||
epoch: current_epoch,
|
||||
root: *state
|
||||
.get_block_root(current_epoch.start_slot(T::slots_per_epoch()))
|
||||
.unwrap(),
|
||||
}
|
||||
};
|
||||
|
||||
let beacon_block_root = *state.get_block_root(slot).unwrap();
|
||||
|
||||
match test_task {
|
||||
AttestationTestTask::IncludedTooEarly => {
|
||||
slot = state.slot - spec.min_attestation_inclusion_delay + 1
|
||||
}
|
||||
AttestationTestTask::IncludedTooLate => slot -= T::SlotsPerEpoch::to_u64(),
|
||||
AttestationTestTask::TargetEpochSlotMismatch => {
|
||||
target = Checkpoint {
|
||||
epoch: current_epoch + 1,
|
||||
root: Hash256::zero(),
|
||||
};
|
||||
assert_ne!(target.epoch, slot.epoch(T::slots_per_epoch()));
|
||||
}
|
||||
AttestationTestTask::WrongJustifiedCheckpoint => {
|
||||
source = Checkpoint {
|
||||
epoch: Epoch::from(0 as u64),
|
||||
root: Hash256::zero(),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let data = AttestationData {
|
||||
slot,
|
||||
index,
|
||||
|
||||
// LMD GHOST vote
|
||||
beacon_block_root,
|
||||
|
||||
// FFG Vote
|
||||
source,
|
||||
target,
|
||||
};
|
||||
|
||||
Self { data }
|
||||
}
|
||||
|
||||
/// Returns the `AttestationData`, consuming the builder.
|
||||
pub fn build(self) -> AttestationData {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
use crate::test_utils::AttesterSlashingTestTask;
|
||||
use crate::*;
|
||||
|
||||
/// Builds an `AttesterSlashing`.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingAttesterSlashingBuilder();
|
||||
|
||||
impl TestingAttesterSlashingBuilder {
|
||||
/// Builds an `AttesterSlashing` that is a double vote.
|
||||
///
|
||||
/// The `signer` function is used to sign the double-vote and accepts:
|
||||
///
|
||||
/// - `validator_index: u64`
|
||||
/// - `message: &[u8]`
|
||||
/// - `epoch: Epoch`
|
||||
/// - `domain: Domain`
|
||||
///
|
||||
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
||||
pub fn double_vote<F, T: EthSpec>(
|
||||
test_task: AttesterSlashingTestTask,
|
||||
validator_indices: &[u64],
|
||||
signer: F,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> AttesterSlashing<T>
|
||||
where
|
||||
F: Fn(u64, &[u8]) -> Signature,
|
||||
{
|
||||
let slot = Slot::new(1);
|
||||
let index = 0;
|
||||
let epoch_1 = Epoch::new(1);
|
||||
let hash_1 = Hash256::from_low_u64_le(1);
|
||||
let hash_2 = Hash256::from_low_u64_le(2);
|
||||
let checkpoint_1 = Checkpoint {
|
||||
epoch: epoch_1,
|
||||
root: hash_1,
|
||||
};
|
||||
let checkpoint_2 = Checkpoint {
|
||||
epoch: epoch_1,
|
||||
root: hash_2,
|
||||
};
|
||||
|
||||
let data_1 = AttestationData {
|
||||
slot,
|
||||
index,
|
||||
beacon_block_root: hash_1,
|
||||
source: checkpoint_1.clone(),
|
||||
target: checkpoint_1,
|
||||
};
|
||||
|
||||
let data_2 = if test_task == AttesterSlashingTestTask::NotSlashable {
|
||||
AttestationData { ..data_1.clone() }
|
||||
} else {
|
||||
AttestationData {
|
||||
target: checkpoint_2,
|
||||
..data_1.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let mut attestation_1 = IndexedAttestation {
|
||||
attesting_indices: if test_task == AttesterSlashingTestTask::IndexedAttestation1Invalid
|
||||
{
|
||||
// Trigger bad validator indices ordering error.
|
||||
vec![1, 0].into()
|
||||
} else {
|
||||
validator_indices.to_vec().into()
|
||||
},
|
||||
data: data_1,
|
||||
signature: AggregateSignature::new(),
|
||||
};
|
||||
|
||||
let mut attestation_2 = IndexedAttestation {
|
||||
attesting_indices: if test_task == AttesterSlashingTestTask::IndexedAttestation2Invalid
|
||||
{
|
||||
// Trigger bad validator indices ordering error.
|
||||
vec![1, 0].into()
|
||||
} else {
|
||||
validator_indices.to_vec().into()
|
||||
},
|
||||
data: data_2,
|
||||
signature: AggregateSignature::new(),
|
||||
};
|
||||
|
||||
let add_signatures = |attestation: &mut IndexedAttestation<T>| {
|
||||
let domain = spec.get_domain(
|
||||
attestation.data.target.epoch,
|
||||
Domain::BeaconAttester,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = attestation.data.signing_root(domain);
|
||||
|
||||
for validator_index in validator_indices {
|
||||
let signature = signer(*validator_index, message.as_bytes());
|
||||
attestation.signature.add(&signature);
|
||||
}
|
||||
};
|
||||
|
||||
add_signatures(&mut attestation_1);
|
||||
add_signatures(&mut attestation_2);
|
||||
|
||||
AttesterSlashing {
|
||||
attestation_1,
|
||||
attestation_2,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
use crate::{
|
||||
test_utils::{
|
||||
TestingAttestationBuilder, TestingAttesterSlashingBuilder, TestingDepositBuilder,
|
||||
TestingProposerSlashingBuilder, TestingVoluntaryExitBuilder,
|
||||
},
|
||||
typenum::U4294967296,
|
||||
*,
|
||||
};
|
||||
use int_to_bytes::int_to_bytes32;
|
||||
use merkle_proof::MerkleTree;
|
||||
use rayon::prelude::*;
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
/// Builds a beacon block to be used for testing purposes.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingBeaconBlockBuilder<T: EthSpec> {
|
||||
pub block: BeaconBlock<T>,
|
||||
}
|
||||
|
||||
/// Enum used for passing test options to builder
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum DepositTestTask {
|
||||
Valid,
|
||||
BadPubKey,
|
||||
BadSig,
|
||||
InvalidPubKey,
|
||||
NoReset,
|
||||
}
|
||||
|
||||
/// Enum used for passing test options to builder
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum AttestationTestTask {
|
||||
Valid,
|
||||
WrongJustifiedCheckpoint,
|
||||
BadIndexedAttestationBadSignature,
|
||||
BadAggregationBitfieldLen,
|
||||
BadSignature,
|
||||
ValidatorUnknown,
|
||||
IncludedTooEarly,
|
||||
IncludedTooLate,
|
||||
TargetEpochSlotMismatch,
|
||||
// Note: BadTargetEpoch is unreachable in block processing due to valid inclusion window and
|
||||
// slot check
|
||||
}
|
||||
|
||||
/// Enum used for passing test options to builder
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum AttesterSlashingTestTask {
|
||||
Valid,
|
||||
NotSlashable,
|
||||
IndexedAttestation1Invalid,
|
||||
IndexedAttestation2Invalid,
|
||||
}
|
||||
|
||||
/// Enum used for passing test options to builder
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum ProposerSlashingTestTask {
|
||||
Valid,
|
||||
ProposerUnknown,
|
||||
ProposalEpochMismatch,
|
||||
ProposalsIdentical,
|
||||
ProposerNotSlashable,
|
||||
BadProposal1Signature,
|
||||
BadProposal2Signature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TestingBeaconBlockBuilder<T> {
|
||||
/// Create a new builder from genesis.
|
||||
pub fn new(spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
block: BeaconBlock::empty(spec),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the previous block root
|
||||
pub fn set_parent_root(&mut self, root: Hash256) {
|
||||
self.block.parent_root = root;
|
||||
}
|
||||
|
||||
/// Set the slot of the block.
|
||||
pub fn set_slot(&mut self, slot: Slot) {
|
||||
self.block.slot = slot;
|
||||
}
|
||||
|
||||
/// Set the proposer index of the block.
|
||||
pub fn set_proposer_index(&mut self, proposer_index: u64) {
|
||||
self.block.proposer_index = proposer_index;
|
||||
}
|
||||
|
||||
/// Sets the randao to be a signature across the blocks epoch.
|
||||
///
|
||||
/// Modifying the block's slot after signing may invalidate the signature.
|
||||
pub fn set_randao_reveal(
|
||||
&mut self,
|
||||
sk: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let epoch = self.block.slot.epoch(T::slots_per_epoch());
|
||||
let domain = spec.get_domain(epoch, Domain::Randao, fork, genesis_validators_root);
|
||||
let message = epoch.signing_root(domain);
|
||||
self.block.body.randao_reveal = Signature::new(message.as_bytes(), sk);
|
||||
}
|
||||
|
||||
/// Has the randao reveal been set?
|
||||
pub fn randao_reveal_not_set(&mut self) -> bool {
|
||||
self.block.body.randao_reveal.is_empty()
|
||||
}
|
||||
|
||||
/// Inserts a signed, valid `ProposerSlashing` for the validator.
|
||||
pub fn insert_proposer_slashing(
|
||||
&mut self,
|
||||
test_task: ProposerSlashingTestTask,
|
||||
validator_index: u64,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let proposer_slashing = build_proposer_slashing::<T>(
|
||||
test_task,
|
||||
validator_index,
|
||||
secret_key,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
self.block
|
||||
.body
|
||||
.proposer_slashings
|
||||
.push(proposer_slashing)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Inserts a signed, valid `AttesterSlashing` for each validator index in `validator_indices`.
|
||||
pub fn insert_attester_slashing(
|
||||
&mut self,
|
||||
test_task: AttesterSlashingTestTask,
|
||||
validator_indices: &[u64],
|
||||
secret_keys: &[&SecretKey],
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let attester_slashing = build_double_vote_attester_slashing(
|
||||
test_task,
|
||||
validator_indices,
|
||||
secret_keys,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
let _ = self.block.body.attester_slashings.push(attester_slashing);
|
||||
}
|
||||
|
||||
/// Fills the block with `num_attestations` attestations.
|
||||
///
|
||||
/// It will first go and get each committee that is able to include an attestation in this
|
||||
/// block. If there _are_ enough committees, it will produce an attestation for each. If there
|
||||
/// _are not_ enough committees, it will start splitting the committees in half until it
|
||||
/// achieves the target. It will then produce separate attestations for each split committee.
|
||||
///
|
||||
/// Note: the signed messages of the split committees will be identical -- it would be possible
|
||||
/// to aggregate these split attestations.
|
||||
pub fn insert_attestations(
|
||||
&mut self,
|
||||
test_task: AttestationTestTask,
|
||||
state: &BeaconState<T>,
|
||||
secret_keys: &[&SecretKey],
|
||||
num_attestations: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
let mut slot = self.block.slot - spec.min_attestation_inclusion_delay;
|
||||
let mut attestations_added = 0;
|
||||
|
||||
// Stores the following (in order):
|
||||
//
|
||||
// - The slot of the committee.
|
||||
// - A list of all validators in the committee.
|
||||
// - A list of all validators in the committee that should sign the attestation.
|
||||
// - The index of the committee.
|
||||
let mut committees: Vec<(Slot, Vec<usize>, Vec<usize>, u64)> = vec![];
|
||||
|
||||
if slot < T::slots_per_epoch() {
|
||||
panic!("slot is too low, will get stuck in loop")
|
||||
}
|
||||
|
||||
// Loop backwards through slots gathering each committee, until:
|
||||
//
|
||||
// - The slot is too old to be included in a block at this slot.
|
||||
// - The `MAX_ATTESTATIONS`.
|
||||
loop {
|
||||
if state.slot >= slot + T::slots_per_epoch() {
|
||||
break;
|
||||
}
|
||||
|
||||
for beacon_committee in state.get_beacon_committees_at_slot(slot)? {
|
||||
if attestations_added >= num_attestations {
|
||||
break;
|
||||
}
|
||||
|
||||
committees.push((
|
||||
slot,
|
||||
beacon_committee.committee.to_vec(),
|
||||
beacon_committee.committee.to_vec(),
|
||||
beacon_committee.index,
|
||||
));
|
||||
|
||||
attestations_added += 1;
|
||||
}
|
||||
|
||||
slot -= 1;
|
||||
}
|
||||
|
||||
// Loop through all the committees, splitting each one in half until we have
|
||||
// `MAX_ATTESTATIONS` committees.
|
||||
loop {
|
||||
if committees.len() >= num_attestations as usize {
|
||||
break;
|
||||
}
|
||||
|
||||
for i in 0..committees.len() {
|
||||
if committees.len() >= num_attestations as usize {
|
||||
break;
|
||||
}
|
||||
|
||||
let (slot, committee, mut signing_validators, index) = committees[i].clone();
|
||||
|
||||
let new_signing_validators =
|
||||
signing_validators.split_off(signing_validators.len() / 2);
|
||||
|
||||
committees[i] = (slot, committee.clone(), signing_validators, index);
|
||||
committees.push((slot, committee, new_signing_validators, index));
|
||||
}
|
||||
}
|
||||
|
||||
let attestations: Vec<_> = committees
|
||||
.par_iter()
|
||||
.map(|(slot, committee, signing_validators, index)| {
|
||||
let mut builder = TestingAttestationBuilder::new(
|
||||
test_task, state, committee, *slot, *index, spec,
|
||||
);
|
||||
|
||||
let signing_secret_keys: Vec<&SecretKey> = signing_validators
|
||||
.iter()
|
||||
.map(|validator_index| secret_keys[*validator_index])
|
||||
.collect();
|
||||
builder.sign(
|
||||
test_task,
|
||||
signing_validators,
|
||||
&signing_secret_keys,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
builder.build()
|
||||
})
|
||||
.collect();
|
||||
|
||||
for attestation in attestations {
|
||||
self.block.body.attestations.push(attestation).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Insert a `Valid` deposit into the state.
|
||||
pub fn insert_deposits(
|
||||
&mut self,
|
||||
amount: u64,
|
||||
test_task: DepositTestTask,
|
||||
// TODO: deal with the fact deposits no longer have explicit indices
|
||||
_index: u64,
|
||||
num_deposits: u64,
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
// Vector containing deposits' data
|
||||
let mut datas = vec![];
|
||||
for _ in 0..num_deposits {
|
||||
let keypair = Keypair::random();
|
||||
|
||||
let mut builder = TestingDepositBuilder::new(keypair.pk.clone(), amount);
|
||||
builder.sign(test_task, &keypair, spec);
|
||||
datas.push(builder.build().data);
|
||||
}
|
||||
|
||||
// Vector containing all leaves
|
||||
let leaves = datas
|
||||
.iter()
|
||||
.map(|data| data.tree_hash_root())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Building a VarList from leaves
|
||||
let deposit_data_list = VariableList::<_, U4294967296>::from(leaves.clone());
|
||||
|
||||
// Setting the deposit_root to be the tree_hash_root of the VarList
|
||||
state.eth1_data.deposit_root = deposit_data_list.tree_hash_root();
|
||||
|
||||
// Building the merkle tree used for generating proofs
|
||||
let tree = MerkleTree::create(&leaves[..], spec.deposit_contract_tree_depth as usize);
|
||||
|
||||
// Building proofs
|
||||
let mut proofs = vec![];
|
||||
for i in 0..leaves.len() {
|
||||
let (_, mut proof) = tree.generate_proof(i, spec.deposit_contract_tree_depth as usize);
|
||||
proof.push(Hash256::from_slice(&int_to_bytes32(leaves.len() as u64)));
|
||||
proofs.push(proof);
|
||||
}
|
||||
|
||||
// Building deposits
|
||||
let deposits = datas
|
||||
.into_par_iter()
|
||||
.zip(proofs.into_par_iter())
|
||||
.map(|(data, proof)| (data, proof.into()))
|
||||
.map(|(data, proof)| Deposit { proof, data })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Pushing deposits to block body
|
||||
for deposit in deposits {
|
||||
let _ = self.block.body.deposits.push(deposit);
|
||||
}
|
||||
|
||||
// Manually setting the deposit_count to process deposits
|
||||
// This is for test purposes only
|
||||
if test_task == DepositTestTask::NoReset {
|
||||
state.eth1_data.deposit_count += num_deposits;
|
||||
} else {
|
||||
state.eth1_deposit_index = 0;
|
||||
state.eth1_data.deposit_count = num_deposits;
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert an exit for the given validator at the given epoch into the block.
|
||||
pub fn insert_exit(
|
||||
&mut self,
|
||||
validator_index: u64,
|
||||
exit_epoch: Epoch,
|
||||
secret_key: &SecretKey,
|
||||
state: &BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let builder = TestingVoluntaryExitBuilder::new(exit_epoch, validator_index);
|
||||
let exit = builder.build(secret_key, &state.fork, state.genesis_validators_root, spec);
|
||||
self.block.body.voluntary_exits.push(exit).unwrap();
|
||||
}
|
||||
|
||||
/// Mutate the block before signing.
|
||||
pub fn modify(&mut self, f: impl FnOnce(&mut BeaconBlock<T>)) {
|
||||
f(&mut self.block)
|
||||
}
|
||||
|
||||
/// Signs and returns the block, consuming the builder.
|
||||
pub fn build(
|
||||
self,
|
||||
sk: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> SignedBeaconBlock<T> {
|
||||
self.block.sign(sk, fork, genesis_validators_root, spec)
|
||||
}
|
||||
|
||||
/// Returns the block, consuming the builder.
|
||||
pub fn build_without_signing(self) -> SignedBeaconBlock<T> {
|
||||
SignedBeaconBlock {
|
||||
message: self.block,
|
||||
signature: Signature::empty_signature(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds an `ProposerSlashing` for some `validator_index`.
|
||||
///
|
||||
/// Signs the message using a `BeaconChainHarness`.
|
||||
pub fn build_proposer_slashing<T: EthSpec>(
|
||||
test_task: ProposerSlashingTestTask,
|
||||
validator_index: u64,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> ProposerSlashing {
|
||||
TestingProposerSlashingBuilder::double_vote::<T>(
|
||||
test_task,
|
||||
validator_index,
|
||||
secret_key,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds an `AttesterSlashing` for some `validator_indices`.
|
||||
///
|
||||
/// Signs the message using a `BeaconChainHarness`.
|
||||
pub fn build_double_vote_attester_slashing<T: EthSpec>(
|
||||
test_task: AttesterSlashingTestTask,
|
||||
validator_indices: &[u64],
|
||||
secret_keys: &[&SecretKey],
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> AttesterSlashing<T> {
|
||||
let signer = |validator_index: u64, message: &[u8]| {
|
||||
let key_index = validator_indices
|
||||
.iter()
|
||||
.position(|&i| i == validator_index)
|
||||
.expect("Unable to find attester slashing key");
|
||||
Signature::new(message, secret_keys[key_index])
|
||||
};
|
||||
|
||||
TestingAttesterSlashingBuilder::double_vote(
|
||||
test_task,
|
||||
validator_indices,
|
||||
signer,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
use super::super::{generate_deterministic_keypairs, KeypairsFile};
|
||||
use crate::test_utils::{AttestationTestTask, TestingPendingAttestationBuilder};
|
||||
use crate::*;
|
||||
use bls::get_withdrawal_credentials;
|
||||
use log::debug;
|
||||
use rayon::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const KEYPAIRS_FILE: &str = "keypairs.raw_keypairs";
|
||||
|
||||
/// Returns the directory where the generated keypairs should be stored.
|
||||
///
|
||||
/// It is either `$HOME/.lighthouse/keypairs.raw_keypairs` or, if `$HOME` is not available,
|
||||
/// `./keypairs.raw_keypairs`.
|
||||
pub fn keypairs_path() -> PathBuf {
|
||||
let dir = dirs::home_dir()
|
||||
.map(|home| (home.join(".lighthouse")))
|
||||
.unwrap_or_else(|| PathBuf::from(""));
|
||||
dir.join(KEYPAIRS_FILE)
|
||||
}
|
||||
|
||||
/// Builds a beacon state to be used for testing purposes.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
#[derive(Clone)]
|
||||
pub struct TestingBeaconStateBuilder<T: EthSpec> {
|
||||
state: BeaconState<T>,
|
||||
keypairs: Vec<Keypair>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TestingBeaconStateBuilder<T> {
|
||||
/// Attempts to load validators from a file in `$HOME/.lighthouse/keypairs.raw_keypairs`. If
|
||||
/// the file is unavailable, it generates the keys at runtime.
|
||||
///
|
||||
/// If the `$HOME` environment variable is not set, the local directory is used.
|
||||
///
|
||||
/// See the `Self::from_keypairs_file` method for more info.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the file does not contain enough keypairs or is invalid.
|
||||
pub fn from_default_keypairs_file_if_exists(validator_count: usize, spec: &ChainSpec) -> Self {
|
||||
let dir = dirs::home_dir()
|
||||
.map(|home| home.join(".lighthouse"))
|
||||
.unwrap_or_else(|| PathBuf::from(""));
|
||||
let file = dir.join(KEYPAIRS_FILE);
|
||||
|
||||
if file.exists() {
|
||||
TestingBeaconStateBuilder::from_keypairs_file(validator_count, &file, spec)
|
||||
} else {
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(validator_count, spec)
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads the initial validator keypairs from a file on disk.
|
||||
///
|
||||
/// Loading keypairs from file is ~10x faster than generating them. Use the `gen_keys` command
|
||||
/// on the `test_harness` binary to generate the keys. In the `test_harness` dir, run `cargo
|
||||
/// run -- gen_keys -h` for help.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the file does not exist, is invalid or does not contain enough keypairs.
|
||||
pub fn from_keypairs_file(validator_count: usize, path: &Path, spec: &ChainSpec) -> Self {
|
||||
debug!("Loading {} keypairs from file...", validator_count);
|
||||
let keypairs = Vec::from_raw_file(path, validator_count).unwrap();
|
||||
TestingBeaconStateBuilder::from_keypairs(keypairs, spec)
|
||||
}
|
||||
|
||||
/// Generates the validator keypairs deterministically.
|
||||
pub fn from_deterministic_keypairs(validator_count: usize, spec: &ChainSpec) -> Self {
|
||||
debug!("Generating {} deterministic keypairs...", validator_count);
|
||||
let keypairs = generate_deterministic_keypairs(validator_count);
|
||||
TestingBeaconStateBuilder::from_keypairs(keypairs, spec)
|
||||
}
|
||||
|
||||
/// Uses the given keypair for all validators.
|
||||
pub fn from_single_keypair(
|
||||
validator_count: usize,
|
||||
keypair: &Keypair,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
debug!("Generating {} cloned keypairs...", validator_count);
|
||||
|
||||
let mut keypairs = Vec::with_capacity(validator_count);
|
||||
for _ in 0..validator_count {
|
||||
keypairs.push(keypair.clone())
|
||||
}
|
||||
|
||||
TestingBeaconStateBuilder::from_keypairs(keypairs, spec)
|
||||
}
|
||||
|
||||
/// Creates the builder from an existing set of keypairs.
|
||||
pub fn from_keypairs(keypairs: Vec<Keypair>, spec: &ChainSpec) -> Self {
|
||||
let validator_count = keypairs.len();
|
||||
let starting_balance = spec.max_effective_balance;
|
||||
|
||||
debug!(
|
||||
"Building {} Validator objects from keypairs...",
|
||||
validator_count
|
||||
);
|
||||
let validators = keypairs
|
||||
.par_iter()
|
||||
.map(|keypair| {
|
||||
let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(
|
||||
&keypair.pk,
|
||||
spec.bls_withdrawal_prefix_byte,
|
||||
));
|
||||
|
||||
Validator {
|
||||
pubkey: keypair.pk.clone().into(),
|
||||
withdrawal_credentials,
|
||||
// All validators start active.
|
||||
activation_eligibility_epoch: T::genesis_epoch(),
|
||||
activation_epoch: T::genesis_epoch(),
|
||||
exit_epoch: spec.far_future_epoch,
|
||||
withdrawable_epoch: spec.far_future_epoch,
|
||||
slashed: false,
|
||||
effective_balance: starting_balance,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
|
||||
let genesis_time = 1_567_052_589; // 29 August, 2019;
|
||||
|
||||
let mut state = BeaconState::new(
|
||||
genesis_time,
|
||||
Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
deposit_count: 0,
|
||||
block_hash: Hash256::zero(),
|
||||
},
|
||||
spec,
|
||||
);
|
||||
|
||||
state.eth1_data.deposit_count = validator_count as u64;
|
||||
state.eth1_deposit_index = validator_count as u64;
|
||||
|
||||
let balances = vec![starting_balance; validator_count].into();
|
||||
|
||||
debug!("Importing {} existing validators...", validator_count);
|
||||
state.validators = validators;
|
||||
state.balances = balances;
|
||||
|
||||
debug!("BeaconState initialized.");
|
||||
|
||||
Self { state, keypairs }
|
||||
}
|
||||
|
||||
/// Consume the builder and return the `BeaconState` and the keypairs for each validator.
|
||||
pub fn build(self) -> (BeaconState<T>, Vec<Keypair>) {
|
||||
(self.state, self.keypairs)
|
||||
}
|
||||
|
||||
/// Ensures that the state returned from `Self::build(..)` has all caches pre-built.
|
||||
///
|
||||
/// Note: this performs the build when called. Ensure that no changes are made that would
|
||||
/// invalidate this cache.
|
||||
pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
|
||||
self.state.build_all_caches(spec).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the `BeaconState` to be in a slot, calling `teleport_to_epoch` to update the epoch.
|
||||
pub fn teleport_to_slot(&mut self, slot: Slot) -> &mut Self {
|
||||
self.teleport_to_epoch(slot.epoch(T::slots_per_epoch()));
|
||||
self.state.slot = slot;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `BeaconState` to be in the first slot of the given epoch.
|
||||
///
|
||||
/// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e.,
|
||||
/// highest justified and finalized slots, full justification bitfield, etc).
|
||||
fn teleport_to_epoch(&mut self, epoch: Epoch) {
|
||||
let state = &mut self.state;
|
||||
|
||||
let slot = epoch.start_slot(T::slots_per_epoch());
|
||||
|
||||
state.slot = slot;
|
||||
|
||||
state.previous_justified_checkpoint.epoch = epoch - 3;
|
||||
state.current_justified_checkpoint.epoch = epoch - 2;
|
||||
state.justification_bits = BitVector::from_bytes(vec![0b0000_1111]).unwrap();
|
||||
|
||||
state.finalized_checkpoint.epoch = epoch - 3;
|
||||
}
|
||||
|
||||
/// Creates a full set of attestations for the `BeaconState`. Each attestation has full
|
||||
/// participation from its committee and references the expected beacon_block hashes.
|
||||
///
|
||||
/// These attestations should be fully conducive to justification and finalization.
|
||||
pub fn insert_attestations(&mut self, spec: &ChainSpec) {
|
||||
let state = &mut self.state;
|
||||
|
||||
state
|
||||
.build_committee_cache(RelativeEpoch::Previous, spec)
|
||||
.unwrap();
|
||||
state
|
||||
.build_committee_cache(RelativeEpoch::Current, spec)
|
||||
.unwrap();
|
||||
|
||||
let current_epoch = state.current_epoch();
|
||||
let previous_epoch = state.previous_epoch();
|
||||
|
||||
let first_slot = previous_epoch.start_slot(T::slots_per_epoch()).as_u64();
|
||||
let last_slot = current_epoch.end_slot(T::slots_per_epoch()).as_u64()
|
||||
- spec.min_attestation_inclusion_delay;
|
||||
let last_slot = std::cmp::min(state.slot.as_u64(), last_slot);
|
||||
|
||||
for slot in first_slot..=last_slot {
|
||||
let slot = Slot::from(slot);
|
||||
|
||||
let committees: Vec<OwnedBeaconCommittee> = state
|
||||
.get_beacon_committees_at_slot(slot)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|c| c.clone().into_owned())
|
||||
.collect();
|
||||
|
||||
for beacon_committee in committees {
|
||||
let mut builder = TestingPendingAttestationBuilder::new(
|
||||
AttestationTestTask::Valid,
|
||||
state,
|
||||
beacon_committee.index,
|
||||
slot,
|
||||
spec,
|
||||
);
|
||||
// The entire committee should have signed the pending attestation.
|
||||
let signers = vec![true; beacon_committee.committee.len()];
|
||||
builder.add_committee_participation(signers);
|
||||
let attestation = builder.build();
|
||||
|
||||
if attestation.data.target.epoch < state.current_epoch() {
|
||||
state.previous_epoch_attestations.push(attestation).unwrap()
|
||||
} else {
|
||||
state.current_epoch_attestations.push(attestation).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use crate::test_utils::DepositTestTask;
|
||||
use crate::*;
|
||||
use bls::{get_withdrawal_credentials, PublicKeyBytes, SignatureBytes};
|
||||
|
||||
/// Builds an deposit to be used for testing purposes.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingDepositBuilder {
|
||||
deposit: Deposit,
|
||||
}
|
||||
|
||||
impl TestingDepositBuilder {
|
||||
/// Instantiates a new builder.
|
||||
pub fn new(pubkey: PublicKey, amount: u64) -> Self {
|
||||
let deposit = Deposit {
|
||||
proof: vec![].into(),
|
||||
data: DepositData {
|
||||
pubkey: PublicKeyBytes::from(pubkey),
|
||||
withdrawal_credentials: Hash256::zero(),
|
||||
amount,
|
||||
signature: SignatureBytes::empty(),
|
||||
},
|
||||
};
|
||||
|
||||
Self { deposit }
|
||||
}
|
||||
|
||||
/// Signs the deposit, also setting the following values:
|
||||
///
|
||||
/// - `pubkey` to the signing pubkey.
|
||||
/// - `withdrawal_credentials` to the signing pubkey.
|
||||
/// - `proof_of_possession`
|
||||
pub fn sign(&mut self, test_task: DepositTestTask, keypair: &Keypair, spec: &ChainSpec) {
|
||||
let new_key = Keypair::random();
|
||||
let mut pubkeybytes = PublicKeyBytes::from(keypair.pk.clone());
|
||||
let mut secret_key = keypair.sk.clone();
|
||||
|
||||
match test_task {
|
||||
DepositTestTask::BadPubKey => pubkeybytes = PublicKeyBytes::from(new_key.pk),
|
||||
DepositTestTask::InvalidPubKey => {
|
||||
// Creating invalid public key bytes
|
||||
let mut public_key_bytes: Vec<u8> = vec![0; 48];
|
||||
public_key_bytes[0] = 255;
|
||||
pubkeybytes = PublicKeyBytes::from_bytes(&public_key_bytes).unwrap();
|
||||
}
|
||||
DepositTestTask::BadSig => secret_key = new_key.sk,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let withdrawal_credentials = Hash256::from_slice(
|
||||
&get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..],
|
||||
);
|
||||
|
||||
// Building the data and signing it
|
||||
self.deposit.data.pubkey = pubkeybytes;
|
||||
self.deposit.data.withdrawal_credentials = withdrawal_credentials;
|
||||
self.deposit.data.signature = self.deposit.data.create_signature(&secret_key, spec);
|
||||
}
|
||||
|
||||
/// Builds the deposit, consuming the builder.
|
||||
pub fn build(self) -> Deposit {
|
||||
self.deposit
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use crate::test_utils::{AttestationTestTask, TestingAttestationDataBuilder};
|
||||
use crate::*;
|
||||
|
||||
/// Builds an `AttesterSlashing` to be used for testing purposes.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingPendingAttestationBuilder<T: EthSpec> {
|
||||
pending_attestation: PendingAttestation<T>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TestingPendingAttestationBuilder<T> {
|
||||
/// Create a new valid* `PendingAttestation` for the given parameters.
|
||||
///
|
||||
/// The `inclusion_delay` will be set to `MIN_ATTESTATION_INCLUSION_DELAY`.
|
||||
///
|
||||
/// * The aggregation bitfield will be empty, it needs to be set with
|
||||
/// `Self::add_committee_participation`.
|
||||
pub fn new(
|
||||
test_task: AttestationTestTask,
|
||||
state: &BeaconState<T>,
|
||||
index: u64,
|
||||
slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Self {
|
||||
let data_builder = TestingAttestationDataBuilder::new(test_task, state, index, slot, spec);
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(slot, spec).unwrap() as u64;
|
||||
|
||||
let pending_attestation = PendingAttestation {
|
||||
aggregation_bits: BitList::with_capacity(T::MaxValidatorsPerCommittee::to_usize())
|
||||
.unwrap(),
|
||||
data: data_builder.build(),
|
||||
inclusion_delay: spec.min_attestation_inclusion_delay,
|
||||
proposer_index,
|
||||
};
|
||||
|
||||
Self {
|
||||
pending_attestation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the committee participation in the `PendingAttestation`.
|
||||
///
|
||||
/// The `PendingAttestation` will appear to be signed by each committee member who's value in
|
||||
/// `signers` is true.
|
||||
pub fn add_committee_participation(&mut self, signers: Vec<bool>) {
|
||||
let mut aggregation_bits = BitList::with_capacity(signers.len()).unwrap();
|
||||
|
||||
for (i, signed) in signers.iter().enumerate() {
|
||||
aggregation_bits.set(i, *signed).unwrap();
|
||||
}
|
||||
|
||||
self.pending_attestation.aggregation_bits = aggregation_bits;
|
||||
}
|
||||
|
||||
/// Returns the `PendingAttestation`, consuming the builder.
|
||||
pub fn build(self) -> PendingAttestation<T> {
|
||||
self.pending_attestation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use crate::test_utils::ProposerSlashingTestTask;
|
||||
use crate::*;
|
||||
|
||||
/// Builds a `ProposerSlashing`.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingProposerSlashingBuilder;
|
||||
|
||||
impl TestingProposerSlashingBuilder {
|
||||
/// Builds a `ProposerSlashing` that is a double vote.
|
||||
///
|
||||
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
||||
pub fn double_vote<T>(
|
||||
test_task: ProposerSlashingTestTask,
|
||||
proposer_index: u64,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> ProposerSlashing
|
||||
where
|
||||
T: EthSpec,
|
||||
{
|
||||
let slot = Slot::new(0);
|
||||
let hash_1 = Hash256::from([1; 32]);
|
||||
let hash_2 = if test_task == ProposerSlashingTestTask::ProposalsIdentical {
|
||||
hash_1
|
||||
} else {
|
||||
Hash256::from([2; 32])
|
||||
};
|
||||
|
||||
let mut signed_header_1 = SignedBeaconBlockHeader {
|
||||
message: BeaconBlockHeader {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root: hash_1,
|
||||
state_root: hash_1,
|
||||
body_root: hash_1,
|
||||
},
|
||||
signature: Signature::empty_signature(),
|
||||
};
|
||||
|
||||
let slot_2 = if test_task == ProposerSlashingTestTask::ProposalEpochMismatch {
|
||||
Slot::new(128)
|
||||
} else {
|
||||
Slot::new(0)
|
||||
};
|
||||
|
||||
let mut signed_header_2 = SignedBeaconBlockHeader {
|
||||
message: BeaconBlockHeader {
|
||||
parent_root: hash_2,
|
||||
slot: slot_2,
|
||||
..signed_header_1.message
|
||||
},
|
||||
signature: Signature::empty_signature(),
|
||||
};
|
||||
|
||||
if test_task != ProposerSlashingTestTask::BadProposal1Signature {
|
||||
signed_header_1 =
|
||||
signed_header_1
|
||||
.message
|
||||
.sign::<T>(secret_key, fork, genesis_validators_root, spec);
|
||||
}
|
||||
|
||||
if test_task != ProposerSlashingTestTask::BadProposal2Signature {
|
||||
signed_header_2 =
|
||||
signed_header_2
|
||||
.message
|
||||
.sign::<T>(secret_key, fork, genesis_validators_root, spec);
|
||||
}
|
||||
|
||||
if test_task == ProposerSlashingTestTask::ProposerUnknown {
|
||||
signed_header_1.message.proposer_index = 3_141_592;
|
||||
signed_header_2.message.proposer_index = 3_141_592;
|
||||
}
|
||||
|
||||
ProposerSlashing {
|
||||
signed_header_1,
|
||||
signed_header_2,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use crate::*;
|
||||
|
||||
/// Builds an exit to be used for testing purposes.
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingVoluntaryExitBuilder {
|
||||
exit: VoluntaryExit,
|
||||
}
|
||||
|
||||
impl TestingVoluntaryExitBuilder {
|
||||
/// Instantiates a new builder.
|
||||
pub fn new(epoch: Epoch, validator_index: u64) -> Self {
|
||||
let exit = VoluntaryExit {
|
||||
epoch,
|
||||
validator_index,
|
||||
};
|
||||
|
||||
Self { exit }
|
||||
}
|
||||
|
||||
/// Build and sign the exit.
|
||||
///
|
||||
/// The signing secret key must match that of the exiting validator.
|
||||
pub fn build(
|
||||
self,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> SignedVoluntaryExit {
|
||||
self.exit
|
||||
.sign(secret_key, fork, genesis_validators_root, spec)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use crate::*;
|
||||
use eth2_interop_keypairs::{keypair, keypairs_from_yaml_file};
|
||||
use log::debug;
|
||||
use rayon::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Generates `validator_count` keypairs where the secret key is derived solely from the index of
|
||||
/// the validator.
|
||||
///
|
||||
/// Uses the `eth2_interop_keypairs` crate to generate keys.
|
||||
pub fn generate_deterministic_keypairs(validator_count: usize) -> Vec<Keypair> {
|
||||
debug!(
|
||||
"Generating {} deterministic validator keypairs...",
|
||||
validator_count
|
||||
);
|
||||
|
||||
let keypairs: Vec<Keypair> = (0..validator_count)
|
||||
.collect::<Vec<usize>>()
|
||||
.into_par_iter()
|
||||
.map(generate_deterministic_keypair)
|
||||
.collect();
|
||||
|
||||
keypairs
|
||||
}
|
||||
|
||||
/// Generates a single deterministic keypair, where the secret key is `validator_index`.
|
||||
///
|
||||
/// This is used for testing only, and not to be used in production!
|
||||
pub fn generate_deterministic_keypair(validator_index: usize) -> Keypair {
|
||||
let raw = keypair(validator_index);
|
||||
Keypair {
|
||||
pk: PublicKey::from_raw(raw.pk),
|
||||
sk: SecretKey::from_raw(raw.sk),
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads a list of keypairs from file.
|
||||
pub fn load_keypairs_from_yaml(path: PathBuf) -> Result<Vec<Keypair>, String> {
|
||||
Ok(keypairs_from_yaml_file(path)?
|
||||
.into_iter()
|
||||
.map(|raw| Keypair {
|
||||
pk: PublicKey::from_raw(raw.pk),
|
||||
sk: SecretKey::from_raw(raw.sk),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
128
consensus/types/src/test_utils/keypairs_file.rs
Normal file
128
consensus/types/src/test_utils/keypairs_file.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use crate::*;
|
||||
use rayon::prelude::*;
|
||||
use std::fs::File;
|
||||
use std::io::{Error, ErrorKind, Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
pub const PUBLIC_KEY_BYTES_LEN: usize = 96;
|
||||
pub const SECRET_KEY_BYTES_LEN: usize = 32;
|
||||
|
||||
pub const BATCH_SIZE: usize = 1_000; // ~15MB
|
||||
|
||||
pub const KEYPAIR_BYTES_LEN: usize = PUBLIC_KEY_BYTES_LEN + SECRET_KEY_BYTES_LEN;
|
||||
pub const BATCH_BYTE_LEN: usize = KEYPAIR_BYTES_LEN * BATCH_SIZE;
|
||||
|
||||
/// Defines a trait that allows reading/writing a vec of `Keypair` from/to a file.
|
||||
pub trait KeypairsFile {
|
||||
/// Write to file, without guaranteeing interoperability with other clients.
|
||||
fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error>;
|
||||
/// Read from file, without guaranteeing interoperability with other clients.
|
||||
fn from_raw_file(path: &Path, count: usize) -> Result<Vec<Keypair>, Error>;
|
||||
}
|
||||
|
||||
impl KeypairsFile for Vec<Keypair> {
|
||||
/// Write the keypairs to file, using the fastest possible method without guaranteeing
|
||||
/// interoperability with other clients.
|
||||
fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error> {
|
||||
let mut keypairs_file = File::create(path)?;
|
||||
|
||||
for keypair_batch in keypairs.chunks(BATCH_SIZE) {
|
||||
let mut buf = Vec::with_capacity(BATCH_BYTE_LEN);
|
||||
|
||||
for keypair in keypair_batch {
|
||||
buf.append(&mut keypair.sk.as_raw().as_bytes());
|
||||
buf.append(&mut keypair.pk.clone().as_uncompressed_bytes());
|
||||
}
|
||||
|
||||
keypairs_file.write_all(&buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the keypairs from file, using the fastest possible method without guaranteeing
|
||||
/// interoperability with other clients.
|
||||
fn from_raw_file(path: &Path, count: usize) -> Result<Vec<Keypair>, Error> {
|
||||
let mut keypairs_file = File::open(path)?;
|
||||
|
||||
let mut keypairs = Vec::with_capacity(count);
|
||||
|
||||
let indices: Vec<usize> = (0..count).collect();
|
||||
|
||||
for batch in indices.chunks(BATCH_SIZE) {
|
||||
let mut buf = vec![0; batch.len() * KEYPAIR_BYTES_LEN];
|
||||
keypairs_file.read_exact(&mut buf)?;
|
||||
|
||||
let mut keypair_batch = batch
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| {
|
||||
let sk_start = i * KEYPAIR_BYTES_LEN;
|
||||
let sk_end = sk_start + SECRET_KEY_BYTES_LEN;
|
||||
let sk = SecretKey::from_bytes(&buf[sk_start..sk_end])
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "Invalid SecretKey bytes"))
|
||||
.unwrap();
|
||||
|
||||
let pk_start = sk_end;
|
||||
let pk_end = pk_start + PUBLIC_KEY_BYTES_LEN;
|
||||
let pk = PublicKey::from_uncompressed_bytes(&buf[pk_start..pk_end])
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "Invalid PublicKey bytes"))
|
||||
.unwrap();
|
||||
|
||||
Keypair { sk, pk }
|
||||
})
|
||||
.collect();
|
||||
|
||||
keypairs.append(&mut keypair_batch);
|
||||
}
|
||||
|
||||
Ok(keypairs)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
use std::fs::remove_file;
|
||||
|
||||
fn random_keypairs(n: usize) -> Vec<Keypair> {
|
||||
(0..n).into_par_iter().map(|_| Keypair::random()).collect()
|
||||
}
|
||||
|
||||
fn random_tmp_file() -> String {
|
||||
let rng = thread_rng();
|
||||
|
||||
rng.sample_iter(&Alphanumeric).take(7).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn read_write_consistency_small_batch() {
|
||||
let num_keypairs = 10;
|
||||
let keypairs = random_keypairs(num_keypairs);
|
||||
|
||||
let keypairs_path = Path::new("/tmp").join(random_tmp_file());
|
||||
keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap();
|
||||
|
||||
let decoded = Vec::from_raw_file(&keypairs_path, num_keypairs).unwrap();
|
||||
remove_file(keypairs_path).unwrap();
|
||||
|
||||
assert_eq!(keypairs, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn read_write_consistency_big_batch() {
|
||||
let num_keypairs = BATCH_SIZE + 1;
|
||||
let keypairs = random_keypairs(num_keypairs);
|
||||
|
||||
let keypairs_path = Path::new("/tmp").join(random_tmp_file());
|
||||
keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap();
|
||||
|
||||
let decoded = Vec::from_raw_file(&keypairs_path, num_keypairs).unwrap();
|
||||
remove_file(keypairs_path).unwrap();
|
||||
|
||||
assert_eq!(keypairs, decoded);
|
||||
}
|
||||
}
|
||||
46
consensus/types/src/test_utils/macros.rs
Normal file
46
consensus/types/src/test_utils/macros.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
#![cfg(test)]
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ssz_and_tree_hash_tests {
|
||||
($type: ty) => {
|
||||
ssz_tests!($type);
|
||||
tree_hash_tests!($type);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ssz_tests {
|
||||
($type: ty) => {
|
||||
#[test]
|
||||
pub fn test_ssz_round_trip() {
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use ssz::{ssz_encode, Decode};
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = <$type>::random_for_test(&mut rng);
|
||||
|
||||
let bytes = ssz_encode(&original);
|
||||
println!("bytes length: {}", bytes.len());
|
||||
let decoded = <$type>::from_ssz_bytes(&bytes).unwrap();
|
||||
|
||||
assert_eq!(original, decoded);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! tree_hash_tests {
|
||||
($type: ty) => {
|
||||
#[test]
|
||||
pub fn test_tree_hash_root() {
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = <$type>::random_for_test(&mut rng);
|
||||
|
||||
// Tree hashing should not panic.
|
||||
original.tree_hash_root();
|
||||
}
|
||||
};
|
||||
}
|
||||
17
consensus/types/src/test_utils/mod.rs
Normal file
17
consensus/types/src/test_utils/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod builders;
|
||||
mod generate_deterministic_keypairs;
|
||||
mod keypairs_file;
|
||||
mod test_random;
|
||||
|
||||
pub use builders::*;
|
||||
pub use generate_deterministic_keypairs::generate_deterministic_keypair;
|
||||
pub use generate_deterministic_keypairs::generate_deterministic_keypairs;
|
||||
pub use generate_deterministic_keypairs::load_keypairs_from_yaml;
|
||||
pub use keypairs_file::KeypairsFile;
|
||||
pub use rand::{RngCore, SeedableRng};
|
||||
pub use rand_xorshift::XorShiftRng;
|
||||
pub use test_random::{test_random_instance, TestRandom};
|
||||
112
consensus/types/src/test_utils/test_random.rs
Normal file
112
consensus/types/src/test_utils/test_random.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use crate::*;
|
||||
use rand::RngCore;
|
||||
use rand::SeedableRng;
|
||||
use rand_xorshift::XorShiftRng;
|
||||
use ssz_types::typenum::Unsigned;
|
||||
|
||||
mod address;
|
||||
mod aggregate_signature;
|
||||
mod bitfield;
|
||||
mod hash256;
|
||||
mod public_key;
|
||||
mod public_key_bytes;
|
||||
mod secret_key;
|
||||
mod signature;
|
||||
mod signature_bytes;
|
||||
|
||||
pub fn test_random_instance<T: TestRandom>() -> T {
|
||||
let mut rng = XorShiftRng::from_seed([0x42; 16]);
|
||||
T::random_for_test(&mut rng)
|
||||
}
|
||||
|
||||
pub trait TestRandom {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self;
|
||||
}
|
||||
|
||||
impl TestRandom for bool {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
(rng.next_u32() % 2) == 1
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRandom for u64 {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
rng.next_u64()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRandom for u32 {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
rng.next_u32()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRandom for usize {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
rng.next_u32() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> TestRandom for Vec<U>
|
||||
where
|
||||
U: TestRandom,
|
||||
{
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let mut output = vec![];
|
||||
|
||||
for _ in 0..(usize::random_for_test(rng) % 4) {
|
||||
output.push(<U>::random_for_test(rng));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> TestRandom for FixedVector<T, N>
|
||||
where
|
||||
T: TestRandom + Default,
|
||||
{
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let mut output = vec![];
|
||||
|
||||
for _ in 0..(usize::random_for_test(rng) % std::cmp::min(4, N::to_usize())) {
|
||||
output.push(<T>::random_for_test(rng));
|
||||
}
|
||||
|
||||
output.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> TestRandom for VariableList<T, N>
|
||||
where
|
||||
T: TestRandom,
|
||||
{
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let mut output = vec![];
|
||||
|
||||
if N::to_usize() != 0 {
|
||||
for _ in 0..(usize::random_for_test(rng) % std::cmp::min(4, N::to_usize())) {
|
||||
output.push(<T>::random_for_test(rng));
|
||||
}
|
||||
}
|
||||
|
||||
output.into()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_test_random_for_u8_array {
|
||||
($len: expr) => {
|
||||
impl TestRandom for [u8; $len] {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let mut bytes = [0; $len];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
bytes
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_test_random_for_u8_array!(4);
|
||||
impl_test_random_for_u8_array!(32);
|
||||
impl_test_random_for_u8_array!(48);
|
||||
impl_test_random_for_u8_array!(96);
|
||||
10
consensus/types/src/test_utils/test_random/address.rs
Normal file
10
consensus/types/src/test_utils/test_random/address.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use super::*;
|
||||
use crate::Address;
|
||||
|
||||
impl TestRandom for Address {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let mut key_bytes = vec![0; 20];
|
||||
rng.fill_bytes(&mut key_bytes);
|
||||
Address::from_slice(&key_bytes[..])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use super::*;
|
||||
use bls::{AggregateSignature, Signature};
|
||||
|
||||
impl TestRandom for AggregateSignature {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let signature = Signature::random_for_test(rng);
|
||||
let mut aggregate_signature = AggregateSignature::new();
|
||||
aggregate_signature.add(&signature);
|
||||
aggregate_signature
|
||||
}
|
||||
}
|
||||
18
consensus/types/src/test_utils/test_random/bitfield.rs
Normal file
18
consensus/types/src/test_utils/test_random/bitfield.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use super::*;
|
||||
use crate::{BitList, BitVector, Unsigned};
|
||||
|
||||
impl<N: Unsigned + Clone> TestRandom for BitList<N> {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let mut raw_bytes = vec![0; std::cmp::max(1, (N::to_usize() + 7) / 8)];
|
||||
rng.fill_bytes(&mut raw_bytes);
|
||||
Self::from_bytes(raw_bytes).expect("we generate a valid BitList")
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Unsigned + Clone> TestRandom for BitVector<N> {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let mut raw_bytes = vec![0; std::cmp::max(1, (N::to_usize() + 7) / 8)];
|
||||
rng.fill_bytes(&mut raw_bytes);
|
||||
Self::from_bytes(raw_bytes).expect("we generate a valid BitVector")
|
||||
}
|
||||
}
|
||||
10
consensus/types/src/test_utils/test_random/hash256.rs
Normal file
10
consensus/types/src/test_utils/test_random/hash256.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use super::*;
|
||||
use crate::Hash256;
|
||||
|
||||
impl TestRandom for Hash256 {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let mut key_bytes = vec![0; 32];
|
||||
rng.fill_bytes(&mut key_bytes);
|
||||
Hash256::from_slice(&key_bytes[..])
|
||||
}
|
||||
}
|
||||
9
consensus/types/src/test_utils/test_random/public_key.rs
Normal file
9
consensus/types/src/test_utils/test_random/public_key.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use super::*;
|
||||
use bls::{PublicKey, SecretKey};
|
||||
|
||||
impl TestRandom for PublicKey {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let secret_key = SecretKey::random_for_test(rng);
|
||||
PublicKey::from_secret_key(&secret_key)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use std::convert::From;
|
||||
|
||||
use bls::{PublicKeyBytes, BLS_PUBLIC_KEY_BYTE_SIZE};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl TestRandom for PublicKeyBytes {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
//50-50 chance for signature to be "valid" or invalid
|
||||
if bool::random_for_test(rng) {
|
||||
//valid signature
|
||||
PublicKeyBytes::from(PublicKey::random_for_test(rng))
|
||||
} else {
|
||||
//invalid signature, just random bytes
|
||||
PublicKeyBytes::from_bytes(&<[u8; BLS_PUBLIC_KEY_BYTE_SIZE]>::random_for_test(rng))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
8
consensus/types/src/test_utils/test_random/secret_key.rs
Normal file
8
consensus/types/src/test_utils/test_random/secret_key.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use super::*;
|
||||
use bls::SecretKey;
|
||||
|
||||
impl TestRandom for SecretKey {
|
||||
fn random_for_test(_rng: &mut impl RngCore) -> Self {
|
||||
SecretKey::random()
|
||||
}
|
||||
}
|
||||
12
consensus/types/src/test_utils/test_random/signature.rs
Normal file
12
consensus/types/src/test_utils/test_random/signature.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use super::*;
|
||||
use bls::{SecretKey, Signature};
|
||||
|
||||
impl TestRandom for Signature {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
let secret_key = SecretKey::random_for_test(rng);
|
||||
let mut message = vec![0; 32];
|
||||
rng.fill_bytes(&mut message);
|
||||
|
||||
Signature::new(&message, &secret_key)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use bls::{SignatureBytes, BLS_SIG_BYTE_SIZE};
|
||||
|
||||
use super::*;
|
||||
use std::convert::From;
|
||||
|
||||
impl TestRandom for SignatureBytes {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
//50-50 chance for signature to be "valid" or invalid
|
||||
if bool::random_for_test(rng) {
|
||||
//valid signature
|
||||
SignatureBytes::from(Signature::random_for_test(rng))
|
||||
} else {
|
||||
//invalid signature, just random bytes
|
||||
SignatureBytes::from_bytes(&<[u8; BLS_SIG_BYTE_SIZE]>::random_for_test(rng)).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
163
consensus/types/src/tree_hash_impls.rs
Normal file
163
consensus/types/src/tree_hash_impls.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
//! This module contains custom implementations of `CachedTreeHash` for ETH2-specific types.
|
||||
//!
|
||||
//! It makes some assumptions about the layouts and update patterns of other structs in this
|
||||
//! crate, and should be updated carefully whenever those structs are changed.
|
||||
use crate::{Epoch, Hash256, PublicKeyBytes, Validator};
|
||||
use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, Error, TreeHashCache};
|
||||
use int_to_bytes::int_to_fixed_bytes32;
|
||||
use tree_hash::merkle_root;
|
||||
|
||||
/// Number of struct fields on `Validator`.
|
||||
const NUM_VALIDATOR_FIELDS: usize = 8;
|
||||
|
||||
impl CachedTreeHash<TreeHashCache> for Validator {
|
||||
fn new_tree_hash_cache(&self, arena: &mut CacheArena) -> TreeHashCache {
|
||||
TreeHashCache::new(arena, int_log(NUM_VALIDATOR_FIELDS), NUM_VALIDATOR_FIELDS)
|
||||
}
|
||||
|
||||
/// Efficiently tree hash a `Validator`, assuming it was updated by a valid state transition.
|
||||
///
|
||||
/// Specifically, we assume that the `pubkey` and `withdrawal_credentials` fields are constant.
|
||||
fn recalculate_tree_hash_root(
|
||||
&self,
|
||||
arena: &mut CacheArena,
|
||||
cache: &mut TreeHashCache,
|
||||
) -> Result<Hash256, Error> {
|
||||
// Otherwise just check the fields which might have changed.
|
||||
let dirty_indices = cache
|
||||
.leaves()
|
||||
.iter_mut(arena)?
|
||||
.enumerate()
|
||||
.flat_map(|(i, leaf)| {
|
||||
// Fields pubkey and withdrawal_credentials are constant
|
||||
if (i == 0 || i == 1) && cache.initialized {
|
||||
None
|
||||
} else if process_field_by_index(self, i, leaf, !cache.initialized) {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
cache.update_merkle_root(arena, dirty_indices)
|
||||
}
|
||||
}
|
||||
|
||||
fn process_field_by_index(
|
||||
v: &Validator,
|
||||
field_idx: usize,
|
||||
leaf: &mut Hash256,
|
||||
force_update: bool,
|
||||
) -> bool {
|
||||
match field_idx {
|
||||
0 => process_pubkey_bytes_field(&v.pubkey, leaf, force_update),
|
||||
1 => process_slice_field(v.withdrawal_credentials.as_bytes(), leaf, force_update),
|
||||
2 => process_u64_field(v.effective_balance, leaf, force_update),
|
||||
3 => process_bool_field(v.slashed, leaf, force_update),
|
||||
4 => process_epoch_field(v.activation_eligibility_epoch, leaf, force_update),
|
||||
5 => process_epoch_field(v.activation_epoch, leaf, force_update),
|
||||
6 => process_epoch_field(v.exit_epoch, leaf, force_update),
|
||||
7 => process_epoch_field(v.withdrawable_epoch, leaf, force_update),
|
||||
_ => panic!(
|
||||
"Validator type only has {} fields, {} out of bounds",
|
||||
NUM_VALIDATOR_FIELDS, field_idx
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_pubkey_bytes_field(
|
||||
val: &PublicKeyBytes,
|
||||
leaf: &mut Hash256,
|
||||
force_update: bool,
|
||||
) -> bool {
|
||||
let new_tree_hash = merkle_root(val.as_slice(), 0);
|
||||
process_slice_field(new_tree_hash.as_bytes(), leaf, force_update)
|
||||
}
|
||||
|
||||
fn process_slice_field(new_tree_hash: &[u8], leaf: &mut Hash256, force_update: bool) -> bool {
|
||||
if force_update || leaf.as_bytes() != new_tree_hash {
|
||||
leaf.assign_from_slice(&new_tree_hash);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn process_u64_field(val: u64, leaf: &mut Hash256, force_update: bool) -> bool {
|
||||
let new_tree_hash = int_to_fixed_bytes32(val);
|
||||
process_slice_field(&new_tree_hash[..], leaf, force_update)
|
||||
}
|
||||
|
||||
fn process_epoch_field(val: Epoch, leaf: &mut Hash256, force_update: bool) -> bool {
|
||||
process_u64_field(val.as_u64(), leaf, force_update)
|
||||
}
|
||||
|
||||
fn process_bool_field(val: bool, leaf: &mut Hash256, force_update: bool) -> bool {
|
||||
process_u64_field(val as u64, leaf, force_update)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::Epoch;
|
||||
use rand::SeedableRng;
|
||||
use rand_xorshift::XorShiftRng;
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
fn test_validator_tree_hash(v: &Validator) {
|
||||
let arena = &mut CacheArena::default();
|
||||
|
||||
let mut cache = v.new_tree_hash_cache(arena);
|
||||
// With a fresh cache
|
||||
assert_eq!(
|
||||
&v.tree_hash_root()[..],
|
||||
v.recalculate_tree_hash_root(arena, &mut cache)
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
"{:?}",
|
||||
v
|
||||
);
|
||||
// With a completely up-to-date cache
|
||||
assert_eq!(
|
||||
&v.tree_hash_root()[..],
|
||||
v.recalculate_tree_hash_root(arena, &mut cache)
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
"{:?}",
|
||||
v
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_validator() {
|
||||
test_validator_tree_hash(&Validator::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zeroed_validator() {
|
||||
let mut v = Validator::default();
|
||||
v.activation_eligibility_epoch = Epoch::from(0u64);
|
||||
v.activation_epoch = Epoch::from(0u64);
|
||||
test_validator_tree_hash(&v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_validators() {
|
||||
let mut rng = XorShiftRng::from_seed([0xf1; 16]);
|
||||
let num_validators = 1000;
|
||||
(0..num_validators)
|
||||
.map(|_| Validator::random_for_test(&mut rng))
|
||||
.for_each(|v| test_validator_tree_hash(&v));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn smallvec_size_check() {
|
||||
// If this test fails we need to go and reassess the length of the `SmallVec` in
|
||||
// `cached_tree_hash::TreeHashCache`. If the size of the `SmallVec` is too slow we're going
|
||||
// to start doing heap allocations for each validator, this will fragment memory and slow
|
||||
// us down.
|
||||
assert!(NUM_VALIDATOR_FIELDS <= 8,);
|
||||
}
|
||||
}
|
||||
3
consensus/types/src/utils.rs
Normal file
3
consensus/types/src/utils.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod serde_utils;
|
||||
|
||||
pub use serde_utils::*;
|
||||
132
consensus/types/src/utils/serde_utils.rs
Normal file
132
consensus/types/src/utils/serde_utils.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub const FORK_BYTES_LEN: usize = 4;
|
||||
pub const GRAFFITI_BYTES_LEN: usize = 32;
|
||||
|
||||
pub fn u8_from_hex_str<'de, D>(deserializer: D) -> Result<u8, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
|
||||
let start = match s.as_str().get(2..) {
|
||||
Some(start) => start,
|
||||
None => return Err(D::Error::custom("string length too small")),
|
||||
};
|
||||
u8::from_str_radix(&start, 16).map_err(D::Error::custom)
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)] // Serde requires the `byte` to be a ref.
|
||||
pub fn u8_to_hex_str<S>(byte: &u8, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut hex: String = "0x".to_string();
|
||||
hex.push_str(&hex::encode(&[*byte]));
|
||||
|
||||
serializer.serialize_str(&hex)
|
||||
}
|
||||
|
||||
pub fn u32_from_hex_str<'de, D>(deserializer: D) -> Result<u32, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
let start = s
|
||||
.as_str()
|
||||
.get(2..)
|
||||
.ok_or_else(|| D::Error::custom("string length too small"))?;
|
||||
|
||||
u32::from_str_radix(&start, 16)
|
||||
.map_err(D::Error::custom)
|
||||
.map(u32::from_be)
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)] // Serde requires the `num` to be a ref.
|
||||
pub fn u32_to_hex_str<S>(num: &u32, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut hex: String = "0x".to_string();
|
||||
let bytes = num.to_le_bytes();
|
||||
hex.push_str(&hex::encode(&bytes));
|
||||
|
||||
serializer.serialize_str(&hex)
|
||||
}
|
||||
|
||||
pub fn fork_from_hex_str<'de, D>(deserializer: D) -> Result<[u8; FORK_BYTES_LEN], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
let mut array = [0 as u8; FORK_BYTES_LEN];
|
||||
|
||||
let start = s
|
||||
.as_str()
|
||||
.get(2..)
|
||||
.ok_or_else(|| D::Error::custom("string length too small"))?;
|
||||
let decoded: Vec<u8> = hex::decode(&start).map_err(D::Error::custom)?;
|
||||
|
||||
if decoded.len() != FORK_BYTES_LEN {
|
||||
return Err(D::Error::custom("Fork length too long"));
|
||||
}
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
if i > decoded.len() {
|
||||
break;
|
||||
}
|
||||
*item = decoded[i];
|
||||
}
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn fork_to_hex_str<S>(bytes: &[u8; FORK_BYTES_LEN], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut hex_string: String = "0x".to_string();
|
||||
hex_string.push_str(&hex::encode(&bytes));
|
||||
|
||||
serializer.serialize_str(&hex_string)
|
||||
}
|
||||
|
||||
pub fn graffiti_to_hex_str<S>(
|
||||
bytes: &[u8; GRAFFITI_BYTES_LEN],
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut hex_string: String = "0x".to_string();
|
||||
hex_string.push_str(&hex::encode(&bytes));
|
||||
|
||||
serializer.serialize_str(&hex_string)
|
||||
}
|
||||
|
||||
pub fn graffiti_from_hex_str<'de, D>(deserializer: D) -> Result<[u8; GRAFFITI_BYTES_LEN], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
let mut array = [0 as u8; GRAFFITI_BYTES_LEN];
|
||||
|
||||
let start = s
|
||||
.as_str()
|
||||
.get(2..)
|
||||
.ok_or_else(|| D::Error::custom("string length too small"))?;
|
||||
let decoded: Vec<u8> = hex::decode(&start).map_err(D::Error::custom)?;
|
||||
|
||||
if decoded.len() > GRAFFITI_BYTES_LEN {
|
||||
return Err(D::Error::custom("Fork length too long"));
|
||||
}
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
if i > decoded.len() {
|
||||
break;
|
||||
}
|
||||
*item = decoded[i];
|
||||
}
|
||||
Ok(array)
|
||||
}
|
||||
144
consensus/types/src/validator.rs
Normal file
144
consensus/types/src/validator.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use crate::{
|
||||
test_utils::TestRandom, BeaconState, ChainSpec, Epoch, EthSpec, Hash256, PublicKeyBytes,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Information about a `BeaconChain` validator.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)]
|
||||
pub struct Validator {
|
||||
pub pubkey: PublicKeyBytes,
|
||||
pub withdrawal_credentials: Hash256,
|
||||
pub effective_balance: u64,
|
||||
pub slashed: bool,
|
||||
pub activation_eligibility_epoch: Epoch,
|
||||
pub activation_epoch: Epoch,
|
||||
pub exit_epoch: Epoch,
|
||||
pub withdrawable_epoch: Epoch,
|
||||
}
|
||||
|
||||
impl Validator {
|
||||
/// Returns `true` if the validator is considered active at some epoch.
|
||||
pub fn is_active_at(&self, epoch: Epoch) -> bool {
|
||||
self.activation_epoch <= epoch && epoch < self.exit_epoch
|
||||
}
|
||||
|
||||
/// Returns `true` if the validator is slashable at some epoch.
|
||||
pub fn is_slashable_at(&self, epoch: Epoch) -> bool {
|
||||
!self.slashed && self.activation_epoch <= epoch && epoch < self.withdrawable_epoch
|
||||
}
|
||||
|
||||
/// Returns `true` if the validator is considered exited at some epoch.
|
||||
pub fn is_exited_at(&self, epoch: Epoch) -> bool {
|
||||
self.exit_epoch <= epoch
|
||||
}
|
||||
|
||||
/// Returns `true` if the validator is able to withdraw at some epoch.
|
||||
pub fn is_withdrawable_at(&self, epoch: Epoch) -> bool {
|
||||
epoch >= self.withdrawable_epoch
|
||||
}
|
||||
|
||||
/// Returns `true` if the validator is eligible to join the activation queue.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn is_eligible_for_activation_queue(&self, spec: &ChainSpec) -> bool {
|
||||
self.activation_eligibility_epoch == spec.far_future_epoch
|
||||
&& self.effective_balance == spec.max_effective_balance
|
||||
}
|
||||
|
||||
/// Returns `true` if the validator is eligible to be activated.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
pub fn is_eligible_for_activation<E: EthSpec>(
|
||||
&self,
|
||||
state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
// Placement in queue is finalized
|
||||
self.activation_eligibility_epoch <= state.finalized_checkpoint.epoch
|
||||
// Has not yet been activated
|
||||
&& self.activation_epoch == spec.far_future_epoch
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Validator {
|
||||
/// Yields a "default" `Validator`. Primarily used for testing.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pubkey: PublicKeyBytes::empty(),
|
||||
withdrawal_credentials: Hash256::default(),
|
||||
activation_eligibility_epoch: Epoch::from(std::u64::MAX),
|
||||
activation_epoch: Epoch::from(std::u64::MAX),
|
||||
exit_epoch: Epoch::from(std::u64::MAX),
|
||||
withdrawable_epoch: Epoch::from(std::u64::MAX),
|
||||
slashed: false,
|
||||
effective_balance: std::u64::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default() {
|
||||
let v = Validator::default();
|
||||
|
||||
let epoch = Epoch::new(0);
|
||||
|
||||
assert_eq!(v.is_active_at(epoch), false);
|
||||
assert_eq!(v.is_exited_at(epoch), false);
|
||||
assert_eq!(v.is_withdrawable_at(epoch), false);
|
||||
assert_eq!(v.slashed, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_active_at() {
|
||||
let epoch = Epoch::new(10);
|
||||
|
||||
let v = Validator {
|
||||
activation_epoch: epoch,
|
||||
..Validator::default()
|
||||
};
|
||||
|
||||
assert_eq!(v.is_active_at(epoch - 1), false);
|
||||
assert_eq!(v.is_active_at(epoch), true);
|
||||
assert_eq!(v.is_active_at(epoch + 1), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_exited_at() {
|
||||
let epoch = Epoch::new(10);
|
||||
|
||||
let v = Validator {
|
||||
exit_epoch: epoch,
|
||||
..Validator::default()
|
||||
};
|
||||
|
||||
assert_eq!(v.is_exited_at(epoch - 1), false);
|
||||
assert_eq!(v.is_exited_at(epoch), true);
|
||||
assert_eq!(v.is_exited_at(epoch + 1), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_withdrawable_at() {
|
||||
let epoch = Epoch::new(10);
|
||||
|
||||
let v = Validator {
|
||||
withdrawable_epoch: epoch,
|
||||
..Validator::default()
|
||||
};
|
||||
|
||||
assert_eq!(v.is_withdrawable_at(epoch - 1), false);
|
||||
assert_eq!(v.is_withdrawable_at(epoch), true);
|
||||
assert_eq!(v.is_withdrawable_at(epoch + 1), true);
|
||||
}
|
||||
|
||||
ssz_and_tree_hash_tests!(Validator);
|
||||
}
|
||||
52
consensus/types/src/voluntary_exit.rs
Normal file
52
consensus/types/src/voluntary_exit.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use crate::{
|
||||
test_utils::TestRandom, ChainSpec, Domain, Epoch, Fork, Hash256, SecretKey, Signature,
|
||||
SignedRoot, SignedVoluntaryExit,
|
||||
};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// An exit voluntarily submitted a validator who wishes to withdraw.
|
||||
///
|
||||
/// Spec v0.11.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct VoluntaryExit {
|
||||
/// Earliest epoch when voluntary exit can be processed.
|
||||
pub epoch: Epoch,
|
||||
pub validator_index: u64,
|
||||
}
|
||||
|
||||
impl SignedRoot for VoluntaryExit {}
|
||||
|
||||
impl VoluntaryExit {
|
||||
pub fn sign(
|
||||
self,
|
||||
secret_key: &SecretKey,
|
||||
fork: &Fork,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> SignedVoluntaryExit {
|
||||
let domain = spec.get_domain(
|
||||
self.epoch,
|
||||
Domain::VoluntaryExit,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
);
|
||||
let message = self.signing_root(domain);
|
||||
let signature = Signature::new(message.as_bytes(), &secret_key);
|
||||
SignedVoluntaryExit {
|
||||
message: self,
|
||||
signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(VoluntaryExit);
|
||||
}
|
||||
Reference in New Issue
Block a user