mirror of
https://github.com/sigp/lighthouse.git
synced 2026-06-29 19:04:27 +00:00
Altair consensus changes and refactors (#2279)
## Proposed Changes Implement the consensus changes necessary for the upcoming Altair hard fork. ## Additional Info This is quite a heavy refactor, with pivotal types like the `BeaconState` and `BeaconBlock` changing from structs to enums. This ripples through the whole codebase with field accesses changing to methods, e.g. `state.slot` => `state.slot()`. Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
@@ -1,58 +1,71 @@
|
||||
use crate::beacon_block_body::{
|
||||
BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyRef, BeaconBlockBodyRefMut,
|
||||
};
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
use bls::Signature;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{Decode, DecodeError};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use superstruct::superstruct;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash::TreeHash;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// A block of the `BeaconChain`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[superstruct(
|
||||
variants(Base, Altair),
|
||||
variant_attributes(
|
||||
derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Encode,
|
||||
Decode,
|
||||
TreeHash,
|
||||
TestRandom
|
||||
),
|
||||
serde(bound = "T: EthSpec", deny_unknown_fields),
|
||||
cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))
|
||||
),
|
||||
ref_attributes(derive(Debug, PartialEq, TreeHash))
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, TreeHash)]
|
||||
#[serde(untagged)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
pub struct BeaconBlock<T: EthSpec> {
|
||||
#[superstruct(getter(copy))]
|
||||
pub slot: Slot,
|
||||
#[superstruct(getter(copy))]
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub proposer_index: u64,
|
||||
#[superstruct(getter(copy))]
|
||||
pub parent_root: Hash256,
|
||||
#[superstruct(getter(copy))]
|
||||
pub state_root: Hash256,
|
||||
pub body: BeaconBlockBody<T>,
|
||||
#[superstruct(only(Base), partial_getter(rename = "body_base"))]
|
||||
pub body: BeaconBlockBodyBase<T>,
|
||||
#[superstruct(only(Altair), partial_getter(rename = "body_altair"))]
|
||||
pub body: BeaconBlockBodyAltair<T>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SignedRoot for BeaconBlock<T> {}
|
||||
impl<'a, T: EthSpec> SignedRoot for BeaconBlockRef<'a, T> {}
|
||||
|
||||
impl<T: EthSpec> BeaconBlock<T> {
|
||||
/// Returns an empty block to be used during genesis.
|
||||
///
|
||||
/// Spec v0.12.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(),
|
||||
eth1_data: Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
deposit_count: 0,
|
||||
},
|
||||
graffiti: Graffiti::default(),
|
||||
proposer_slashings: VariableList::empty(),
|
||||
attester_slashings: VariableList::empty(),
|
||||
attestations: VariableList::empty(),
|
||||
deposits: VariableList::empty(),
|
||||
voluntary_exits: VariableList::empty(),
|
||||
},
|
||||
if spec.altair_fork_epoch == Some(T::genesis_epoch()) {
|
||||
Self::Altair(BeaconBlockAltair::empty(spec))
|
||||
} else {
|
||||
Self::Base(BeaconBlockBase::empty(spec))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a block where the block has the max possible operations.
|
||||
/// Return a block where the block has maximum size.
|
||||
pub fn full(spec: &ChainSpec) -> BeaconBlock<T> {
|
||||
let header = BeaconBlockHeader {
|
||||
slot: Slot::new(1),
|
||||
@@ -114,7 +127,8 @@ impl<T: EthSpec> BeaconBlock<T> {
|
||||
signature: Signature::empty(),
|
||||
};
|
||||
|
||||
let mut block: BeaconBlock<T> = BeaconBlock::empty(spec);
|
||||
// FIXME(altair): use an Altair block (they're bigger)
|
||||
let mut block = BeaconBlockBase::<T>::empty(spec);
|
||||
for _ in 0..T::MaxProposerSlashings::to_usize() {
|
||||
block
|
||||
.body
|
||||
@@ -143,19 +157,50 @@ impl<T: EthSpec> BeaconBlock<T> {
|
||||
for _ in 0..T::MaxAttestations::to_usize() {
|
||||
block.body.attestations.push(attestation.clone()).unwrap();
|
||||
}
|
||||
block
|
||||
BeaconBlock::Base(block)
|
||||
}
|
||||
|
||||
/// Returns the epoch corresponding to `self.slot`.
|
||||
/// Custom SSZ decoder that takes a `ChainSpec` as context.
|
||||
pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result<Self, ssz::DecodeError> {
|
||||
let slot_len = <Slot as Decode>::ssz_fixed_len();
|
||||
let slot_bytes = bytes
|
||||
.get(0..slot_len)
|
||||
.ok_or(DecodeError::InvalidByteLength {
|
||||
len: bytes.len(),
|
||||
expected: slot_len,
|
||||
})?;
|
||||
|
||||
let slot = Slot::from_ssz_bytes(slot_bytes)?;
|
||||
let epoch = slot.epoch(T::slots_per_epoch());
|
||||
|
||||
if spec
|
||||
.altair_fork_epoch
|
||||
.map_or(true, |altair_epoch| epoch < altair_epoch)
|
||||
{
|
||||
BeaconBlockBase::from_ssz_bytes(bytes).map(Self::Base)
|
||||
} else {
|
||||
BeaconBlockAltair::from_ssz_bytes(bytes).map(Self::Altair)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience accessor for the `body` as a `BeaconBlockBodyRef`.
|
||||
pub fn body(&self) -> BeaconBlockBodyRef<'_, T> {
|
||||
self.to_ref().body()
|
||||
}
|
||||
|
||||
/// Convenience accessor for the `body` as a `BeaconBlockBodyRefMut`.
|
||||
pub fn body_mut(&mut self) -> BeaconBlockBodyRefMut<'_, T> {
|
||||
self.to_mut().body_mut()
|
||||
}
|
||||
|
||||
/// Returns the epoch corresponding to `self.slot()`.
|
||||
pub fn epoch(&self) -> Epoch {
|
||||
self.slot.epoch(T::slots_per_epoch())
|
||||
self.slot().epoch(T::slots_per_epoch())
|
||||
}
|
||||
|
||||
/// Returns the `tree_hash_root` of the block.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn canonical_root(&self) -> Hash256 {
|
||||
Hash256::from_slice(&self.tree_hash_root()[..])
|
||||
self.tree_hash_root()
|
||||
}
|
||||
|
||||
/// Returns a full `BeaconBlockHeader` of this block.
|
||||
@@ -164,26 +209,18 @@ impl<T: EthSpec> BeaconBlock<T> {
|
||||
/// when you want to have the block _and_ the header.
|
||||
///
|
||||
/// Note: performs a full tree-hash of `self.body`.
|
||||
///
|
||||
/// Spec v0.12.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()[..]),
|
||||
}
|
||||
self.to_ref().block_header()
|
||||
}
|
||||
|
||||
/// Returns a "temporary" header, where the `state_root` is `Hash256::zero()`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn temporary_block_header(&self) -> BeaconBlockHeader {
|
||||
BeaconBlockHeader {
|
||||
state_root: Hash256::zero(),
|
||||
..self.block_header()
|
||||
}
|
||||
self.to_ref().temporary_block_header()
|
||||
}
|
||||
|
||||
/// Return the tree hash root of the block's body.
|
||||
pub fn body_root(&self) -> Hash256 {
|
||||
self.to_ref().body_root()
|
||||
}
|
||||
|
||||
/// Signs `self`, producing a `SignedBeaconBlock`.
|
||||
@@ -202,9 +239,106 @@ impl<T: EthSpec> BeaconBlock<T> {
|
||||
);
|
||||
let message = self.signing_root(domain);
|
||||
let signature = secret_key.sign(message);
|
||||
SignedBeaconBlock {
|
||||
message: self,
|
||||
signature,
|
||||
SignedBeaconBlock::from_block(self, signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> BeaconBlockRef<'a, T> {
|
||||
/// Convenience accessor for the `body` as a `BeaconBlockBodyRef`.
|
||||
pub fn body(&self) -> BeaconBlockBodyRef<'a, T> {
|
||||
match self {
|
||||
BeaconBlockRef::Base(block) => BeaconBlockBodyRef::Base(&block.body),
|
||||
BeaconBlockRef::Altair(block) => BeaconBlockBodyRef::Altair(&block.body),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the tree hash root of the block's body.
|
||||
pub fn body_root(&self) -> Hash256 {
|
||||
match self {
|
||||
BeaconBlockRef::Base(block) => block.body.tree_hash_root(),
|
||||
BeaconBlockRef::Altair(block) => block.body.tree_hash_root(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a full `BeaconBlockHeader` of this block.
|
||||
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: self.body_root(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a "temporary" header, where the `state_root` is `Hash256::zero()`.
|
||||
pub fn temporary_block_header(self) -> BeaconBlockHeader {
|
||||
BeaconBlockHeader {
|
||||
state_root: Hash256::zero(),
|
||||
..self.block_header()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> BeaconBlockRefMut<'a, T> {
|
||||
/// Convert a mutable reference to a beacon block to a mutable ref to its body.
|
||||
pub fn body_mut(self) -> BeaconBlockBodyRefMut<'a, T> {
|
||||
match self {
|
||||
BeaconBlockRefMut::Base(block) => BeaconBlockBodyRefMut::Base(&mut block.body),
|
||||
BeaconBlockRefMut::Altair(block) => BeaconBlockBodyRefMut::Altair(&mut block.body),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> BeaconBlockBase<T> {
|
||||
/// Returns an empty block to be used during genesis.
|
||||
pub fn empty(spec: &ChainSpec) -> Self {
|
||||
BeaconBlockBase {
|
||||
slot: spec.genesis_slot,
|
||||
proposer_index: 0,
|
||||
parent_root: Hash256::zero(),
|
||||
state_root: Hash256::zero(),
|
||||
body: BeaconBlockBodyBase {
|
||||
randao_reveal: Signature::empty(),
|
||||
eth1_data: Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
deposit_count: 0,
|
||||
},
|
||||
graffiti: Graffiti::default(),
|
||||
proposer_slashings: VariableList::empty(),
|
||||
attester_slashings: VariableList::empty(),
|
||||
attestations: VariableList::empty(),
|
||||
deposits: VariableList::empty(),
|
||||
voluntary_exits: VariableList::empty(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> BeaconBlockAltair<T> {
|
||||
/// Returns an empty block to be used during genesis.
|
||||
pub fn empty(spec: &ChainSpec) -> Self {
|
||||
BeaconBlockAltair {
|
||||
slot: spec.genesis_slot,
|
||||
proposer_index: 0,
|
||||
parent_root: Hash256::zero(),
|
||||
state_root: Hash256::zero(),
|
||||
body: BeaconBlockBodyAltair {
|
||||
randao_reveal: Signature::empty(),
|
||||
eth1_data: Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
deposit_count: 0,
|
||||
},
|
||||
graffiti: Graffiti::default(),
|
||||
proposer_slashings: VariableList::empty(),
|
||||
attester_slashings: VariableList::empty(),
|
||||
attestations: VariableList::empty(),
|
||||
deposits: VariableList::empty(),
|
||||
voluntary_exits: VariableList::empty(),
|
||||
sync_aggregate: SyncAggregate::empty(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,6 +346,110 @@ impl<T: EthSpec> BeaconBlock<T> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{test_ssz_tree_hash_pair_with, SeedableRng, TestRandom, XorShiftRng};
|
||||
use crate::{ForkName, MainnetEthSpec};
|
||||
use ssz::Encode;
|
||||
|
||||
ssz_and_tree_hash_tests!(BeaconBlock<MainnetEthSpec>);
|
||||
type BeaconBlock = super::BeaconBlock<MainnetEthSpec>;
|
||||
type BeaconBlockBase = super::BeaconBlockBase<MainnetEthSpec>;
|
||||
type BeaconBlockAltair = super::BeaconBlockAltair<MainnetEthSpec>;
|
||||
|
||||
#[test]
|
||||
fn roundtrip_base_block() {
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
let spec = &ForkName::Base.make_genesis_spec(MainnetEthSpec::default_spec());
|
||||
|
||||
let inner_block = BeaconBlockBase {
|
||||
slot: Slot::random_for_test(rng),
|
||||
proposer_index: u64::random_for_test(rng),
|
||||
parent_root: Hash256::random_for_test(rng),
|
||||
state_root: Hash256::random_for_test(rng),
|
||||
body: BeaconBlockBodyBase::random_for_test(rng),
|
||||
};
|
||||
let block = BeaconBlock::Base(inner_block.clone());
|
||||
|
||||
test_ssz_tree_hash_pair_with(&block, &inner_block, |bytes| {
|
||||
BeaconBlock::from_ssz_bytes(bytes, spec)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_altair_block() {
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
let spec = &ForkName::Altair.make_genesis_spec(MainnetEthSpec::default_spec());
|
||||
|
||||
let inner_block = BeaconBlockAltair {
|
||||
slot: Slot::random_for_test(rng),
|
||||
proposer_index: u64::random_for_test(rng),
|
||||
parent_root: Hash256::random_for_test(rng),
|
||||
state_root: Hash256::random_for_test(rng),
|
||||
body: BeaconBlockBodyAltair::random_for_test(rng),
|
||||
};
|
||||
let block = BeaconBlock::Altair(inner_block.clone());
|
||||
|
||||
test_ssz_tree_hash_pair_with(&block, &inner_block, |bytes| {
|
||||
BeaconBlock::from_ssz_bytes(bytes, spec)
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_base_and_altair() {
|
||||
type E = MainnetEthSpec;
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let fork_epoch = Epoch::from_ssz_bytes(&[7, 6, 5, 4, 3, 2, 1, 0]).unwrap();
|
||||
|
||||
let base_epoch = fork_epoch.saturating_sub(1_u64);
|
||||
let base_slot = base_epoch.end_slot(E::slots_per_epoch());
|
||||
let altair_epoch = fork_epoch;
|
||||
let altair_slot = altair_epoch.start_slot(E::slots_per_epoch());
|
||||
|
||||
let mut spec = E::default_spec();
|
||||
spec.altair_fork_epoch = Some(fork_epoch);
|
||||
|
||||
// BeaconBlockBase
|
||||
{
|
||||
let good_base_block = BeaconBlock::Base(BeaconBlockBase {
|
||||
slot: base_slot,
|
||||
..<_>::random_for_test(rng)
|
||||
});
|
||||
// It's invalid to have a base block with a slot higher than the fork epoch.
|
||||
let bad_base_block = {
|
||||
let mut bad = good_base_block.clone();
|
||||
*bad.slot_mut() = altair_slot;
|
||||
bad
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
BeaconBlock::from_ssz_bytes(&good_base_block.as_ssz_bytes(), &spec)
|
||||
.expect("good base block can be decoded"),
|
||||
good_base_block
|
||||
);
|
||||
BeaconBlock::from_ssz_bytes(&bad_base_block.as_ssz_bytes(), &spec)
|
||||
.expect_err("bad base block cannot be decoded");
|
||||
}
|
||||
|
||||
// BeaconBlockAltair
|
||||
{
|
||||
let good_altair_block = BeaconBlock::Altair(BeaconBlockAltair {
|
||||
slot: altair_slot,
|
||||
..<_>::random_for_test(rng)
|
||||
});
|
||||
// It's invalid to have an Altair block with a epoch lower than the fork epoch.
|
||||
let bad_altair_block = {
|
||||
let mut bad = good_altair_block.clone();
|
||||
*bad.slot_mut() = base_slot;
|
||||
bad
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
BeaconBlock::from_ssz_bytes(&good_altair_block.as_ssz_bytes(), &spec)
|
||||
.expect("good altair block can be decoded"),
|
||||
good_altair_block
|
||||
);
|
||||
BeaconBlock::from_ssz_bytes(&bad_altair_block.as_ssz_bytes(), &spec)
|
||||
.expect_err("bad altair block cannot be decoded");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use ssz_types::VariableList;
|
||||
use superstruct::superstruct;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// The body of a `BeaconChain` block, containing operations.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
/// This *superstruct* abstracts over the hard-fork.
|
||||
#[superstruct(
|
||||
variants(Base, Altair),
|
||||
variant_attributes(
|
||||
derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Encode,
|
||||
Decode,
|
||||
TreeHash,
|
||||
TestRandom
|
||||
),
|
||||
serde(bound = "T: EthSpec", deny_unknown_fields),
|
||||
cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))
|
||||
)
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
pub struct BeaconBlockBody<T: EthSpec> {
|
||||
pub randao_reveal: Signature,
|
||||
pub eth1_data: Eth1Data,
|
||||
@@ -22,11 +41,18 @@ pub struct BeaconBlockBody<T: EthSpec> {
|
||||
pub attestations: VariableList<Attestation<T>, T::MaxAttestations>,
|
||||
pub deposits: VariableList<Deposit, T::MaxDeposits>,
|
||||
pub voluntary_exits: VariableList<SignedVoluntaryExit, T::MaxVoluntaryExits>,
|
||||
#[superstruct(only(Altair))]
|
||||
pub sync_aggregate: SyncAggregate<T>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_and_tree_hash_tests!(BeaconBlockBody<MainnetEthSpec>);
|
||||
mod base {
|
||||
use super::super::*;
|
||||
ssz_and_tree_hash_tests!(BeaconBlockBodyBase<MainnetEthSpec>);
|
||||
}
|
||||
mod altair {
|
||||
use super::super::*;
|
||||
ssz_and_tree_hash_tests!(BeaconBlockBodyAltair<MainnetEthSpec>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,19 +33,6 @@ impl BeaconBlockHeader {
|
||||
Hash256::from_slice(&self.tree_hash_root()[..])
|
||||
}
|
||||
|
||||
/// Given a `body`, consumes `self` and returns a complete `BeaconBlock`.
|
||||
///
|
||||
/// Spec v0.12.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,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,7 @@ impl CommitteeCache {
|
||||
return Err(Error::ZeroSlotsPerEpoch);
|
||||
}
|
||||
|
||||
let active_validator_indices = get_active_validator_indices(&state.validators, epoch);
|
||||
let active_validator_indices = get_active_validator_indices(state.validators(), epoch);
|
||||
|
||||
if active_validator_indices.is_empty() {
|
||||
return Err(Error::InsufficientValidators);
|
||||
@@ -59,13 +59,15 @@ impl CommitteeCache {
|
||||
.ok_or(Error::UnableToShuffle)?;
|
||||
|
||||
// The use of `NonZeroUsize` reduces the maximum number of possible validators by one.
|
||||
if state.validators.len() == usize::max_value() {
|
||||
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);
|
||||
let mut shuffling_positions = vec![None; state.validators().len()];
|
||||
for (i, &v) in shuffling.iter().enumerate() {
|
||||
*shuffling_positions
|
||||
.get_mut(v)
|
||||
.ok_or(Error::ShuffleIndexOutOfBounds(v))? = NonZeroUsize::new(i + 1);
|
||||
}
|
||||
|
||||
Ok(CommitteeCache {
|
||||
@@ -229,7 +231,7 @@ impl CommitteeCache {
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
fn compute_committee(&self, index: usize) -> Option<&[usize]> {
|
||||
Some(&self.shuffling[self.compute_committee_range(index)?])
|
||||
self.shuffling.get(self.compute_committee_range(index)?)
|
||||
}
|
||||
|
||||
/// Returns a range of `self.shuffling` that represents the `index`'th committee in the epoch.
|
||||
@@ -255,7 +257,7 @@ impl CommitteeCache {
|
||||
/// 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> {
|
||||
pub fn shuffled_position(&self, validator_index: usize) -> Option<usize> {
|
||||
self.shuffling_positions
|
||||
.get(validator_index)?
|
||||
.and_then(|p| Some(p.get() - 1))
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
#![cfg(test)]
|
||||
use super::*;
|
||||
use crate::{test_utils::*, *};
|
||||
use crate::test_utils::*;
|
||||
use beacon_chain::store::StoreConfig;
|
||||
use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType};
|
||||
use beacon_chain::types::*;
|
||||
use swap_or_not_shuffle::shuffle_list;
|
||||
|
||||
pub const VALIDATOR_COUNT: usize = 16;
|
||||
|
||||
lazy_static! {
|
||||
/// A cached set of keys.
|
||||
static ref KEYPAIRS: Vec<Keypair> = generate_deterministic_keypairs(VALIDATOR_COUNT);
|
||||
}
|
||||
|
||||
fn get_harness<E: EthSpec>(validator_count: usize) -> BeaconChainHarness<EphemeralHarnessType<E>> {
|
||||
let harness = BeaconChainHarness::new_with_store_config(
|
||||
E::default(),
|
||||
None,
|
||||
KEYPAIRS[0..validator_count].to_vec(),
|
||||
StoreConfig::default(),
|
||||
);
|
||||
harness.advance_slot();
|
||||
harness
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_values() {
|
||||
@@ -16,27 +37,26 @@ fn default_values() {
|
||||
}
|
||||
|
||||
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
|
||||
let harness = get_harness(validator_count);
|
||||
let head_state = harness.get_current_state();
|
||||
if slot > Slot::new(0) {
|
||||
harness.add_attested_blocks_at_slots(
|
||||
head_state,
|
||||
Hash256::zero(),
|
||||
(1..slot.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
(0..validator_count).collect::<Vec<_>>().as_slice(),
|
||||
);
|
||||
}
|
||||
harness.get_current_state()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
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)
|
||||
);
|
||||
new_state::<MinimalEthSpec>(0, Slot::new(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -45,24 +65,22 @@ fn initializes_with_the_right_epoch() {
|
||||
let spec = &MinimalEthSpec::default_spec();
|
||||
|
||||
let cache = CommitteeCache::default();
|
||||
assert_eq!(cache.initialized_epoch, None);
|
||||
assert!(!cache.is_initialized_at(state.current_epoch()));
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.current_epoch(), &spec).unwrap();
|
||||
assert_eq!(cache.initialized_epoch, Some(state.current_epoch()));
|
||||
assert!(cache.is_initialized_at(state.current_epoch()));
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), &spec).unwrap();
|
||||
assert_eq!(cache.initialized_epoch, Some(state.previous_epoch()));
|
||||
assert!(cache.is_initialized_at(state.previous_epoch()));
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.next_epoch().unwrap(), &spec).unwrap();
|
||||
assert_eq!(cache.initialized_epoch, Some(state.next_epoch().unwrap()));
|
||||
assert!(cache.is_initialized_at(state.next_epoch().unwrap()));
|
||||
}
|
||||
|
||||
#[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 epoch = Epoch::new(6);
|
||||
let slot = epoch.start_slot(MinimalEthSpec::slots_per_epoch());
|
||||
|
||||
let mut state = new_state::<MinimalEthSpec>(num_validators, slot);
|
||||
@@ -72,7 +90,7 @@ fn shuffles_for_the_right_epoch() {
|
||||
.map(|i| Hash256::from_low_u64_be(i as u64))
|
||||
.collect();
|
||||
|
||||
state.randao_mixes = FixedVector::from(distinct_hashes);
|
||||
*state.randao_mixes_mut() = FixedVector::from(distinct_hashes);
|
||||
|
||||
let previous_seed = state
|
||||
.get_seed(state.previous_epoch(), Domain::BeaconAttester, spec)
|
||||
@@ -97,9 +115,9 @@ fn shuffles_for_the_right_epoch() {
|
||||
};
|
||||
|
||||
let assert_shuffling_positions_accurate = |cache: &CommitteeCache| {
|
||||
for (i, v) in cache.shuffling.iter().enumerate() {
|
||||
for (i, v) in cache.shuffling().iter().enumerate() {
|
||||
assert_eq!(
|
||||
cache.shuffling_positions[*v].unwrap().get() - 1,
|
||||
cache.shuffled_position(*v).unwrap(),
|
||||
i,
|
||||
"Shuffling position inaccurate"
|
||||
);
|
||||
@@ -107,14 +125,14 @@ fn shuffles_for_the_right_epoch() {
|
||||
};
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.current_epoch(), spec).unwrap();
|
||||
assert_eq!(cache.shuffling, shuffling_with_seed(current_seed));
|
||||
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_eq!(cache.shuffling(), shuffling_with_seed(previous_seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.next_epoch().unwrap(), spec).unwrap();
|
||||
assert_eq!(cache.shuffling, shuffling_with_seed(next_seed));
|
||||
assert_eq!(cache.shuffling(), shuffling_with_seed(next_seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
}
|
||||
|
||||
@@ -11,22 +11,18 @@ pub struct ExitCache {
|
||||
}
|
||||
|
||||
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;
|
||||
/// Initialize a new cache for the given list of validators.
|
||||
pub fn new(validators: &[Validator], spec: &ChainSpec) -> Result<Self, BeaconStateError> {
|
||||
let mut exit_cache = ExitCache {
|
||||
initialized: true,
|
||||
..ExitCache::default()
|
||||
};
|
||||
// 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))
|
||||
.try_for_each(|validator| exit_cache.record_validator_exit(validator.exit_epoch))?;
|
||||
Ok(exit_cache)
|
||||
}
|
||||
|
||||
/// Check that the cache is initialized and return an error if it is not.
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::*;
|
||||
///
|
||||
/// The iterator has the following characteristics:
|
||||
///
|
||||
/// - Will only return *at most* `state.block_roots.len()` entries.
|
||||
/// - Will only return *at most* `state.block_roots().len()` entries.
|
||||
/// - Will not return slots prior to the genesis_slot.
|
||||
/// - Each call to next will result in a slot one less than the prior one (or `None`).
|
||||
/// - Skipped slots will contain the block root from the prior non-skipped slot.
|
||||
@@ -22,7 +22,7 @@ impl<'a, T: EthSpec> BlockRootsIter<'a, T> {
|
||||
Self {
|
||||
state,
|
||||
genesis_slot,
|
||||
prev: state.slot,
|
||||
prev: state.slot(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,8 @@ impl<'a, T: EthSpec> Iterator for BlockRootsIter<'a, T> {
|
||||
&& self.prev
|
||||
> self
|
||||
.state
|
||||
.slot
|
||||
.saturating_sub(self.state.block_roots.len() as u64)
|
||||
.slot()
|
||||
.saturating_sub(self.state.block_roots().len() as u64)
|
||||
{
|
||||
self.prev = self.prev.saturating_sub(1_u64);
|
||||
Some(
|
||||
@@ -73,12 +73,13 @@ mod test {
|
||||
|
||||
let mut state: BeaconState<E> = BeaconState::new(0, <_>::default(), &spec);
|
||||
|
||||
for i in 0..state.block_roots.len() {
|
||||
state.block_roots[i] = root_slot(i).1;
|
||||
for i in 0..state.block_roots().len() {
|
||||
state.block_roots_mut()[i] = root_slot(i).1;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
state.slot, spec.genesis_slot,
|
||||
state.slot(),
|
||||
spec.genesis_slot,
|
||||
"test assume a genesis slot state"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -87,22 +88,22 @@ mod test {
|
||||
"state at genesis slot has no history"
|
||||
);
|
||||
|
||||
state.slot = Slot::new(1);
|
||||
*state.slot_mut() = Slot::new(1);
|
||||
assert_eq!(
|
||||
all_roots(&state, &spec),
|
||||
vec![root_slot(0)],
|
||||
"first slot after genesis has one slot history"
|
||||
);
|
||||
|
||||
state.slot = Slot::new(2);
|
||||
*state.slot_mut() = Slot::new(2);
|
||||
assert_eq!(
|
||||
all_roots(&state, &spec),
|
||||
vec![root_slot(1), root_slot(0)],
|
||||
"second slot after genesis has two slot history"
|
||||
);
|
||||
|
||||
state.slot = Slot::from(state.block_roots.len() + 2);
|
||||
let expected = (2..state.block_roots.len() + 2)
|
||||
*state.slot_mut() = Slot::from(state.block_roots().len() + 2);
|
||||
let expected = (2..state.block_roots().len() + 2)
|
||||
.rev()
|
||||
.map(|i| (Slot::from(i), *state.get_block_root(Slot::from(i)).unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -120,12 +121,13 @@ mod test {
|
||||
|
||||
let mut state: BeaconState<E> = BeaconState::new(0, <_>::default(), &spec);
|
||||
|
||||
for i in 0..state.block_roots.len() {
|
||||
state.block_roots[i] = root_slot(i).1;
|
||||
for i in 0..state.block_roots().len() {
|
||||
state.block_roots_mut()[i] = root_slot(i).1;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
state.slot, spec.genesis_slot,
|
||||
state.slot(),
|
||||
spec.genesis_slot,
|
||||
"test assume a genesis slot state"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -134,14 +136,14 @@ mod test {
|
||||
"state at genesis slot has no history"
|
||||
);
|
||||
|
||||
state.slot = Slot::new(5);
|
||||
*state.slot_mut() = Slot::new(5);
|
||||
assert_eq!(
|
||||
all_roots(&state, &spec),
|
||||
vec![root_slot(4)],
|
||||
"first slot after genesis has one slot history"
|
||||
);
|
||||
|
||||
state.slot = Slot::new(6);
|
||||
*state.slot_mut() = Slot::new(6);
|
||||
assert_eq!(
|
||||
all_roots(&state, &spec),
|
||||
vec![root_slot(5), root_slot(4)],
|
||||
|
||||
@@ -1,23 +1,60 @@
|
||||
#![cfg(test)]
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
use beacon_chain::store::config::StoreConfig;
|
||||
use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType};
|
||||
use beacon_chain::types::{
|
||||
test_utils::TestRandom, BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateError,
|
||||
ChainSpec, CloneConfig, Domain, Epoch, EthSpec, FixedVector, Hash256, Keypair, MainnetEthSpec,
|
||||
MinimalEthSpec, RelativeEpoch, Slot,
|
||||
};
|
||||
use ssz::{Decode, Encode};
|
||||
use std::ops::Mul;
|
||||
use swap_or_not_shuffle::compute_shuffled_index;
|
||||
|
||||
ssz_and_tree_hash_tests!(FoundationBeaconState);
|
||||
pub const MAX_VALIDATOR_COUNT: usize = 129;
|
||||
pub const SLOT_OFFSET: Slot = Slot::new(1);
|
||||
|
||||
lazy_static! {
|
||||
/// A cached set of keys.
|
||||
static ref KEYPAIRS: Vec<Keypair> = generate_deterministic_keypairs(MAX_VALIDATOR_COUNT);
|
||||
}
|
||||
|
||||
fn get_harness<E: EthSpec>(
|
||||
validator_count: usize,
|
||||
slot: Slot,
|
||||
) -> BeaconChainHarness<EphemeralHarnessType<E>> {
|
||||
let harness = BeaconChainHarness::new_with_store_config(
|
||||
E::default(),
|
||||
None,
|
||||
KEYPAIRS[0..validator_count].to_vec(),
|
||||
StoreConfig::default(),
|
||||
);
|
||||
|
||||
let skip_to_slot = slot - SLOT_OFFSET;
|
||||
if skip_to_slot > Slot::new(0) {
|
||||
let slots = (skip_to_slot.as_u64()..=slot.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>();
|
||||
let state = harness.get_current_state();
|
||||
harness.add_attested_blocks_at_slots(
|
||||
state,
|
||||
Hash256::zero(),
|
||||
slots.as_slice(),
|
||||
(0..validator_count).collect::<Vec<_>>().as_slice(),
|
||||
);
|
||||
}
|
||||
harness
|
||||
}
|
||||
|
||||
fn build_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
|
||||
get_harness(validator_count, Slot::new(0))
|
||||
.chain
|
||||
.head_beacon_state()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
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_deterministic_keypairs(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, spec: &ChainSpec| {
|
||||
@@ -56,9 +93,9 @@ fn test_beacon_proposer_index<T: EthSpec>() {
|
||||
}
|
||||
|
||||
// Test with two validators per slot, first validator has zero balance.
|
||||
let mut state = build_state((T::slots_per_epoch() as usize).mul(2));
|
||||
let mut state = build_state::<T>((T::slots_per_epoch() as usize).mul(2));
|
||||
let slot0_candidate0 = ith_candidate(&state, Slot::new(0), 0, &spec);
|
||||
state.validators[slot0_candidate0].effective_balance = 0;
|
||||
state.validators_mut()[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);
|
||||
@@ -81,17 +118,9 @@ fn test_cache_initialization<T: EthSpec>(
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let slot = relative_epoch
|
||||
.into_epoch(state.slot.epoch(T::slots_per_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();
|
||||
|
||||
@@ -99,7 +128,7 @@ fn test_cache_initialization<T: EthSpec>(
|
||||
state.get_beacon_committee(slot, 0).unwrap();
|
||||
|
||||
// Drop the cache.
|
||||
state.drop_committee_cache(relative_epoch);
|
||||
state.drop_committee_cache(relative_epoch).unwrap();
|
||||
|
||||
// Assert a call to a cache-using function fail.
|
||||
assert_eq!(
|
||||
@@ -114,11 +143,9 @@ fn test_cache_initialization<T: EthSpec>(
|
||||
fn cache_initialization() {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(16, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
let mut state = build_state::<MinimalEthSpec>(16);
|
||||
|
||||
state.slot =
|
||||
*state.slot_mut() =
|
||||
(MinimalEthSpec::genesis_epoch() + 1).start_slot(MinimalEthSpec::slots_per_epoch());
|
||||
|
||||
test_cache_initialization(&mut state, RelativeEpoch::Previous, &spec);
|
||||
@@ -150,25 +177,29 @@ fn test_clone_config<E: EthSpec>(base_state: &BeaconState<E>, clone_config: Clon
|
||||
.expect_err("shouldn't exist");
|
||||
}
|
||||
if clone_config.pubkey_cache {
|
||||
assert_ne!(state.pubkey_cache.len(), 0);
|
||||
assert_ne!(state.pubkey_cache().len(), 0);
|
||||
} else {
|
||||
assert_eq!(state.pubkey_cache.len(), 0);
|
||||
assert_eq!(state.pubkey_cache().len(), 0);
|
||||
}
|
||||
if clone_config.exit_cache {
|
||||
state
|
||||
.exit_cache
|
||||
.exit_cache()
|
||||
.check_initialized()
|
||||
.expect("exit cache exists");
|
||||
} else {
|
||||
state
|
||||
.exit_cache
|
||||
.exit_cache()
|
||||
.check_initialized()
|
||||
.expect_err("exit cache doesn't exist");
|
||||
}
|
||||
if clone_config.tree_hash_cache {
|
||||
assert!(state.tree_hash_cache.is_some());
|
||||
assert!(state.tree_hash_cache().is_initialized());
|
||||
} else {
|
||||
assert!(state.tree_hash_cache.is_none(), "{:?}", clone_config);
|
||||
assert!(
|
||||
!state.tree_hash_cache().is_initialized(),
|
||||
"{:?}",
|
||||
clone_config
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +207,7 @@ fn test_clone_config<E: EthSpec>(base_state: &BeaconState<E>, clone_config: Clon
|
||||
fn clone_config() {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(16, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
let mut state = build_state::<MinimalEthSpec>(16);
|
||||
|
||||
state.build_all_caches(&spec).unwrap();
|
||||
state
|
||||
@@ -198,69 +227,10 @@ fn clone_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()[..]);
|
||||
|
||||
/*
|
||||
* A cache should hash twice without updating the slot.
|
||||
*/
|
||||
|
||||
assert_eq!(
|
||||
state.update_tree_hash_cache().unwrap(),
|
||||
root,
|
||||
"tree hash result should be identical on the same slot"
|
||||
);
|
||||
|
||||
/*
|
||||
* A cache should not hash after updating the slot but not updating the state roots.
|
||||
*/
|
||||
|
||||
// The tree hash cache needs to be rebuilt since it was dropped when it failed.
|
||||
state
|
||||
.update_tree_hash_cache()
|
||||
.expect("should rebuild cache");
|
||||
|
||||
state.slot += 1;
|
||||
|
||||
assert_eq!(
|
||||
state.update_tree_hash_cache(),
|
||||
Err(BeaconStateError::NonLinearTreeHashCacheHistory),
|
||||
"should not build hash without updating the state root"
|
||||
);
|
||||
|
||||
/*
|
||||
* The cache should update if the slot and state root are updated.
|
||||
*/
|
||||
|
||||
// The tree hash cache needs to be rebuilt since it was dropped when it failed.
|
||||
let root = state
|
||||
.update_tree_hash_cache()
|
||||
.expect("should rebuild cache");
|
||||
|
||||
state.slot += 1;
|
||||
state
|
||||
.set_state_root(state.slot - 1, root)
|
||||
.expect("should set state root");
|
||||
|
||||
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 std::ops::{Add, Div};
|
||||
use swap_or_not_shuffle::shuffle_list;
|
||||
|
||||
@@ -343,35 +313,33 @@ mod committees {
|
||||
) {
|
||||
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 harness = get_harness::<T>(validator_count, slot);
|
||||
let mut new_head_state = harness.get_current_state();
|
||||
|
||||
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);
|
||||
*new_head_state.randao_mixes_mut() = FixedVector::from(distinct_hashes);
|
||||
|
||||
state
|
||||
.build_committee_cache(RelativeEpoch::Previous, spec)
|
||||
new_head_state
|
||||
.force_build_committee_cache(RelativeEpoch::Previous, spec)
|
||||
.unwrap();
|
||||
state
|
||||
.build_committee_cache(RelativeEpoch::Current, spec)
|
||||
new_head_state
|
||||
.force_build_committee_cache(RelativeEpoch::Current, spec)
|
||||
.unwrap();
|
||||
state
|
||||
.build_committee_cache(RelativeEpoch::Next, spec)
|
||||
new_head_state
|
||||
.force_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);
|
||||
execute_committee_consistency_test(
|
||||
new_head_state,
|
||||
cache_epoch,
|
||||
validator_count as usize,
|
||||
&spec,
|
||||
);
|
||||
}
|
||||
|
||||
fn committee_consistency_test_suite<T: EthSpec>(cached_epoch: RelativeEpoch) {
|
||||
@@ -419,16 +387,12 @@ mod committees {
|
||||
|
||||
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_deterministic_keypairs(16, &spec);
|
||||
let (state, _keypairs) = builder.build();
|
||||
|
||||
state
|
||||
get_harness(16, Slot::new(0))
|
||||
.chain
|
||||
.head_beacon_state()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -436,8 +400,8 @@ mod get_outstanding_deposit_len {
|
||||
let mut state = state();
|
||||
assert_eq!(state.get_outstanding_deposit_len(), Ok(0));
|
||||
|
||||
state.eth1_data.deposit_count = 17;
|
||||
state.eth1_deposit_index = 16;
|
||||
state.eth1_data_mut().deposit_count = 17;
|
||||
*state.eth1_deposit_index_mut() = 16;
|
||||
assert_eq!(state.get_outstanding_deposit_len(), Ok(1));
|
||||
}
|
||||
|
||||
@@ -445,8 +409,8 @@ mod get_outstanding_deposit_len {
|
||||
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;
|
||||
state.eth1_data_mut().deposit_count = 16;
|
||||
*state.eth1_deposit_index_mut() = 17;
|
||||
|
||||
assert_eq!(
|
||||
state.get_outstanding_deposit_len(),
|
||||
@@ -457,3 +421,124 @@ mod get_outstanding_deposit_len {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_base_and_altair() {
|
||||
type E = MainnetEthSpec;
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let fork_epoch = Epoch::from_ssz_bytes(&[7, 6, 5, 4, 3, 2, 1, 0]).unwrap();
|
||||
|
||||
let base_epoch = fork_epoch.saturating_sub(1_u64);
|
||||
let base_slot = base_epoch.end_slot(E::slots_per_epoch());
|
||||
let altair_epoch = fork_epoch;
|
||||
let altair_slot = altair_epoch.start_slot(E::slots_per_epoch());
|
||||
|
||||
let mut spec = E::default_spec();
|
||||
spec.altair_fork_epoch = Some(altair_epoch);
|
||||
|
||||
// BeaconStateBase
|
||||
{
|
||||
let good_base_block: BeaconState<MainnetEthSpec> = BeaconState::Base(BeaconStateBase {
|
||||
slot: base_slot,
|
||||
..<_>::random_for_test(rng)
|
||||
});
|
||||
// It's invalid to have a base block with a slot higher than the fork slot.
|
||||
let bad_base_block = {
|
||||
let mut bad = good_base_block.clone();
|
||||
*bad.slot_mut() = altair_slot;
|
||||
bad
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
BeaconState::from_ssz_bytes(&good_base_block.as_ssz_bytes(), &spec)
|
||||
.expect("good base block can be decoded"),
|
||||
good_base_block
|
||||
);
|
||||
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_base_block.as_ssz_bytes(), &spec)
|
||||
.expect_err("bad base block cannot be decoded");
|
||||
}
|
||||
|
||||
// BeaconStateAltair
|
||||
{
|
||||
let good_altair_block: BeaconState<MainnetEthSpec> =
|
||||
BeaconState::Altair(BeaconStateAltair {
|
||||
slot: altair_slot,
|
||||
..<_>::random_for_test(rng)
|
||||
});
|
||||
// It's invalid to have an Altair block with a slot lower than the fork slot.
|
||||
let bad_altair_block = {
|
||||
let mut bad = good_altair_block.clone();
|
||||
*bad.slot_mut() = base_slot;
|
||||
bad
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
BeaconState::from_ssz_bytes(&good_altair_block.as_ssz_bytes(), &spec)
|
||||
.expect("good altair block can be decoded"),
|
||||
good_altair_block
|
||||
);
|
||||
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_altair_block.as_ssz_bytes(), &spec)
|
||||
.expect_err("bad altair block cannot be decoded");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_hash_cache_linear_history() {
|
||||
use crate::test_utils::{SeedableRng, XorShiftRng};
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let mut state: BeaconState<MainnetEthSpec> =
|
||||
BeaconState::Base(BeaconStateBase::random_for_test(&mut rng));
|
||||
|
||||
let root = state.update_tree_hash_cache().unwrap();
|
||||
|
||||
assert_eq!(root.as_bytes(), &state.tree_hash_root()[..]);
|
||||
|
||||
/*
|
||||
* A cache should hash twice without updating the slot.
|
||||
*/
|
||||
|
||||
assert_eq!(
|
||||
state.update_tree_hash_cache().unwrap(),
|
||||
root,
|
||||
"tree hash result should be identical on the same slot"
|
||||
);
|
||||
|
||||
/*
|
||||
* A cache should not hash after updating the slot but not updating the state roots.
|
||||
*/
|
||||
|
||||
// The tree hash cache needs to be rebuilt since it was dropped when it failed.
|
||||
state
|
||||
.update_tree_hash_cache()
|
||||
.expect("should rebuild cache");
|
||||
|
||||
*state.slot_mut() += 1;
|
||||
|
||||
assert_eq!(
|
||||
state.update_tree_hash_cache(),
|
||||
Err(BeaconStateError::NonLinearTreeHashCacheHistory),
|
||||
"should not build hash without updating the state root"
|
||||
);
|
||||
|
||||
/*
|
||||
* The cache should update if the slot and state root are updated.
|
||||
*/
|
||||
|
||||
// The tree hash cache needs to be rebuilt since it was dropped when it failed.
|
||||
let root = state
|
||||
.update_tree_hash_cache()
|
||||
.expect("should rebuild cache");
|
||||
|
||||
*state.slot_mut() += 1;
|
||||
state
|
||||
.set_state_root(state.slot() - 1, root)
|
||||
.expect("should set state root");
|
||||
|
||||
let root = state.update_tree_hash_cache().unwrap();
|
||||
assert_eq!(root.as_bytes(), &state.tree_hash_root()[..]);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#![allow(clippy::disallowed_method)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::Error;
|
||||
use crate::{BeaconState, EthSpec, Hash256, Slot, Unsigned, Validator};
|
||||
@@ -11,8 +12,13 @@ use std::cmp::Ordering;
|
||||
use std::iter::ExactSizeIterator;
|
||||
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 leaves (including padding) on the `BeaconState` Merkle tree.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// This constant is set with the assumption that there are `> 16` and `<= 32` fields on the
|
||||
/// `BeaconState`. **Tree hashing will fail if this value is set incorrectly.**
|
||||
const NUM_BEACON_STATE_HASH_TREE_ROOT_LEAVES: usize = 32;
|
||||
|
||||
/// The number of nodes in the Merkle tree of a validator record.
|
||||
const NODES_PER_VALIDATOR: usize = 15;
|
||||
@@ -41,7 +47,7 @@ impl<T: EthSpec> Eth1DataVotesTreeHashCache<T> {
|
||||
pub fn new(state: &BeaconState<T>) -> Self {
|
||||
let mut arena = CacheArena::default();
|
||||
let roots: VariableList<_, _> = state
|
||||
.eth1_data_votes
|
||||
.eth1_data_votes()
|
||||
.iter()
|
||||
.map(|eth1_data| eth1_data.tree_hash_root())
|
||||
.collect::<Vec<_>>()
|
||||
@@ -51,7 +57,7 @@ impl<T: EthSpec> Eth1DataVotesTreeHashCache<T> {
|
||||
Self {
|
||||
arena,
|
||||
tree_hash_cache,
|
||||
voting_period: Self::voting_period(state.slot),
|
||||
voting_period: Self::voting_period(state.slot()),
|
||||
roots,
|
||||
}
|
||||
}
|
||||
@@ -61,14 +67,14 @@ impl<T: EthSpec> Eth1DataVotesTreeHashCache<T> {
|
||||
}
|
||||
|
||||
pub fn recalculate_tree_hash_root(&mut self, state: &BeaconState<T>) -> Result<Hash256, Error> {
|
||||
if state.eth1_data_votes.len() < self.roots.len()
|
||||
|| Self::voting_period(state.slot) != self.voting_period
|
||||
if state.eth1_data_votes().len() < self.roots.len()
|
||||
|| Self::voting_period(state.slot()) != self.voting_period
|
||||
{
|
||||
*self = Self::new(state);
|
||||
}
|
||||
|
||||
state
|
||||
.eth1_data_votes
|
||||
.eth1_data_votes()
|
||||
.iter()
|
||||
.skip(self.roots.len())
|
||||
.try_for_each(|eth1_data| self.roots.push(eth1_data.tree_hash_root()))?;
|
||||
@@ -80,8 +86,42 @@ impl<T: EthSpec> Eth1DataVotesTreeHashCache<T> {
|
||||
}
|
||||
|
||||
/// A cache that performs a caching tree hash of the entire `BeaconState` struct.
|
||||
#[derive(Debug, PartialEq, Clone, Encode, Decode)]
|
||||
///
|
||||
/// This type is a wrapper around the inner cache, which does all the work.
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
pub struct BeaconTreeHashCache<T: EthSpec> {
|
||||
inner: Option<BeaconTreeHashCacheInner<T>>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> BeaconTreeHashCache<T> {
|
||||
pub fn new(state: &BeaconState<T>) -> Self {
|
||||
Self {
|
||||
inner: Some(BeaconTreeHashCacheInner::new(state)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.inner.is_some()
|
||||
}
|
||||
|
||||
/// Move the inner cache out so that the containing `BeaconState` can be borrowed.
|
||||
pub fn take(&mut self) -> Option<BeaconTreeHashCacheInner<T>> {
|
||||
self.inner.take()
|
||||
}
|
||||
|
||||
/// Restore the inner cache after using `take`.
|
||||
pub fn restore(&mut self, inner: BeaconTreeHashCacheInner<T>) {
|
||||
self.inner = Some(inner);
|
||||
}
|
||||
|
||||
/// Make the cache empty.
|
||||
pub fn uninitialize(&mut self) {
|
||||
self.inner = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct BeaconTreeHashCacheInner<T: EthSpec> {
|
||||
/// Tracks the previously generated state root to ensure the next state root provided descends
|
||||
/// directly from this state.
|
||||
previous_state: Option<(Hash256, Slot)>,
|
||||
@@ -101,25 +141,27 @@ pub struct BeaconTreeHashCache<T: EthSpec> {
|
||||
eth1_data_votes: Eth1DataVotesTreeHashCache<T>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> BeaconTreeHashCache<T> {
|
||||
impl<T: EthSpec> BeaconTreeHashCacheInner<T> {
|
||||
/// Instantiates a new cache.
|
||||
///
|
||||
/// Allocates the necessary memory to store all of the cached Merkle trees. Only the leaves are
|
||||
/// hashed, leaving the internal nodes as all-zeros.
|
||||
pub fn new(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 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 validators = ValidatorsListTreeHashCache::new::<T>(state.validators());
|
||||
|
||||
let mut balances_arena = CacheArena::default();
|
||||
let balances = state.balances.new_tree_hash_cache(&mut balances_arena);
|
||||
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);
|
||||
let slashings = state.slashings().new_tree_hash_cache(&mut slashings_arena);
|
||||
|
||||
Self {
|
||||
previous_state: None,
|
||||
@@ -150,100 +192,132 @@ impl<T: EthSpec> BeaconTreeHashCache<T> {
|
||||
// efficient algorithm.
|
||||
if let Some((previous_root, previous_slot)) = self.previous_state {
|
||||
// The previously-hashed state must not be newer than `state`.
|
||||
if previous_slot > state.slot {
|
||||
if previous_slot > state.slot() {
|
||||
return Err(Error::TreeHashCacheSkippedSlot {
|
||||
cache: previous_slot,
|
||||
state: state.slot,
|
||||
state: state.slot(),
|
||||
});
|
||||
}
|
||||
|
||||
// If the state is newer, the previous root must be in the history of the given state.
|
||||
if previous_slot < state.slot && *state.get_state_root(previous_slot)? != previous_root
|
||||
if previous_slot < state.slot()
|
||||
&& *state.get_state_root(previous_slot)? != previous_root
|
||||
{
|
||||
return Err(Error::NonLinearTreeHashCacheHistory);
|
||||
}
|
||||
}
|
||||
|
||||
let mut hasher = MerkleHasher::with_leaves(NUM_BEACON_STATE_HASHING_FIELDS);
|
||||
let mut hasher = MerkleHasher::with_leaves(NUM_BEACON_STATE_HASH_TREE_ROOT_LEAVES);
|
||||
|
||||
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.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
|
||||
.block_roots()
|
||||
.recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.block_roots)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.state_roots
|
||||
.state_roots()
|
||||
.recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.state_roots)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.historical_roots
|
||||
.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().tree_hash_root().as_bytes())?;
|
||||
hasher.write(
|
||||
self.eth1_data_votes
|
||||
.recalculate_tree_hash_root(&state)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(state.eth1_deposit_index.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[..])?
|
||||
.recalculate_tree_hash_root(state.validators())?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.balances
|
||||
.balances()
|
||||
.recalculate_tree_hash_root(&mut self.balances_arena, &mut self.balances)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.randao_mixes
|
||||
.randao_mixes()
|
||||
.recalculate_tree_hash_root(&mut self.fixed_arena, &mut self.randao_mixes)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.slashings
|
||||
.slashings()
|
||||
.recalculate_tree_hash_root(&mut self.slashings_arena, &mut self.slashings)?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
|
||||
// Participation
|
||||
match state {
|
||||
BeaconState::Base(state) => {
|
||||
hasher.write(
|
||||
state
|
||||
.previous_epoch_attestations
|
||||
.tree_hash_root()
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(state.current_epoch_attestations.tree_hash_root().as_bytes())?;
|
||||
}
|
||||
// FIXME(altair): add a cache to accelerate hashing of these fields
|
||||
BeaconState::Altair(state) => {
|
||||
hasher.write(
|
||||
state
|
||||
.previous_epoch_participation
|
||||
.tree_hash_root()
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.current_epoch_participation
|
||||
.tree_hash_root()
|
||||
.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
hasher.write(state.justification_bits().tree_hash_root().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
|
||||
.previous_justified_checkpoint()
|
||||
.tree_hash_root()
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(
|
||||
state
|
||||
.current_justified_checkpoint
|
||||
.current_justified_checkpoint()
|
||||
.tree_hash_root()
|
||||
.as_bytes(),
|
||||
)?;
|
||||
hasher.write(state.finalized_checkpoint.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.finalized_checkpoint().tree_hash_root().as_bytes())?;
|
||||
|
||||
// Inactivity & light-client sync committees
|
||||
if let BeaconState::Altair(ref state) = state {
|
||||
// FIXME(altair): add cache for this field
|
||||
hasher.write(state.inactivity_scores.tree_hash_root().as_bytes())?;
|
||||
|
||||
hasher.write(state.current_sync_committee.tree_hash_root().as_bytes())?;
|
||||
hasher.write(state.next_sync_committee.tree_hash_root().as_bytes())?;
|
||||
}
|
||||
|
||||
let root = hasher.finish()?;
|
||||
|
||||
self.previous_state = Some((root, state.slot));
|
||||
self.previous_state = Some((root, state.slot()));
|
||||
|
||||
Ok(root)
|
||||
}
|
||||
@@ -432,6 +506,13 @@ impl ParallelValidatorTreeHash {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary-fuzz")]
|
||||
impl<T: EthSpec> arbitrary::Arbitrary for BeaconTreeHashCache<T> {
|
||||
fn arbitrary(_u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use crate::*;
|
||||
use int_to_bytes::int_to_bytes4;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use serde_utils::quoted_u64::MaybeQuoted;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
/// Each of the BLS signature domains.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum Domain {
|
||||
BeaconProposer,
|
||||
@@ -18,11 +16,12 @@ pub enum Domain {
|
||||
VoluntaryExit,
|
||||
SelectionProof,
|
||||
AggregateAndProof,
|
||||
SyncCommittee,
|
||||
}
|
||||
|
||||
/// Holds all the "constants" for a BeaconChain.
|
||||
/// Lighthouse's internal configuration struct.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
/// Contains a mixture of "preset" and "config" values w.r.t to the EF definitions.
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct ChainSpec {
|
||||
@@ -87,13 +86,13 @@ pub struct ChainSpec {
|
||||
/*
|
||||
* 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,
|
||||
pub(crate) domain_beacon_proposer: u32,
|
||||
pub(crate) domain_beacon_attester: u32,
|
||||
pub(crate) domain_randao: u32,
|
||||
pub(crate) domain_deposit: u32,
|
||||
pub(crate) domain_voluntary_exit: u32,
|
||||
pub(crate) domain_selection_proof: u32,
|
||||
pub(crate) domain_aggregate_and_proof: u32,
|
||||
|
||||
/*
|
||||
* Fork choice
|
||||
@@ -109,6 +108,23 @@ pub struct ChainSpec {
|
||||
pub deposit_network_id: u64,
|
||||
pub deposit_contract_address: Address,
|
||||
|
||||
/*
|
||||
* Altair hard fork params
|
||||
*/
|
||||
pub inactivity_penalty_quotient_altair: u64,
|
||||
pub min_slashing_penalty_quotient_altair: u64,
|
||||
pub proportional_slashing_multiplier_altair: u64,
|
||||
pub epochs_per_sync_committee_period: Epoch,
|
||||
pub inactivity_score_bias: u64,
|
||||
pub inactivity_score_recovery_rate: u64,
|
||||
pub min_sync_committee_participants: u64,
|
||||
pub(crate) domain_sync_committee: u32,
|
||||
pub(crate) domain_sync_committee_selection_proof: u32,
|
||||
pub(crate) domain_contribution_and_proof: u32,
|
||||
pub altair_fork_version: [u8; 4],
|
||||
/// The Altair fork epoch is optional, with `None` representing "Altair never happens".
|
||||
pub altair_fork_epoch: Option<Epoch>,
|
||||
|
||||
/*
|
||||
* Networking
|
||||
*/
|
||||
@@ -123,6 +139,12 @@ pub struct ChainSpec {
|
||||
}
|
||||
|
||||
impl ChainSpec {
|
||||
/// Construct a `ChainSpec` from a standard config.
|
||||
pub fn from_config<T: EthSpec>(config: &Config) -> Option<Self> {
|
||||
let spec = T::default_spec();
|
||||
config.apply_to_chain_spec::<T>(&spec)
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -146,6 +168,19 @@ impl ChainSpec {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the name of the fork which is active at `slot`.
|
||||
pub fn fork_name_at_slot<E: EthSpec>(&self, slot: Slot) -> ForkName {
|
||||
self.fork_name_at_epoch(slot.epoch(E::slots_per_epoch()))
|
||||
}
|
||||
|
||||
/// Returns the name of the fork which is active at `epoch`.
|
||||
pub fn fork_name_at_epoch(&self, epoch: Epoch) -> ForkName {
|
||||
match self.altair_fork_epoch {
|
||||
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Altair,
|
||||
_ => ForkName::Base,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the domain number, unmodified by the fork.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
@@ -158,6 +193,7 @@ impl ChainSpec {
|
||||
Domain::VoluntaryExit => self.domain_voluntary_exit,
|
||||
Domain::SelectionProof => self.domain_selection_proof,
|
||||
Domain::AggregateAndProof => self.domain_aggregate_and_proof,
|
||||
Domain::SyncCommittee => self.domain_sync_committee,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,13 +247,15 @@ impl ChainSpec {
|
||||
) -> [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.copy_from_slice(
|
||||
root.as_bytes()
|
||||
.get(0..4)
|
||||
.expect("root hash is at least 4 bytes"),
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
/// Compute a domain by applying the given `fork_version`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn compute_domain(
|
||||
&self,
|
||||
domain: Domain,
|
||||
@@ -229,7 +267,10 @@ impl ChainSpec {
|
||||
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],
|
||||
Self::compute_fork_data_root(fork_version, genesis_validators_root)
|
||||
.as_bytes()
|
||||
.get(..28)
|
||||
.expect("fork has is 32 bytes so first 28 bytes should exist"),
|
||||
);
|
||||
|
||||
Hash256::from(domain)
|
||||
@@ -265,10 +306,22 @@ impl ChainSpec {
|
||||
/*
|
||||
* 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)),
|
||||
min_deposit_amount: option_wrapper(|| {
|
||||
u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?)
|
||||
})
|
||||
.expect("calculation does not overflow"),
|
||||
max_effective_balance: option_wrapper(|| {
|
||||
u64::checked_pow(2, 5)?.checked_mul(u64::checked_pow(10, 9)?)
|
||||
})
|
||||
.expect("calculation does not overflow"),
|
||||
ejection_balance: option_wrapper(|| {
|
||||
u64::checked_pow(2, 4)?.checked_mul(u64::checked_pow(10, 9)?)
|
||||
})
|
||||
.expect("calculation does not overflow"),
|
||||
effective_balance_increment: option_wrapper(|| {
|
||||
u64::checked_pow(2, 0)?.checked_mul(u64::checked_pow(10, 9)?)
|
||||
})
|
||||
.expect("calculation does not overflow"),
|
||||
|
||||
/*
|
||||
* Initial Values
|
||||
@@ -294,7 +347,7 @@ impl ChainSpec {
|
||||
base_reward_factor: 64,
|
||||
whistleblower_reward_quotient: 512,
|
||||
proposer_reward_quotient: 8,
|
||||
inactivity_penalty_quotient: u64::pow(2, 26),
|
||||
inactivity_penalty_quotient: u64::checked_pow(2, 26).expect("pow does not overflow"),
|
||||
min_slashing_penalty_quotient: 128,
|
||||
proportional_slashing_multiplier: 1,
|
||||
|
||||
@@ -325,6 +378,26 @@ impl ChainSpec {
|
||||
.parse()
|
||||
.expect("chain spec deposit contract address"),
|
||||
|
||||
/*
|
||||
* Altair hard fork params
|
||||
*/
|
||||
inactivity_penalty_quotient_altair: option_wrapper(|| {
|
||||
u64::checked_pow(2, 24)?.checked_mul(3)
|
||||
})
|
||||
.expect("calculation does not overflow"),
|
||||
min_slashing_penalty_quotient_altair: u64::checked_pow(2, 6)
|
||||
.expect("pow does not overflow"),
|
||||
proportional_slashing_multiplier_altair: 2,
|
||||
inactivity_score_bias: 4,
|
||||
inactivity_score_recovery_rate: 16,
|
||||
min_sync_committee_participants: 1,
|
||||
epochs_per_sync_committee_period: Epoch::new(256),
|
||||
domain_sync_committee: 7,
|
||||
domain_sync_committee_selection_proof: 8,
|
||||
domain_contribution_and_proof: 9,
|
||||
altair_fork_version: [0x01, 0x00, 0x00, 0x00],
|
||||
altair_fork_epoch: Some(Epoch::new(u64::MAX)),
|
||||
|
||||
/*
|
||||
* Network specific
|
||||
*/
|
||||
@@ -340,8 +413,6 @@ impl ChainSpec {
|
||||
}
|
||||
|
||||
/// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn minimal() -> Self {
|
||||
// Note: bootnodes to be updated when static nodes exist.
|
||||
let boot_nodes = vec![];
|
||||
@@ -357,10 +428,15 @@ impl ChainSpec {
|
||||
shard_committee_period: 64,
|
||||
genesis_delay: 300,
|
||||
seconds_per_slot: 6,
|
||||
inactivity_penalty_quotient: u64::pow(2, 25),
|
||||
inactivity_penalty_quotient: u64::checked_pow(2, 25).expect("pow does not overflow"),
|
||||
min_slashing_penalty_quotient: 64,
|
||||
proportional_slashing_multiplier: 2,
|
||||
safe_slots_to_update_justified: 2,
|
||||
// Altair
|
||||
epochs_per_sync_committee_period: Epoch::new(8),
|
||||
altair_fork_version: [0x01, 0x00, 0x00, 0x01],
|
||||
altair_fork_epoch: Some(Epoch::new(u64::MAX)),
|
||||
// Other
|
||||
network_id: 2, // lighthouse testnet network id
|
||||
deposit_chain_id: 5,
|
||||
deposit_network_id: 5,
|
||||
@@ -371,24 +447,6 @@ impl ChainSpec {
|
||||
..ChainSpec::mainnet()
|
||||
}
|
||||
}
|
||||
|
||||
/// Suits the `v0.12.3` version of the eth2 spec:
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/configs/mainnet/phase0.yaml
|
||||
///
|
||||
/// This method only needs to exist whilst we provide support for "legacy" testnets prior to v1.0.0
|
||||
/// (e.g., Medalla, Pyrmont, Spadina, Altona, etc.).
|
||||
pub fn v012_legacy() -> Self {
|
||||
let boot_nodes = vec![];
|
||||
|
||||
Self {
|
||||
genesis_delay: 172_800, // 2 days
|
||||
inactivity_penalty_quotient: u64::pow(2, 24),
|
||||
min_slashing_penalty_quotient: 32,
|
||||
eth1_follow_distance: 1024,
|
||||
boot_nodes,
|
||||
..ChainSpec::mainnet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChainSpec {
|
||||
@@ -397,6 +455,175 @@ impl Default for ChainSpec {
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact implementation of the *config* object from the Ethereum spec (YAML/JSON).
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
pub preset_base: String,
|
||||
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_genesis_active_validator_count: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_genesis_time: u64,
|
||||
#[serde(with = "serde_utils::bytes_4_hex")]
|
||||
genesis_fork_version: [u8; 4],
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
genesis_delay: u64,
|
||||
|
||||
#[serde(with = "serde_utils::bytes_4_hex")]
|
||||
altair_fork_version: [u8; 4],
|
||||
altair_fork_epoch: Option<MaybeQuoted<Epoch>>,
|
||||
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
seconds_per_slot: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
seconds_per_eth1_block: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_validator_withdrawability_delay: Epoch,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
shard_committee_period: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
eth1_follow_distance: u64,
|
||||
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
inactivity_score_bias: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
inactivity_score_recovery_rate: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
ejection_balance: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_per_epoch_churn_limit: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
churn_limit_quotient: u64,
|
||||
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
deposit_chain_id: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
deposit_network_id: u64,
|
||||
deposit_contract_address: Address,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let chain_spec = MainnetEthSpec::default_spec();
|
||||
Config::from_chain_spec::<MainnetEthSpec>(&chain_spec)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Maps `self` to an identifier for an `EthSpec` instance.
|
||||
///
|
||||
/// Returns `None` if there is no match.
|
||||
pub fn eth_spec_id(&self) -> Option<EthSpecId> {
|
||||
match self.preset_base.as_str() {
|
||||
"minimal" => Some(EthSpecId::Minimal),
|
||||
"mainnet" => Some(EthSpecId::Mainnet),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_chain_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
preset_base: T::spec_name().to_string(),
|
||||
|
||||
min_genesis_active_validator_count: spec.min_genesis_active_validator_count,
|
||||
min_genesis_time: spec.min_genesis_time,
|
||||
genesis_fork_version: spec.genesis_fork_version,
|
||||
genesis_delay: spec.genesis_delay,
|
||||
|
||||
altair_fork_version: spec.altair_fork_version,
|
||||
altair_fork_epoch: spec
|
||||
.altair_fork_epoch
|
||||
.map(|slot| MaybeQuoted { value: slot }),
|
||||
|
||||
seconds_per_slot: spec.seconds_per_slot,
|
||||
seconds_per_eth1_block: spec.seconds_per_eth1_block,
|
||||
min_validator_withdrawability_delay: spec.min_validator_withdrawability_delay,
|
||||
shard_committee_period: spec.shard_committee_period,
|
||||
eth1_follow_distance: spec.eth1_follow_distance,
|
||||
|
||||
inactivity_score_bias: spec.inactivity_score_bias,
|
||||
inactivity_score_recovery_rate: spec.inactivity_score_recovery_rate,
|
||||
ejection_balance: spec.ejection_balance,
|
||||
churn_limit_quotient: spec.churn_limit_quotient,
|
||||
min_per_epoch_churn_limit: spec.min_per_epoch_churn_limit,
|
||||
|
||||
deposit_chain_id: spec.deposit_chain_id,
|
||||
deposit_network_id: spec.deposit_network_id,
|
||||
deposit_contract_address: spec.deposit_contract_address,
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
// Pattern match here to avoid missing any fields.
|
||||
let &Config {
|
||||
ref preset_base,
|
||||
min_genesis_active_validator_count,
|
||||
min_genesis_time,
|
||||
genesis_fork_version,
|
||||
genesis_delay,
|
||||
altair_fork_version,
|
||||
altair_fork_epoch,
|
||||
seconds_per_slot,
|
||||
seconds_per_eth1_block,
|
||||
min_validator_withdrawability_delay,
|
||||
shard_committee_period,
|
||||
eth1_follow_distance,
|
||||
inactivity_score_bias,
|
||||
inactivity_score_recovery_rate,
|
||||
ejection_balance,
|
||||
min_per_epoch_churn_limit,
|
||||
churn_limit_quotient,
|
||||
deposit_chain_id,
|
||||
deposit_network_id,
|
||||
deposit_contract_address,
|
||||
} = self;
|
||||
|
||||
if preset_base != T::spec_name().to_string().as_str() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ChainSpec {
|
||||
min_genesis_active_validator_count,
|
||||
min_genesis_time,
|
||||
genesis_fork_version,
|
||||
genesis_delay,
|
||||
altair_fork_version,
|
||||
altair_fork_epoch: altair_fork_epoch.map(|q| q.value),
|
||||
seconds_per_slot,
|
||||
seconds_per_eth1_block,
|
||||
min_validator_withdrawability_delay,
|
||||
shard_committee_period,
|
||||
eth1_follow_distance,
|
||||
inactivity_score_bias,
|
||||
inactivity_score_recovery_rate,
|
||||
ejection_balance,
|
||||
min_per_epoch_churn_limit,
|
||||
churn_limit_quotient,
|
||||
deposit_chain_id,
|
||||
deposit_network_id,
|
||||
deposit_contract_address,
|
||||
..chain_spec.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple wrapper to permit the in-line use of `?`.
|
||||
fn option_wrapper<F, T>(f: F) -> Option<T>
|
||||
where
|
||||
F: Fn() -> Option<T>,
|
||||
{
|
||||
f()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -446,359 +673,7 @@ mod tests {
|
||||
spec.domain_aggregate_and_proof,
|
||||
&spec,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// YAML config file as defined by the spec.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct YamlConfig {
|
||||
pub config_name: String,
|
||||
// ChainSpec
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
max_committees_per_slot: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
target_committee_size: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_per_epoch_churn_limit: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
churn_limit_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u8")]
|
||||
shuffle_round_count: u8,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_genesis_active_validator_count: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_genesis_time: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
genesis_delay: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_deposit_amount: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
max_effective_balance: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
ejection_balance: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
effective_balance_increment: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
hysteresis_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
hysteresis_downward_multiplier: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
hysteresis_upward_multiplier: u64,
|
||||
#[serde(with = "serde_utils::bytes_4_hex")]
|
||||
genesis_fork_version: [u8; 4],
|
||||
#[serde(with = "serde_utils::u8_hex")]
|
||||
bls_withdrawal_prefix: u8,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
seconds_per_slot: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_attestation_inclusion_delay: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_seed_lookahead: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
max_seed_lookahead: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_epochs_to_inactivity_penalty: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_validator_withdrawability_delay: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
shard_committee_period: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
base_reward_factor: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
whistleblower_reward_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
proposer_reward_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
inactivity_penalty_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
min_slashing_penalty_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
proportional_slashing_multiplier: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
safe_slots_to_update_justified: u64,
|
||||
|
||||
#[serde(with = "serde_utils::u32_hex")]
|
||||
domain_beacon_proposer: u32,
|
||||
#[serde(with = "serde_utils::u32_hex")]
|
||||
domain_beacon_attester: u32,
|
||||
#[serde(with = "serde_utils::u32_hex")]
|
||||
domain_randao: u32,
|
||||
#[serde(with = "serde_utils::u32_hex")]
|
||||
domain_deposit: u32,
|
||||
#[serde(with = "serde_utils::u32_hex")]
|
||||
domain_voluntary_exit: u32,
|
||||
#[serde(with = "serde_utils::u32_hex")]
|
||||
domain_selection_proof: u32,
|
||||
#[serde(with = "serde_utils::u32_hex")]
|
||||
domain_aggregate_and_proof: u32,
|
||||
// EthSpec
|
||||
#[serde(with = "serde_utils::quoted_u32")]
|
||||
max_validators_per_committee: u32,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
slots_per_epoch: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
epochs_per_eth1_voting_period: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
slots_per_historical_root: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
epochs_per_historical_vector: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
epochs_per_slashings_vector: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
historical_roots_limit: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
validator_registry_limit: u64,
|
||||
#[serde(with = "serde_utils::quoted_u32")]
|
||||
max_proposer_slashings: u32,
|
||||
#[serde(with = "serde_utils::quoted_u32")]
|
||||
max_attester_slashings: u32,
|
||||
#[serde(with = "serde_utils::quoted_u32")]
|
||||
max_attestations: u32,
|
||||
#[serde(with = "serde_utils::quoted_u32")]
|
||||
max_deposits: u32,
|
||||
#[serde(with = "serde_utils::quoted_u32")]
|
||||
max_voluntary_exits: u32,
|
||||
// Validator
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
eth1_follow_distance: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
target_aggregators_per_committee: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
random_subnets_per_validator: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
epochs_per_random_subnet_subscription: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
seconds_per_eth1_block: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
deposit_chain_id: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
deposit_network_id: u64,
|
||||
deposit_contract_address: Address,
|
||||
|
||||
// Extra fields (could be from a future hard-fork that we don't yet know).
|
||||
#[serde(flatten)]
|
||||
pub extra_fields: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Default for YamlConfig {
|
||||
fn default() -> Self {
|
||||
let chain_spec = MainnetEthSpec::default_spec();
|
||||
YamlConfig::from_spec::<MainnetEthSpec>(&chain_spec)
|
||||
}
|
||||
}
|
||||
|
||||
/// Spec v0.12.1
|
||||
impl YamlConfig {
|
||||
/// Maps `self.config_name` to an identifier for an `EthSpec` instance.
|
||||
///
|
||||
/// Returns `None` if there is no match.
|
||||
pub fn eth_spec_id(&self) -> Option<EthSpecId> {
|
||||
Some(match self.config_name.as_str() {
|
||||
"mainnet" => EthSpecId::Mainnet,
|
||||
"minimal" => EthSpecId::Minimal,
|
||||
"toledo" => EthSpecId::Mainnet,
|
||||
"prater" => EthSpecId::Mainnet,
|
||||
"pyrmont" => EthSpecId::Mainnet,
|
||||
"spadina" => EthSpecId::V012Legacy,
|
||||
"medalla" => EthSpecId::V012Legacy,
|
||||
"altona" => EthSpecId::V012Legacy,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
config_name: T::spec_name().to_string(),
|
||||
// ChainSpec
|
||||
max_committees_per_slot: spec.max_committees_per_slot as u64,
|
||||
target_committee_size: spec.target_committee_size as u64,
|
||||
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,
|
||||
genesis_delay: spec.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,
|
||||
proportional_slashing_multiplier: spec.proportional_slashing_multiplier,
|
||||
bls_withdrawal_prefix: spec.bls_withdrawal_prefix_byte,
|
||||
seconds_per_slot: spec.seconds_per_slot,
|
||||
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(),
|
||||
shard_committee_period: spec.shard_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
|
||||
max_validators_per_committee: T::MaxValidatorsPerCommittee::to_u32(),
|
||||
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() as u64,
|
||||
epochs_per_historical_vector: T::epochs_per_historical_vector() as u64,
|
||||
epochs_per_slashings_vector: T::EpochsPerSlashingsVector::to_u64(),
|
||||
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,
|
||||
deposit_chain_id: spec.deposit_chain_id,
|
||||
deposit_network_id: spec.deposit_network_id,
|
||||
deposit_contract_address: spec.deposit_contract_address,
|
||||
|
||||
extra_fields: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
// Check that YAML values match type-level EthSpec constants
|
||||
if self.max_validators_per_committee != T::MaxValidatorsPerCommittee::to_u32()
|
||||
|| 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() as u64
|
||||
|| self.epochs_per_historical_vector != T::epochs_per_historical_vector() as u64
|
||||
|| self.epochs_per_slashings_vector != T::EpochsPerSlashingsVector::to_u64()
|
||||
|| 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 {
|
||||
/*
|
||||
* Misc
|
||||
*/
|
||||
max_committees_per_slot: self.max_committees_per_slot as usize,
|
||||
target_committee_size: self.target_committee_size as usize,
|
||||
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,
|
||||
hysteresis_quotient: self.hysteresis_quotient,
|
||||
hysteresis_downward_multiplier: self.hysteresis_downward_multiplier,
|
||||
hysteresis_upward_multiplier: self.hysteresis_upward_multiplier,
|
||||
proportional_slashing_multiplier: self.proportional_slashing_multiplier,
|
||||
/*
|
||||
* Fork Choice
|
||||
*/
|
||||
safe_slots_to_update_justified: self.safe_slots_to_update_justified,
|
||||
/*
|
||||
* Validator
|
||||
*/
|
||||
eth1_follow_distance: self.eth1_follow_distance,
|
||||
target_aggregators_per_committee: self.target_aggregators_per_committee,
|
||||
random_subnets_per_validator: self.random_subnets_per_validator,
|
||||
epochs_per_random_subnet_subscription: self.epochs_per_random_subnet_subscription,
|
||||
seconds_per_eth1_block: self.seconds_per_eth1_block,
|
||||
deposit_chain_id: self.deposit_chain_id,
|
||||
deposit_network_id: self.deposit_network_id,
|
||||
deposit_contract_address: self.deposit_contract_address,
|
||||
/*
|
||||
* Gwei values
|
||||
*/
|
||||
min_deposit_amount: self.min_deposit_amount,
|
||||
max_effective_balance: self.max_effective_balance,
|
||||
ejection_balance: self.ejection_balance,
|
||||
effective_balance_increment: self.effective_balance_increment,
|
||||
/*
|
||||
* Initial values
|
||||
*/
|
||||
genesis_fork_version: self.genesis_fork_version,
|
||||
bls_withdrawal_prefix_byte: self.bls_withdrawal_prefix,
|
||||
/*
|
||||
* Time parameters
|
||||
*/
|
||||
genesis_delay: self.genesis_delay,
|
||||
seconds_per_slot: self.seconds_per_slot,
|
||||
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,
|
||||
),
|
||||
shard_committee_period: self.shard_committee_period,
|
||||
min_epochs_to_inactivity_penalty: self.min_epochs_to_inactivity_penalty,
|
||||
/*
|
||||
* Reward and penalty quotients
|
||||
*/
|
||||
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,
|
||||
/*
|
||||
* Signature domains
|
||||
*/
|
||||
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,
|
||||
domain_selection_proof: self.domain_selection_proof,
|
||||
domain_aggregate_and_proof: self.domain_aggregate_and_proof,
|
||||
/*
|
||||
* Lighthouse-specific parameters
|
||||
*
|
||||
* These are paramaters that are present in the chain spec but aren't part of the YAML
|
||||
* config. We avoid using `..chain_spec` so that changes to the set of fields don't
|
||||
* accidentally get forgotten (explicit better than implicit, yada yada).
|
||||
*/
|
||||
boot_nodes: chain_spec.boot_nodes.clone(),
|
||||
network_id: chain_spec.network_id,
|
||||
attestation_propagation_slot_range: chain_spec.attestation_propagation_slot_range,
|
||||
maximum_gossip_clock_disparity_millis: chain_spec.maximum_gossip_clock_disparity_millis,
|
||||
attestation_subnet_count: chain_spec.attestation_subnet_count,
|
||||
/*
|
||||
* Constants, not configurable.
|
||||
*/
|
||||
genesis_slot: chain_spec.genesis_slot,
|
||||
far_future_epoch: chain_spec.far_future_epoch,
|
||||
base_rewards_per_epoch: chain_spec.base_rewards_per_epoch,
|
||||
deposit_contract_tree_depth: chain_spec.deposit_contract_tree_depth,
|
||||
})
|
||||
test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,7 +694,7 @@ mod yaml_tests {
|
||||
.expect("error opening file");
|
||||
let minimal_spec = ChainSpec::minimal();
|
||||
|
||||
let yamlconfig = YamlConfig::from_spec::<MinimalEthSpec>(&minimal_spec);
|
||||
let yamlconfig = Config::from_chain_spec::<MinimalEthSpec>(&minimal_spec);
|
||||
// write fresh minimal config to file
|
||||
serde_yaml::to_writer(writer, &yamlconfig).expect("failed to write or serialize");
|
||||
|
||||
@@ -829,7 +704,7 @@ mod yaml_tests {
|
||||
.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");
|
||||
let from: Config = serde_yaml::from_reader(reader).expect("error while deserializing");
|
||||
assert_eq!(from, yamlconfig);
|
||||
}
|
||||
|
||||
@@ -842,7 +717,7 @@ mod yaml_tests {
|
||||
.open(tmp_file.as_ref())
|
||||
.expect("error opening file");
|
||||
let mainnet_spec = ChainSpec::mainnet();
|
||||
let yamlconfig = YamlConfig::from_spec::<MainnetEthSpec>(&mainnet_spec);
|
||||
let yamlconfig = Config::from_chain_spec::<MainnetEthSpec>(&mainnet_spec);
|
||||
serde_yaml::to_writer(writer, &yamlconfig).expect("failed to write or serialize");
|
||||
|
||||
let reader = OpenOptions::new()
|
||||
@@ -850,42 +725,17 @@ mod yaml_tests {
|
||||
.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 extra_fields_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 mut yamlconfig = YamlConfig::from_spec::<MainnetEthSpec>(&mainnet_spec);
|
||||
let (k1, v1) = ("SAMPLE_HARDFORK_KEY1", "123456789");
|
||||
let (k2, v2) = ("SAMPLE_HARDFORK_KEY2", "987654321");
|
||||
yamlconfig.extra_fields.insert(k1.into(), v1.into());
|
||||
yamlconfig.extra_fields.insert(k2.into(), v2.into());
|
||||
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");
|
||||
let from: Config = 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);
|
||||
let yamlconfig = Config::from_chain_spec::<MinimalEthSpec>(&spec);
|
||||
|
||||
// modifying the original spec
|
||||
spec.max_committees_per_slot += 1;
|
||||
spec.min_genesis_active_validator_count += 1;
|
||||
spec.deposit_chain_id += 1;
|
||||
spec.deposit_network_id += 1;
|
||||
// Applying a yaml config with incorrect EthSpec should fail
|
||||
|
||||
119
consensus/types/src/config_and_preset.rs
Normal file
119
consensus/types/src/config_and_preset.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use crate::{AltairPreset, BasePreset, ChainSpec, Config, EthSpec};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Fusion of a runtime-config with the compile-time preset values.
|
||||
///
|
||||
/// Mostly useful for the API.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct ConfigAndPreset {
|
||||
#[serde(flatten)]
|
||||
pub config: Config,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub base_preset: BasePreset,
|
||||
#[serde(flatten)]
|
||||
pub altair_preset: AltairPreset,
|
||||
|
||||
/// The `extra_fields` map allows us to gracefully decode fields intended for future hard forks.
|
||||
#[serde(flatten)]
|
||||
pub extra_fields: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ConfigAndPreset {
|
||||
pub fn from_chain_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
|
||||
let config = Config::from_chain_spec::<T>(spec);
|
||||
let base_preset = BasePreset::from_chain_spec::<T>(spec);
|
||||
let altair_preset = AltairPreset::from_chain_spec::<T>(spec);
|
||||
let extra_fields = HashMap::new();
|
||||
|
||||
Self {
|
||||
config,
|
||||
base_preset,
|
||||
altair_preset,
|
||||
extra_fields,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add fields that were previously part of the config but are now constants.
|
||||
pub fn make_backwards_compat(&mut self, spec: &ChainSpec) {
|
||||
let hex_string = |value: &[u8]| format!("0x{}", hex::encode(&value));
|
||||
let u32_hex = |v: u32| hex_string(&v.to_le_bytes());
|
||||
let u8_hex = |v: u8| hex_string(&v.to_le_bytes());
|
||||
let fields = vec![
|
||||
("config_name", self.config.preset_base.clone()),
|
||||
(
|
||||
"bls_withdrawal_prefix",
|
||||
u8_hex(spec.bls_withdrawal_prefix_byte),
|
||||
),
|
||||
(
|
||||
"domain_beacon_proposer",
|
||||
u32_hex(spec.domain_beacon_proposer),
|
||||
),
|
||||
(
|
||||
"domain_beacon_attester",
|
||||
u32_hex(spec.domain_beacon_attester),
|
||||
),
|
||||
("domain_randao", u32_hex(spec.domain_randao)),
|
||||
("domain_deposit", u32_hex(spec.domain_deposit)),
|
||||
("domain_voluntary_exit", u32_hex(spec.domain_voluntary_exit)),
|
||||
(
|
||||
"domain_selection_proof",
|
||||
u32_hex(spec.domain_selection_proof),
|
||||
),
|
||||
(
|
||||
"domain_aggregate_and_proof",
|
||||
u32_hex(spec.domain_aggregate_and_proof),
|
||||
),
|
||||
(
|
||||
"target_aggregators_per_committee",
|
||||
spec.target_aggregators_per_committee.to_string(),
|
||||
),
|
||||
(
|
||||
"random_subnets_per_validator",
|
||||
spec.random_subnets_per_validator.to_string(),
|
||||
),
|
||||
(
|
||||
"epochs_per_random_subnet_subscription",
|
||||
spec.epochs_per_random_subnet_subscription.to_string(),
|
||||
),
|
||||
];
|
||||
for (key, value) in fields {
|
||||
self.extra_fields.insert(key.to_uppercase(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::MainnetEthSpec;
|
||||
use std::fs::OpenOptions;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn extra_fields_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 mut yamlconfig = ConfigAndPreset::from_chain_spec::<MainnetEthSpec>(&mainnet_spec);
|
||||
let (k1, v1) = ("SAMPLE_HARDFORK_KEY1", "123456789");
|
||||
let (k2, v2) = ("SAMPLE_HARDFORK_KEY2", "987654321");
|
||||
yamlconfig.extra_fields.insert(k1.into(), v1.into());
|
||||
yamlconfig.extra_fields.insert(k2.into(), v2.into());
|
||||
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: ConfigAndPreset =
|
||||
serde_yaml::from_reader(reader).expect("error while deserializing");
|
||||
assert_eq!(from, yamlconfig);
|
||||
}
|
||||
}
|
||||
19
consensus/types/src/consts.rs
Normal file
19
consensus/types/src/consts.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
pub mod altair {
|
||||
pub const TIMELY_SOURCE_FLAG_INDEX: usize = 0;
|
||||
pub const TIMELY_TARGET_FLAG_INDEX: usize = 1;
|
||||
pub const TIMELY_HEAD_FLAG_INDEX: usize = 2;
|
||||
pub const TIMELY_SOURCE_WEIGHT: u64 = 14;
|
||||
pub const TIMELY_TARGET_WEIGHT: u64 = 26;
|
||||
pub const TIMELY_HEAD_WEIGHT: u64 = 14;
|
||||
pub const SYNC_REWARD_WEIGHT: u64 = 2;
|
||||
pub const PROPOSER_WEIGHT: u64 = 8;
|
||||
pub const WEIGHT_DENOMINATOR: u64 = 64;
|
||||
|
||||
pub const PARTICIPATION_FLAG_WEIGHTS: [u64; NUM_FLAG_INDICES] = [
|
||||
TIMELY_SOURCE_WEIGHT,
|
||||
TIMELY_TARGET_WEIGHT,
|
||||
TIMELY_HEAD_WEIGHT,
|
||||
];
|
||||
|
||||
pub const NUM_FLAG_INDICES: usize = 3;
|
||||
}
|
||||
@@ -3,15 +3,14 @@ use crate::*;
|
||||
use safe_arith::SafeArith;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_types::typenum::{
|
||||
Unsigned, U0, U1024, U1099511627776, U128, U16, U16777216, U2, U2048, U32, U4, U4096, U64,
|
||||
U65536, U8, U8192,
|
||||
Unsigned, U0, U1024, U1099511627776, U128, U16, U16777216, U2, U2048, U32, U4, U4096, U512,
|
||||
U64, U65536, U8, U8192,
|
||||
};
|
||||
use std::fmt::{self, Debug};
|
||||
use std::str::FromStr;
|
||||
|
||||
const MAINNET: &str = "mainnet";
|
||||
const MINIMAL: &str = "minimal";
|
||||
const LEGACY: &str = "v0.12-legacy";
|
||||
|
||||
/// Used to identify one of the `EthSpec` instances defined here.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@@ -19,7 +18,6 @@ const LEGACY: &str = "v0.12-legacy";
|
||||
pub enum EthSpecId {
|
||||
Mainnet,
|
||||
Minimal,
|
||||
V012Legacy,
|
||||
}
|
||||
|
||||
impl FromStr for EthSpecId {
|
||||
@@ -29,7 +27,6 @@ impl FromStr for EthSpecId {
|
||||
match s {
|
||||
MAINNET => Ok(EthSpecId::Mainnet),
|
||||
MINIMAL => Ok(EthSpecId::Minimal),
|
||||
LEGACY => Ok(EthSpecId::V012Legacy),
|
||||
_ => Err(format!("Unknown eth spec: {}", s)),
|
||||
}
|
||||
}
|
||||
@@ -40,7 +37,6 @@ impl fmt::Display for EthSpecId {
|
||||
let s = match self {
|
||||
EthSpecId::Mainnet => MAINNET,
|
||||
EthSpecId::Minimal => MINIMAL,
|
||||
EthSpecId::V012Legacy => LEGACY,
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
@@ -78,6 +74,10 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + 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;
|
||||
/*
|
||||
* New in Altair
|
||||
*/
|
||||
type SyncCommitteeSize: Unsigned + Clone + Sync + Send + Debug + PartialEq;
|
||||
/*
|
||||
* Derived values (set these CAREFULLY)
|
||||
*/
|
||||
@@ -182,8 +182,6 @@ macro_rules! params_from_eth_spec {
|
||||
}
|
||||
|
||||
/// Ethereum Foundation specifications.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct MainnetEthSpec;
|
||||
@@ -205,6 +203,7 @@ impl EthSpec for MainnetEthSpec {
|
||||
type MaxAttestations = U128;
|
||||
type MaxDeposits = U16;
|
||||
type MaxVoluntaryExits = U16;
|
||||
type SyncCommitteeSize = U512;
|
||||
type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch
|
||||
type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch
|
||||
|
||||
@@ -217,11 +216,7 @@ impl EthSpec for MainnetEthSpec {
|
||||
}
|
||||
}
|
||||
|
||||
pub type FoundationBeaconState = BeaconState<MainnetEthSpec>;
|
||||
|
||||
/// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct MinimalEthSpec;
|
||||
@@ -232,6 +227,7 @@ impl EthSpec for MinimalEthSpec {
|
||||
type SlotsPerHistoricalRoot = U64;
|
||||
type EpochsPerHistoricalVector = U64;
|
||||
type EpochsPerSlashingsVector = U64;
|
||||
type SyncCommitteeSize = U32;
|
||||
type MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch
|
||||
type SlotsPerEth1VotingPeriod = U32; // 4 epochs * 8 slots per epoch
|
||||
|
||||
@@ -257,46 +253,3 @@ impl EthSpec for MinimalEthSpec {
|
||||
EthSpecId::Minimal
|
||||
}
|
||||
}
|
||||
|
||||
pub type MinimalBeaconState = BeaconState<MinimalEthSpec>;
|
||||
|
||||
/// Suits the `v0.12.3` version of the eth2 spec:
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.3/configs/mainnet/phase0.yaml
|
||||
///
|
||||
/// This struct only needs to exist whilst we provide support for "legacy" testnets prior to v1.0.0
|
||||
/// (e.g., Medalla, Pyrmont, Spadina, Altona, etc.).
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct V012LegacyEthSpec;
|
||||
|
||||
impl EthSpec for V012LegacyEthSpec {
|
||||
type EpochsPerEth1VotingPeriod = U32;
|
||||
type SlotsPerEth1VotingPeriod = U1024; // 32 epochs * 32 slots per epoch
|
||||
|
||||
params_from_eth_spec!(MainnetEthSpec {
|
||||
SlotsPerEpoch,
|
||||
SlotsPerHistoricalRoot,
|
||||
EpochsPerHistoricalVector,
|
||||
EpochsPerSlashingsVector,
|
||||
MaxPendingAttestations,
|
||||
JustificationBitsLength,
|
||||
SubnetBitfieldLength,
|
||||
MaxValidatorsPerCommittee,
|
||||
GenesisEpoch,
|
||||
HistoricalRootsLimit,
|
||||
ValidatorRegistryLimit,
|
||||
MaxProposerSlashings,
|
||||
MaxAttesterSlashings,
|
||||
MaxAttestations,
|
||||
MaxDeposits,
|
||||
MaxVoluntaryExits
|
||||
});
|
||||
|
||||
fn default_spec() -> ChainSpec {
|
||||
ChainSpec::v012_legacy()
|
||||
}
|
||||
|
||||
fn spec_name() -> EthSpecId {
|
||||
EthSpecId::V012Legacy
|
||||
}
|
||||
}
|
||||
|
||||
47
consensus/types/src/fork_name.rs
Normal file
47
consensus/types/src/fork_name.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use crate::{ChainSpec, Epoch};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ForkName {
|
||||
Base,
|
||||
Altair,
|
||||
}
|
||||
|
||||
impl ForkName {
|
||||
pub fn list_all() -> Vec<ForkName> {
|
||||
vec![ForkName::Base, ForkName::Altair]
|
||||
}
|
||||
|
||||
/// Set the activation slots in the given `ChainSpec` so that the fork named by `self`
|
||||
/// is the only fork in effect from genesis.
|
||||
pub fn make_genesis_spec(&self, mut spec: ChainSpec) -> ChainSpec {
|
||||
// Assumes GENESIS_EPOCH = 0, which is safe because it's a constant.
|
||||
match self {
|
||||
ForkName::Base => {
|
||||
spec.altair_fork_epoch = None;
|
||||
spec
|
||||
}
|
||||
ForkName::Altair => {
|
||||
spec.altair_fork_epoch = Some(Epoch::new(0));
|
||||
spec
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ForkName {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(fork_name: &str) -> Result<Self, ()> {
|
||||
Ok(match fork_name {
|
||||
"phase0" | "base" => ForkName::Base,
|
||||
"altair" => ForkName::Altair,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct InconsistentFork {
|
||||
pub fork_at_slot: ForkName,
|
||||
pub object_fork: ForkName,
|
||||
}
|
||||
@@ -74,14 +74,17 @@ impl<'de> Deserialize<'de> for GraffitiString {
|
||||
impl Into<Graffiti> for GraffitiString {
|
||||
fn into(self) -> Graffiti {
|
||||
let graffiti_bytes = self.0.as_bytes();
|
||||
let mut graffiti = [0; 32];
|
||||
let mut graffiti = [0; GRAFFITI_BYTES_LEN];
|
||||
|
||||
let graffiti_len = std::cmp::min(graffiti_bytes.len(), 32);
|
||||
let graffiti_len = std::cmp::min(graffiti_bytes.len(), GRAFFITI_BYTES_LEN);
|
||||
|
||||
// Copy the provided bytes over.
|
||||
//
|
||||
// Panic-free because `graffiti_bytes.len()` <= `GRAFFITI_BYTES_LEN`.
|
||||
graffiti[..graffiti_len].copy_from_slice(&graffiti_bytes);
|
||||
graffiti
|
||||
.get_mut(..graffiti_len)
|
||||
.expect("graffiti_len <= GRAFFITI_BYTES_LEN")
|
||||
.copy_from_slice(&graffiti_bytes);
|
||||
graffiti.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,14 @@
|
||||
// Required for big type-level numbers
|
||||
#![recursion_limit = "128"]
|
||||
// Clippy lint set up
|
||||
#![deny(clippy::integer_arithmetic)]
|
||||
#![deny(clippy::disallowed_method)]
|
||||
#![cfg_attr(
|
||||
not(test),
|
||||
deny(
|
||||
clippy::integer_arithmetic,
|
||||
clippy::disallowed_method,
|
||||
clippy::indexing_slicing
|
||||
)
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
@@ -23,6 +29,7 @@ pub mod beacon_committee;
|
||||
pub mod beacon_state;
|
||||
pub mod chain_spec;
|
||||
pub mod checkpoint;
|
||||
pub mod consts;
|
||||
pub mod deposit;
|
||||
pub mod deposit_data;
|
||||
pub mod deposit_message;
|
||||
@@ -31,6 +38,7 @@ pub mod eth1_data;
|
||||
pub mod eth_spec;
|
||||
pub mod fork;
|
||||
pub mod fork_data;
|
||||
pub mod fork_name;
|
||||
pub mod free_attestation;
|
||||
pub mod graffiti;
|
||||
pub mod historical_batch;
|
||||
@@ -50,8 +58,13 @@ pub mod validator_subscription;
|
||||
pub mod voluntary_exit;
|
||||
#[macro_use]
|
||||
pub mod slot_epoch_macros;
|
||||
pub mod config_and_preset;
|
||||
pub mod participation_flags;
|
||||
pub mod preset;
|
||||
pub mod slot_epoch;
|
||||
pub mod subnet_id;
|
||||
pub mod sync_aggregate;
|
||||
pub mod sync_committee;
|
||||
mod tree_hash_impls;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
@@ -64,13 +77,19 @@ 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::{
|
||||
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockRef, BeaconBlockRefMut,
|
||||
};
|
||||
pub use crate::beacon_block_body::{
|
||||
BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyRef,
|
||||
BeaconBlockBodyRefMut,
|
||||
};
|
||||
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::chain_spec::{ChainSpec, Config, Domain};
|
||||
pub use crate::checkpoint::Checkpoint;
|
||||
pub use crate::config_and_preset::ConfigAndPreset;
|
||||
pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH};
|
||||
pub use crate::deposit_data::DepositData;
|
||||
pub use crate::deposit_message::DepositMessage;
|
||||
@@ -79,22 +98,29 @@ pub use crate::eth1_data::Eth1Data;
|
||||
pub use crate::eth_spec::EthSpecId;
|
||||
pub use crate::fork::Fork;
|
||||
pub use crate::fork_data::ForkData;
|
||||
pub use crate::fork_name::{ForkName, InconsistentFork};
|
||||
pub use crate::free_attestation::FreeAttestation;
|
||||
pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN};
|
||||
pub use crate::historical_batch::HistoricalBatch;
|
||||
pub use crate::indexed_attestation::IndexedAttestation;
|
||||
pub use crate::participation_flags::ParticipationFlags;
|
||||
pub use crate::pending_attestation::PendingAttestation;
|
||||
pub use crate::preset::{AltairPreset, BasePreset};
|
||||
pub use crate::proposer_slashing::ProposerSlashing;
|
||||
pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch};
|
||||
pub use crate::selection_proof::SelectionProof;
|
||||
pub use crate::shuffling_id::AttestationShufflingId;
|
||||
pub use crate::signed_aggregate_and_proof::SignedAggregateAndProof;
|
||||
pub use crate::signed_beacon_block::{SignedBeaconBlock, SignedBeaconBlockHash};
|
||||
pub use crate::signed_beacon_block::{
|
||||
SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockHash,
|
||||
};
|
||||
pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader;
|
||||
pub use crate::signed_voluntary_exit::SignedVoluntaryExit;
|
||||
pub use crate::signing_data::{SignedRoot, SigningData};
|
||||
pub use crate::slot_epoch::{Epoch, Slot};
|
||||
pub use crate::subnet_id::SubnetId;
|
||||
pub use crate::sync_aggregate::SyncAggregate;
|
||||
pub use crate::sync_committee::SyncCommittee;
|
||||
pub use crate::validator::Validator;
|
||||
pub use crate::validator_subscription::ValidatorSubscription;
|
||||
pub use crate::voluntary_exit::VoluntaryExit;
|
||||
@@ -105,6 +131,8 @@ pub type Address = H160;
|
||||
pub type ForkVersion = [u8; 4];
|
||||
|
||||
pub use bls::{
|
||||
AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, Signature, SignatureBytes,
|
||||
AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey,
|
||||
Signature, SignatureBytes,
|
||||
};
|
||||
pub use ssz_types::{typenum, typenum::Unsigned, BitList, BitVector, FixedVector, VariableList};
|
||||
pub use superstruct::superstruct;
|
||||
|
||||
87
consensus/types/src/participation_flags.rs
Normal file
87
consensus/types/src/participation_flags.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use crate::{consts::altair::NUM_FLAG_INDICES, test_utils::TestRandom, Hash256};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{Decode, DecodeError, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash::{TreeHash, TreeHashType};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Deserialize, Serialize, TestRandom)]
|
||||
#[serde(transparent)]
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
pub struct ParticipationFlags {
|
||||
bits: u8,
|
||||
}
|
||||
|
||||
impl ParticipationFlags {
|
||||
pub fn add_flag(&mut self, flag_index: usize) -> Result<(), ArithError> {
|
||||
if flag_index >= NUM_FLAG_INDICES {
|
||||
return Err(ArithError::Overflow);
|
||||
}
|
||||
self.bits |= 1u8.safe_shl(flag_index as u32)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has_flag(&self, flag_index: usize) -> Result<bool, ArithError> {
|
||||
if flag_index >= NUM_FLAG_INDICES {
|
||||
return Err(ArithError::Overflow);
|
||||
}
|
||||
let mask = 1u8.safe_shl(flag_index as u32)?;
|
||||
Ok(self.bits & mask == mask)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode implementation that transparently behaves like the inner `u8`.
|
||||
impl Decode for ParticipationFlags {
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
<u8 as Decode>::is_ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_fixed_len() -> usize {
|
||||
<u8 as Decode>::ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
|
||||
u8::from_ssz_bytes(bytes).map(|bits| Self { bits })
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode implementation that transparently behaves like the inner `u8`.
|
||||
impl Encode for ParticipationFlags {
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
<u8 as Encode>::is_ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_append(&self, buf: &mut Vec<u8>) {
|
||||
self.bits.ssz_append(buf);
|
||||
}
|
||||
|
||||
fn ssz_fixed_len() -> usize {
|
||||
<u8 as Encode>::ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_bytes_len(&self) -> usize {
|
||||
self.bits.ssz_bytes_len()
|
||||
}
|
||||
|
||||
fn as_ssz_bytes(&self) -> Vec<u8> {
|
||||
self.bits.as_ssz_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeHash for ParticipationFlags {
|
||||
fn tree_hash_type() -> TreeHashType {
|
||||
u8::tree_hash_type()
|
||||
}
|
||||
|
||||
fn tree_hash_packed_encoding(&self) -> Vec<u8> {
|
||||
self.bits.tree_hash_packed_encoding()
|
||||
}
|
||||
|
||||
fn tree_hash_packing_factor() -> usize {
|
||||
u8::tree_hash_packing_factor()
|
||||
}
|
||||
|
||||
fn tree_hash_root(&self) -> Hash256 {
|
||||
self.bits.tree_hash_root()
|
||||
}
|
||||
}
|
||||
196
consensus/types/src/preset.rs
Normal file
196
consensus/types/src/preset.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
use crate::{ChainSpec, Epoch, EthSpec, Unsigned};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
/// Value-level representation of an Ethereum consensus "preset".
|
||||
///
|
||||
/// This should only be used to check consistency of the compile-time constants
|
||||
/// with a preset YAML file, or to make preset values available to the API. Prefer
|
||||
/// the constants on `EthSpec` or the fields on `ChainSpec` to constructing and using
|
||||
/// one of these structs.
|
||||
///
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/dev/presets/mainnet/phase0.yaml
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct BasePreset {
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_committees_per_slot: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub target_committee_size: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_validators_per_committee: u64,
|
||||
#[serde(with = "serde_utils::quoted_u8")]
|
||||
pub shuffle_round_count: u8,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub hysteresis_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub hysteresis_downward_multiplier: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub hysteresis_upward_multiplier: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub safe_slots_to_update_justified: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub min_deposit_amount: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_effective_balance: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub effective_balance_increment: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub min_attestation_inclusion_delay: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub slots_per_epoch: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub min_seed_lookahead: Epoch,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_seed_lookahead: Epoch,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub epochs_per_eth1_voting_period: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub slots_per_historical_root: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub min_epochs_to_inactivity_penalty: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub epochs_per_historical_vector: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub epochs_per_slashings_vector: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub historical_roots_limit: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub validator_registry_limit: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub base_reward_factor: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub whistleblower_reward_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub proposer_reward_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub inactivity_penalty_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub min_slashing_penalty_quotient: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub proportional_slashing_multiplier: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_proposer_slashings: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_attester_slashings: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_attestations: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_deposits: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub max_voluntary_exits: u64,
|
||||
}
|
||||
|
||||
impl BasePreset {
|
||||
pub fn from_chain_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
max_committees_per_slot: spec.max_committees_per_slot as u64,
|
||||
target_committee_size: spec.target_committee_size as u64,
|
||||
max_validators_per_committee: T::MaxValidatorsPerCommittee::to_u64(),
|
||||
shuffle_round_count: spec.shuffle_round_count,
|
||||
hysteresis_quotient: spec.hysteresis_quotient,
|
||||
hysteresis_downward_multiplier: spec.hysteresis_downward_multiplier,
|
||||
hysteresis_upward_multiplier: spec.hysteresis_upward_multiplier,
|
||||
safe_slots_to_update_justified: spec.safe_slots_to_update_justified,
|
||||
min_deposit_amount: spec.min_deposit_amount,
|
||||
max_effective_balance: spec.max_effective_balance,
|
||||
effective_balance_increment: spec.effective_balance_increment,
|
||||
min_attestation_inclusion_delay: spec.min_attestation_inclusion_delay,
|
||||
slots_per_epoch: T::SlotsPerEpoch::to_u64(),
|
||||
min_seed_lookahead: spec.min_seed_lookahead,
|
||||
max_seed_lookahead: spec.max_seed_lookahead,
|
||||
epochs_per_eth1_voting_period: T::EpochsPerEth1VotingPeriod::to_u64(),
|
||||
slots_per_historical_root: T::SlotsPerHistoricalRoot::to_u64(),
|
||||
min_epochs_to_inactivity_penalty: spec.min_epochs_to_inactivity_penalty,
|
||||
epochs_per_historical_vector: T::EpochsPerHistoricalVector::to_u64(),
|
||||
epochs_per_slashings_vector: T::EpochsPerSlashingsVector::to_u64(),
|
||||
historical_roots_limit: T::HistoricalRootsLimit::to_u64(),
|
||||
validator_registry_limit: T::ValidatorRegistryLimit::to_u64(),
|
||||
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,
|
||||
proportional_slashing_multiplier: spec.proportional_slashing_multiplier,
|
||||
max_proposer_slashings: T::MaxProposerSlashings::to_u64(),
|
||||
max_attester_slashings: T::MaxAttesterSlashings::to_u64(),
|
||||
max_attestations: T::MaxAttestations::to_u64(),
|
||||
max_deposits: T::MaxDeposits::to_u64(),
|
||||
max_voluntary_exits: T::MaxVoluntaryExits::to_u64(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct AltairPreset {
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub inactivity_penalty_quotient_altair: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub min_slashing_penalty_quotient_altair: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub proportional_slashing_multiplier_altair: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub sync_committee_size: u64,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub epochs_per_sync_committee_period: Epoch,
|
||||
#[serde(with = "serde_utils::quoted_u64")]
|
||||
pub min_sync_committee_participants: u64,
|
||||
}
|
||||
|
||||
impl AltairPreset {
|
||||
pub fn from_chain_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
inactivity_penalty_quotient_altair: spec.inactivity_penalty_quotient_altair,
|
||||
min_slashing_penalty_quotient_altair: spec.min_slashing_penalty_quotient_altair,
|
||||
proportional_slashing_multiplier_altair: spec.proportional_slashing_multiplier_altair,
|
||||
sync_committee_size: T::SyncCommitteeSize::to_u64(),
|
||||
epochs_per_sync_committee_period: spec.epochs_per_sync_committee_period,
|
||||
min_sync_committee_participants: spec.min_sync_committee_participants,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{MainnetEthSpec, MinimalEthSpec};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn presets_base_path() -> PathBuf {
|
||||
env::var("CARGO_MANIFEST_DIR")
|
||||
.expect("should know manifest dir")
|
||||
.parse::<PathBuf>()
|
||||
.expect("should parse manifest dir as path")
|
||||
.join("presets")
|
||||
}
|
||||
|
||||
fn preset_from_file<T: DeserializeOwned>(preset_name: &str, filename: &str) -> T {
|
||||
let f = File::open(&presets_base_path().join(preset_name).join(filename))
|
||||
.expect("preset file exists");
|
||||
serde_yaml::from_reader(f).unwrap()
|
||||
}
|
||||
|
||||
fn preset_test<E: EthSpec>() {
|
||||
let preset_name = E::spec_name().to_string();
|
||||
let spec = E::default_spec();
|
||||
|
||||
let phase0: BasePreset = preset_from_file(&preset_name, "phase0.yaml");
|
||||
assert_eq!(phase0, BasePreset::from_chain_spec::<E>(&spec));
|
||||
|
||||
let altair: AltairPreset = preset_from_file(&preset_name, "altair.yaml");
|
||||
assert_eq!(altair, AltairPreset::from_chain_spec::<E>(&spec));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mainnet_presets_consistent() {
|
||||
preset_test::<MainnetEthSpec>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minimal_presets_consistent() {
|
||||
preset_test::<MinimalEthSpec>();
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,9 @@ impl SelectionProof {
|
||||
pub fn is_aggregator_from_modulo(&self, modulo: u64) -> Result<bool, ArithError> {
|
||||
let signature_hash = hash(&self.0.as_ssz_bytes());
|
||||
let signature_hash_int = u64::from_le_bytes(
|
||||
signature_hash[0..8]
|
||||
.as_ref()
|
||||
signature_hash
|
||||
.get(0..8)
|
||||
.expect("hash is 32 bytes")
|
||||
.try_into()
|
||||
.expect("first 8 bytes of signature should always convert to fixed array"),
|
||||
);
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use crate::{
|
||||
test_utils::TestRandom, BeaconBlock, ChainSpec, Domain, EthSpec, Fork, Hash256, PublicKey,
|
||||
SignedBeaconBlockHeader, SignedRoot, SigningData, Slot,
|
||||
};
|
||||
use crate::*;
|
||||
use bls::Signature;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::fmt;
|
||||
use test_random_derive::TestRandom;
|
||||
use superstruct::superstruct;
|
||||
use tree_hash::TreeHash;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
@@ -39,17 +36,116 @@ impl From<SignedBeaconBlockHash> for Hash256 {
|
||||
}
|
||||
|
||||
/// A `BeaconBlock` and a signature from its proposer.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[superstruct(
|
||||
variants(Base, Altair),
|
||||
variant_attributes(
|
||||
derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Encode,
|
||||
Decode,
|
||||
TreeHash
|
||||
),
|
||||
cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)),
|
||||
serde(bound = "E: EthSpec")
|
||||
)
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, TreeHash)]
|
||||
#[serde(untagged)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
pub struct SignedBeaconBlock<E: EthSpec> {
|
||||
pub message: BeaconBlock<E>,
|
||||
#[superstruct(only(Base), partial_getter(rename = "message_base"))]
|
||||
pub message: BeaconBlockBase<E>,
|
||||
#[superstruct(only(Altair), partial_getter(rename = "message_altair"))]
|
||||
pub message: BeaconBlockAltair<E>,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> SignedBeaconBlock<E> {
|
||||
/// Returns the name of the fork pertaining to `self`.
|
||||
///
|
||||
/// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork
|
||||
/// dictated by `self.slot()`.
|
||||
pub fn fork_name(&self, spec: &ChainSpec) -> Result<ForkName, InconsistentFork> {
|
||||
let fork_at_slot = spec.fork_name_at_slot::<E>(self.slot());
|
||||
let object_fork = match self {
|
||||
SignedBeaconBlock::Base { .. } => ForkName::Base,
|
||||
SignedBeaconBlock::Altair { .. } => ForkName::Altair,
|
||||
};
|
||||
|
||||
if fork_at_slot == object_fork {
|
||||
Ok(object_fork)
|
||||
} else {
|
||||
Err(InconsistentFork {
|
||||
fork_at_slot,
|
||||
object_fork,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// SSZ decode.
|
||||
pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result<Self, ssz::DecodeError> {
|
||||
// We need to use the slot-switching `from_ssz_bytes` of `BeaconBlock`, which doesn't
|
||||
// compose with the other SSZ utils, so we duplicate some parts of `ssz_derive` here.
|
||||
let mut builder = ssz::SszDecoderBuilder::new(bytes);
|
||||
|
||||
builder.register_anonymous_variable_length_item()?;
|
||||
builder.register_type::<Signature>()?;
|
||||
|
||||
let mut decoder = builder.build()?;
|
||||
|
||||
// Read the first item as a `BeaconBlock`.
|
||||
let message = decoder.decode_next_with(|bytes| BeaconBlock::from_ssz_bytes(bytes, spec))?;
|
||||
let signature = decoder.decode_next()?;
|
||||
|
||||
Ok(Self::from_block(message, signature))
|
||||
}
|
||||
|
||||
/// Create a new `SignedBeaconBlock` from a `BeaconBlock` and `Signature`.
|
||||
pub fn from_block(block: BeaconBlock<E>, signature: Signature) -> Self {
|
||||
match block {
|
||||
BeaconBlock::Base(message) => {
|
||||
SignedBeaconBlock::Base(SignedBeaconBlockBase { message, signature })
|
||||
}
|
||||
BeaconBlock::Altair(message) => {
|
||||
SignedBeaconBlock::Altair(SignedBeaconBlockAltair { message, signature })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deconstruct the `SignedBeaconBlock` into a `BeaconBlock` and `Signature`.
|
||||
///
|
||||
/// This is necessary to get a `&BeaconBlock` from a `SignedBeaconBlock` because
|
||||
/// `SignedBeaconBlock` only contains a `BeaconBlock` _variant_.
|
||||
pub fn deconstruct(self) -> (BeaconBlock<E>, Signature) {
|
||||
match self {
|
||||
SignedBeaconBlock::Base(block) => (BeaconBlock::Base(block.message), block.signature),
|
||||
SignedBeaconBlock::Altair(block) => {
|
||||
(BeaconBlock::Altair(block.message), block.signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessor for the block's `message` field as a ref.
|
||||
pub fn message(&self) -> BeaconBlockRef<'_, E> {
|
||||
match self {
|
||||
SignedBeaconBlock::Base(inner) => BeaconBlockRef::Base(&inner.message),
|
||||
SignedBeaconBlock::Altair(inner) => BeaconBlockRef::Altair(&inner.message),
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessor for the block's `message` as a mutable reference (for testing only).
|
||||
pub fn message_mut(&mut self) -> BeaconBlockRefMut<'_, E> {
|
||||
match self {
|
||||
SignedBeaconBlock::Base(inner) => BeaconBlockRefMut::Base(&mut inner.message),
|
||||
SignedBeaconBlock::Altair(inner) => BeaconBlockRefMut::Altair(&mut inner.message),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify `self.signature`.
|
||||
///
|
||||
/// If the root of `block.message` is already known it can be passed in via `object_root_opt`.
|
||||
@@ -62,8 +158,14 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
|
||||
genesis_validators_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
// Refuse to verify the signature of a block if its structure does not match the fork at
|
||||
// `self.slot()`.
|
||||
if self.fork_name(spec).is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let domain = spec.get_domain(
|
||||
self.message.slot.epoch(E::slots_per_epoch()),
|
||||
self.slot().epoch(E::slots_per_epoch()),
|
||||
Domain::BeaconProposer,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
@@ -76,47 +178,37 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
|
||||
}
|
||||
.tree_hash_root()
|
||||
} else {
|
||||
self.message.signing_root(domain)
|
||||
self.message().signing_root(domain)
|
||||
};
|
||||
|
||||
self.signature.verify(pubkey, message)
|
||||
self.signature().verify(pubkey, message)
|
||||
}
|
||||
|
||||
/// Produce a signed beacon block header corresponding to this block.
|
||||
pub fn signed_block_header(&self) -> SignedBeaconBlockHeader {
|
||||
SignedBeaconBlockHeader {
|
||||
message: self.message.block_header(),
|
||||
signature: self.signature.clone(),
|
||||
message: self.message().block_header(),
|
||||
signature: self.signature().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience accessor for the block's slot.
|
||||
pub fn slot(&self) -> Slot {
|
||||
self.message.slot
|
||||
self.message().slot()
|
||||
}
|
||||
|
||||
/// Convenience accessor for the block's parent root.
|
||||
pub fn parent_root(&self) -> Hash256 {
|
||||
self.message.parent_root
|
||||
self.message().parent_root()
|
||||
}
|
||||
|
||||
/// Convenience accessor for the block's state root.
|
||||
pub fn state_root(&self) -> Hash256 {
|
||||
self.message.state_root
|
||||
self.message().state_root()
|
||||
}
|
||||
|
||||
/// Returns the `tree_hash_root` of the block.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn canonical_root(&self) -> Hash256 {
|
||||
Hash256::from_slice(&self.message.tree_hash_root()[..])
|
||||
self.message().tree_hash_root()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::MainnetEthSpec;
|
||||
|
||||
ssz_tests!(SignedBeaconBlock<MainnetEthSpec>);
|
||||
}
|
||||
|
||||
36
consensus/types/src/sync_aggregate.rs
Normal file
36
consensus/types/src/sync_aggregate.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{AggregateSignature, BitVector, EthSpec};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct SyncAggregate<T: EthSpec> {
|
||||
pub sync_committee_bits: BitVector<T::SyncCommitteeSize>,
|
||||
pub sync_committee_signature: AggregateSignature,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SyncAggregate<T> {
|
||||
/// New aggregate to be used as the seed for aggregating other signatures.
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sync_committee_bits: BitVector::default(),
|
||||
sync_committee_signature: AggregateSignature::infinity(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Empty aggregate to be used at genesis.
|
||||
///
|
||||
/// Contains an empty signature and should *not* be used as the starting point for aggregation,
|
||||
/// use `new` instead.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
sync_committee_bits: BitVector::default(),
|
||||
sync_committee_signature: AggregateSignature::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
29
consensus/types/src/sync_committee.rs
Normal file
29
consensus/types/src/sync_committee.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::typenum::Unsigned;
|
||||
use crate::{EthSpec, FixedVector};
|
||||
use bls::PublicKeyBytes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct SyncCommittee<T: EthSpec> {
|
||||
pub pubkeys: FixedVector<PublicKeyBytes, T::SyncCommitteeSize>,
|
||||
pub aggregate_pubkey: PublicKeyBytes,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> SyncCommittee<T> {
|
||||
/// Create a temporary sync committee that should *never* be included in a legitimate consensus object.
|
||||
pub fn temporary() -> Result<Self, ssz_types::Error> {
|
||||
Ok(Self {
|
||||
pubkeys: FixedVector::new(vec![
|
||||
PublicKeyBytes::empty();
|
||||
T::SyncCommitteeSize::to_usize()
|
||||
])?,
|
||||
aggregate_pubkey: PublicKeyBytes::empty(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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::*;
|
||||
@@ -1,112 +0,0 @@
|
||||
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::empty(),
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
use crate::test_utils::AttestationTestTask;
|
||||
use crate::*;
|
||||
use safe_arith::SafeArith;
|
||||
|
||||
/// 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
|
||||
} else {
|
||||
state.current_justified_checkpoint
|
||||
};
|
||||
|
||||
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
|
||||
.safe_sub(spec.min_attestation_inclusion_delay)
|
||||
.unwrap()
|
||||
.safe_add(1u64)
|
||||
.unwrap();
|
||||
}
|
||||
AttestationTestTask::IncludedTooLate => slot
|
||||
.safe_sub_assign(Slot::new(T::SlotsPerEpoch::to_u64()))
|
||||
.unwrap(),
|
||||
AttestationTestTask::TargetEpochSlotMismatch => {
|
||||
target = Checkpoint {
|
||||
epoch: current_epoch.safe_add(1u64).unwrap(),
|
||||
root: Hash256::zero(),
|
||||
};
|
||||
assert_ne!(target.epoch, slot.epoch(T::slots_per_epoch()));
|
||||
}
|
||||
AttestationTestTask::WrongJustifiedCheckpoint => {
|
||||
source = Checkpoint {
|
||||
epoch: Epoch::from(0_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
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
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]`
|
||||
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,
|
||||
{
|
||||
TestingAttesterSlashingBuilder::double_vote_with_additional_indices(
|
||||
test_task,
|
||||
validator_indices,
|
||||
None,
|
||||
signer,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn double_vote_with_additional_indices<F, T: EthSpec>(
|
||||
test_task: AttesterSlashingTestTask,
|
||||
validator_indices: &[u64],
|
||||
additional_validator_indices: Option<&[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,
|
||||
target: checkpoint_1,
|
||||
};
|
||||
|
||||
let data_2 = if test_task == AttesterSlashingTestTask::NotSlashable {
|
||||
data_1.clone()
|
||||
} else {
|
||||
AttestationData {
|
||||
target: checkpoint_2,
|
||||
..data_1
|
||||
}
|
||||
};
|
||||
|
||||
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::empty(),
|
||||
};
|
||||
|
||||
let mut attestation_2 = IndexedAttestation {
|
||||
attesting_indices: if test_task == AttesterSlashingTestTask::IndexedAttestation2Invalid
|
||||
{
|
||||
// Trigger bad validator indices ordering error.
|
||||
vec![1, 0].into()
|
||||
} else {
|
||||
match additional_validator_indices {
|
||||
Some(x) => x.to_vec().into(),
|
||||
None => validator_indices.to_vec().into(),
|
||||
}
|
||||
},
|
||||
data: data_2,
|
||||
signature: AggregateSignature::empty(),
|
||||
};
|
||||
|
||||
let add_signatures = |attestation: &mut IndexedAttestation<T>, indices_to_sign: &[u64]| {
|
||||
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 indices_to_sign {
|
||||
let signature = signer(*validator_index, message.as_bytes());
|
||||
attestation.signature.add_assign(&signature);
|
||||
}
|
||||
};
|
||||
|
||||
add_signatures(&mut attestation_1, validator_indices);
|
||||
add_signatures(
|
||||
&mut attestation_2,
|
||||
additional_validator_indices.unwrap_or(validator_indices),
|
||||
);
|
||||
|
||||
AttesterSlashing {
|
||||
attestation_1,
|
||||
attestation_2,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,428 +0,0 @@
|
||||
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 safe_arith::SafeArith;
|
||||
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 = sk.sign(message);
|
||||
}
|
||||
|
||||
/// 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
|
||||
.safe_sub(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.safe_add(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.safe_sub_assign(1u64)?;
|
||||
}
|
||||
|
||||
// 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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");
|
||||
secret_keys[key_index].sign(Hash256::from_slice(message))
|
||||
};
|
||||
|
||||
TestingAttesterSlashingBuilder::double_vote(
|
||||
test_task,
|
||||
validator_indices,
|
||||
signer,
|
||||
fork,
|
||||
genesis_validators_root,
|
||||
spec,
|
||||
)
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
use super::super::generate_deterministic_keypairs;
|
||||
use crate::test_utils::{AttestationTestTask, TestingPendingAttestationBuilder};
|
||||
use crate::*;
|
||||
use bls::get_withdrawal_credentials;
|
||||
use log::debug;
|
||||
use rayon::prelude::*;
|
||||
|
||||
pub const KEYPAIRS_FILE: &str = "keypairs.raw_keypairs";
|
||||
|
||||
/// 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> {
|
||||
/// 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.saturating_sub(3u64);
|
||||
state.current_justified_checkpoint.epoch = epoch.saturating_sub(2u64);
|
||||
state.justification_bits = BitVector::from_bytes(vec![0b0000_1111]).unwrap();
|
||||
|
||||
state.finalized_checkpoint.epoch = state.previous_justified_checkpoint.epoch;
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
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::deserialize(&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
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
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(),
|
||||
};
|
||||
|
||||
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(),
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,50 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod builders;
|
||||
mod generate_deterministic_keypairs;
|
||||
mod test_random;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use rand::{RngCore, SeedableRng};
|
||||
pub use rand_xorshift::XorShiftRng;
|
||||
|
||||
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 rand::{RngCore, SeedableRng};
|
||||
pub use rand_xorshift::XorShiftRng;
|
||||
use ssz::{ssz_encode, Decode, Encode};
|
||||
pub use test_random::{test_random_instance, TestRandom};
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod generate_deterministic_keypairs;
|
||||
mod test_random;
|
||||
|
||||
pub fn test_ssz_tree_hash_pair<T, U>(v1: &T, v2: &U)
|
||||
where
|
||||
T: TreeHash + Encode + Decode + Debug + PartialEq,
|
||||
U: TreeHash + Encode + Decode + Debug + PartialEq,
|
||||
{
|
||||
test_ssz_tree_hash_pair_with(v1, v2, T::from_ssz_bytes)
|
||||
}
|
||||
|
||||
pub fn test_ssz_tree_hash_pair_with<T, U>(
|
||||
v1: &T,
|
||||
v2: &U,
|
||||
t_decoder: impl FnOnce(&[u8]) -> Result<T, ssz::DecodeError>,
|
||||
) where
|
||||
T: TreeHash + Encode + Debug + PartialEq,
|
||||
U: TreeHash + Encode + Decode + Debug + PartialEq,
|
||||
{
|
||||
// SSZ encoding should agree between the two types.
|
||||
let encoding1 = ssz_encode(v1);
|
||||
let encoding2 = ssz_encode(v2);
|
||||
assert_eq!(encoding1, encoding2);
|
||||
|
||||
// Decoding the encoding should yield either value.
|
||||
let decoded1 = t_decoder(&encoding1).unwrap();
|
||||
assert_eq!(&decoded1, v1);
|
||||
|
||||
let decoded2 = U::from_ssz_bytes(&encoding1).unwrap();
|
||||
assert_eq!(&decoded2, v2);
|
||||
|
||||
// Tree hashing should agree.
|
||||
assert_eq!(v1.tree_hash_root(), v2.tree_hash_root());
|
||||
}
|
||||
|
||||
@@ -41,6 +41,12 @@ impl TestRandom for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRandom for u8 {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
rng.next_u32().to_be_bytes()[0]
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRandom for usize {
|
||||
fn random_for_test(rng: &mut impl RngCore) -> Self {
|
||||
rng.next_u32() as usize
|
||||
@@ -64,16 +70,15 @@ where
|
||||
|
||||
impl<T, N: Unsigned> TestRandom for FixedVector<T, N>
|
||||
where
|
||||
T: TestRandom + Default,
|
||||
T: TestRandom,
|
||||
{
|
||||
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()
|
||||
Self::new(
|
||||
(0..N::to_usize())
|
||||
.map(|_| T::random_for_test(rng))
|
||||
.collect(),
|
||||
)
|
||||
.expect("N items provided")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Validator {
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
// Placement in queue is finalized
|
||||
self.activation_eligibility_epoch <= state.finalized_checkpoint.epoch
|
||||
self.activation_eligibility_epoch <= state.finalized_checkpoint().epoch
|
||||
// Has not yet been activated
|
||||
&& self.activation_epoch == spec.far_future_epoch
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user