Merge branch 'master' into fork-choices.
This introduces the `Height` type which keeps track of block_height types. Further integration into beacon chain with the merge.
This commit is contained in:
commit
ccaaaffde1
@ -2,8 +2,8 @@
|
||||
members = [
|
||||
"eth2/attester",
|
||||
"eth2/block_producer",
|
||||
"eth2/genesis",
|
||||
"eth2/fork_choice",
|
||||
"eth2/state_processing",
|
||||
"eth2/types",
|
||||
"eth2/utils/bls",
|
||||
"eth2/utils/boolean-bitfield",
|
||||
@ -12,7 +12,6 @@ members = [
|
||||
"eth2/utils/slot_clock",
|
||||
"eth2/utils/ssz",
|
||||
"eth2/utils/vec_shuffle",
|
||||
"eth2/validator_induction",
|
||||
"beacon_node",
|
||||
"beacon_node/db",
|
||||
"beacon_node/beacon_chain",
|
||||
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
FROM rust:latest
|
||||
|
||||
RUN apt-get update && apt-get install -y clang libclang-dev cmake build-essential git unzip autoconf libtool
|
||||
|
||||
RUN git clone https://github.com/google/protobuf.git && \
|
||||
cd protobuf && \
|
||||
./autogen.sh && \
|
||||
./configure && \
|
||||
make && \
|
||||
make install && \
|
||||
ldconfig && \
|
||||
make clean && \
|
||||
cd .. && \
|
||||
rm -r protobuf
|
||||
|
||||
|
||||
RUN mkdir /cargocache && chmod -R ugo+rwX /cargocache
|
20
Jenkinsfile
vendored
Normal file
20
Jenkinsfile
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
pipeline {
|
||||
agent {
|
||||
dockerfile {
|
||||
filename 'Dockerfile'
|
||||
args '-v cargo-cache:/cargocache:rw -e "CARGO_HOME=/cargocache"'
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'cargo build'
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'cargo test --all'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
README.md
11
README.md
@ -100,15 +100,8 @@ tests and benchmarks which may be of interest.
|
||||
|
||||
A few basic steps are needed to get set up:
|
||||
|
||||
1. Install [rustup](https://rustup.rs/). It's a toolchain manager for Rust (Linux | macos | Windows). For installation run the below command in your terminal
|
||||
```
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
2. (Linux & MacOS) To configure your current shell run:
|
||||
|
||||
```
|
||||
$ source $HOME/.cargo/env
|
||||
```
|
||||
1. Install [rustup](https://rustup.rs/). It's a toolchain manager for Rust (Linux | macos | Windows). For installation run the below command in your terminal `$ curl https://sh.rustup.rs -sSf | sh`
|
||||
2. (Linux & MacOS) To configure your current shell run: `$ source $HOME/.cargo/env`
|
||||
3. Use the command `rustup show` to get information about the Rust installation. You should see that the
|
||||
active toolchain is the stable version.
|
||||
4. Run `rustc --version` to check the installation and version of rust.
|
||||
|
@ -15,7 +15,6 @@ db = { path = "db" }
|
||||
dirs = "1.0.3"
|
||||
futures = "0.1.23"
|
||||
fork_choice = { path = "../eth2/fork_choice" }
|
||||
genesis = { path = "../eth2/genesis" }
|
||||
slog = "^2.2.3"
|
||||
slot_clock = { path = "../eth2/utils/slot_clock" }
|
||||
slog-term = "^2.4.0"
|
||||
|
@ -11,7 +11,6 @@ boolean-bitfield = { path = "../../eth2/utils/boolean-bitfield" }
|
||||
db = { path = "../db" }
|
||||
failure = "0.1"
|
||||
failure_derive = "0.1"
|
||||
genesis = { path = "../../eth2/genesis" }
|
||||
hashing = { path = "../../eth2/utils/hashing" }
|
||||
fork_choice = { path = "../../eth2/fork_choice" }
|
||||
parking_lot = "0.7"
|
||||
@ -22,4 +21,5 @@ serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
||||
ssz = { path = "../../eth2/utils/ssz" }
|
||||
state_processing = { path = "../../eth2/state_processing" }
|
||||
types = { path = "../../eth2/types" }
|
||||
|
@ -1,3 +1,4 @@
|
||||
use state_processing::validate_attestation_without_signature;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use types::{
|
||||
beacon_state::CommitteesError, AggregateSignature, Attestation, AttestationData, BeaconState,
|
||||
@ -16,6 +17,7 @@ const PHASE_0_CUSTODY_BIT: bool = false;
|
||||
///
|
||||
/// Note: `Attestations` are stored in memory and never deleted. This is not scalable and must be
|
||||
/// rectified in a future revision.
|
||||
#[derive(Default)]
|
||||
pub struct AttestationAggregator {
|
||||
store: HashMap<Vec<u8>, Attestation>,
|
||||
}
|
||||
@ -172,9 +174,7 @@ impl AttestationAggregator {
|
||||
self.store
|
||||
.values()
|
||||
.filter_map(|attestation| {
|
||||
if state
|
||||
.validate_attestation_without_signature(attestation, spec)
|
||||
.is_ok()
|
||||
if validate_attestation_without_signature(&state, attestation, spec).is_ok()
|
||||
&& !known_attestation_data.contains(&attestation.data)
|
||||
{
|
||||
Some(attestation.clone())
|
||||
|
@ -1,24 +1,25 @@
|
||||
use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome};
|
||||
use crate::checkpoint::CheckPoint;
|
||||
use db::{
|
||||
stores::{BeaconBlockStore, BeaconStateStore},
|
||||
ClientDB, DBError,
|
||||
};
|
||||
use fork_choice::{ForkChoice, ForkChoiceError};
|
||||
use genesis::{genesis_beacon_block, genesis_beacon_state};
|
||||
use log::{debug, trace};
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use slot_clock::SlotClock;
|
||||
use ssz::ssz_encode;
|
||||
use state_processing::{
|
||||
BlockProcessable, BlockProcessingError, SlotProcessable, SlotProcessingError,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
beacon_state::{BlockProcessingError, CommitteesError, SlotProcessingError},
|
||||
beacon_state::CommitteesError,
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Eth1Data,
|
||||
FreeAttestation, Hash256, PublicKey, Signature,
|
||||
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Crosslink, Deposit,
|
||||
Epoch, Eth1Data, FreeAttestation, Hash256, PublicKey, Signature, Slot,
|
||||
};
|
||||
|
||||
use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome};
|
||||
use crate::checkpoint::CheckPoint;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
InsufficientValidators,
|
||||
@ -67,7 +68,6 @@ pub struct BeaconChain<T: ClientDB + Sized, U: SlotClock, F: ForkChoice> {
|
||||
pub attestation_aggregator: RwLock<AttestationAggregator>,
|
||||
canonical_head: RwLock<CheckPoint>,
|
||||
finalized_head: RwLock<CheckPoint>,
|
||||
justified_head: RwLock<CheckPoint>,
|
||||
pub state: RwLock<BeaconState>,
|
||||
pub spec: ChainSpec,
|
||||
pub fork_choice: RwLock<F>,
|
||||
@ -84,18 +84,26 @@ where
|
||||
state_store: Arc<BeaconStateStore<T>>,
|
||||
block_store: Arc<BeaconBlockStore<T>>,
|
||||
slot_clock: U,
|
||||
genesis_time: u64,
|
||||
latest_eth1_data: Eth1Data,
|
||||
initial_validator_deposits: Vec<Deposit>,
|
||||
spec: ChainSpec,
|
||||
fork_choice: F,
|
||||
) -> Result<Self, Error> {
|
||||
if spec.initial_validators.is_empty() {
|
||||
if initial_validator_deposits.is_empty() {
|
||||
return Err(Error::InsufficientValidators);
|
||||
}
|
||||
|
||||
let genesis_state = genesis_beacon_state(&spec);
|
||||
let genesis_state = BeaconState::genesis(
|
||||
genesis_time,
|
||||
initial_validator_deposits,
|
||||
latest_eth1_data,
|
||||
&spec,
|
||||
);
|
||||
let state_root = genesis_state.canonical_root();
|
||||
state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?;
|
||||
|
||||
let genesis_block = genesis_beacon_block(state_root, &spec);
|
||||
let genesis_block = BeaconBlock::genesis(state_root, &spec);
|
||||
let block_root = genesis_block.canonical_root();
|
||||
block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?;
|
||||
|
||||
@ -105,12 +113,6 @@ where
|
||||
genesis_state.clone(),
|
||||
state_root,
|
||||
));
|
||||
let justified_head = RwLock::new(CheckPoint::new(
|
||||
genesis_block.clone(),
|
||||
block_root,
|
||||
genesis_state.clone(),
|
||||
state_root,
|
||||
));
|
||||
let canonical_head = RwLock::new(CheckPoint::new(
|
||||
genesis_block.clone(),
|
||||
block_root,
|
||||
@ -125,7 +127,6 @@ where
|
||||
slot_clock,
|
||||
attestation_aggregator,
|
||||
state: RwLock::new(genesis_state.clone()),
|
||||
justified_head,
|
||||
finalized_head,
|
||||
canonical_head,
|
||||
spec,
|
||||
@ -193,10 +194,10 @@ where
|
||||
/// It is important to note that this is _not_ the state corresponding to the canonical head
|
||||
/// block, instead it is that state which may or may not have had additional per slot/epoch
|
||||
/// processing applied to it.
|
||||
pub fn advance_state(&self, slot: u64) -> Result<(), SlotProcessingError> {
|
||||
pub fn advance_state(&self, slot: Slot) -> Result<(), SlotProcessingError> {
|
||||
let state_slot = self.state.read().slot;
|
||||
let head_block_root = self.head().beacon_block_root;
|
||||
for _ in state_slot..slot {
|
||||
for _ in state_slot.as_u64()..slot.as_u64() {
|
||||
self.state
|
||||
.write()
|
||||
.per_slot_processing(head_block_root, &self.spec)?;
|
||||
@ -222,19 +223,6 @@ where
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the number of slots the validator has been required to propose.
|
||||
///
|
||||
/// Returns `None` if the `validator_index` is invalid.
|
||||
///
|
||||
/// Information is retrieved from the present `beacon_state.validator_registry`.
|
||||
pub fn proposer_slots(&self, validator_index: usize) -> Option<u64> {
|
||||
if let Some(validator) = self.state.read().validator_registry.get(validator_index) {
|
||||
Some(validator.proposer_slots)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the slot clock, returns `None` if the slot is unavailable.
|
||||
///
|
||||
/// The slot might be unavailable due to an error with the system clock, or if the present time
|
||||
@ -243,9 +231,10 @@ where
|
||||
/// This is distinct to `present_slot`, which simply reads the latest state. If a
|
||||
/// call to `read_slot_clock` results in a higher slot than a call to `present_slot`,
|
||||
/// `self.state` should undergo per slot processing.
|
||||
pub fn read_slot_clock(&self) -> Option<u64> {
|
||||
pub fn read_slot_clock(&self) -> Option<Slot> {
|
||||
match self.slot_clock.present_slot() {
|
||||
Ok(some_slot) => some_slot,
|
||||
Ok(Some(some_slot)) => Some(some_slot),
|
||||
Ok(None) => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -255,7 +244,7 @@ where
|
||||
/// This is distinct to `read_slot_clock`, which reads from the actual system clock. If
|
||||
/// `self.state` has not been transitioned it is possible for the system clock to be on a
|
||||
/// different slot to what is returned from this call.
|
||||
pub fn present_slot(&self) -> u64 {
|
||||
pub fn present_slot(&self) -> Slot {
|
||||
self.state.read().slot
|
||||
}
|
||||
|
||||
@ -263,7 +252,7 @@ where
|
||||
///
|
||||
/// Information is read from the present `beacon_state` shuffling, so only information from the
|
||||
/// present and prior epoch is available.
|
||||
pub fn block_proposer(&self, slot: u64) -> Result<usize, CommitteesError> {
|
||||
pub fn block_proposer(&self, slot: Slot) -> Result<usize, CommitteesError> {
|
||||
let index = self
|
||||
.state
|
||||
.read()
|
||||
@ -273,8 +262,8 @@ where
|
||||
}
|
||||
|
||||
/// Returns the justified slot for the present state.
|
||||
pub fn justified_slot(&self) -> u64 {
|
||||
self.state.read().justified_slot
|
||||
pub fn justified_epoch(&self) -> Epoch {
|
||||
self.state.read().justified_epoch
|
||||
}
|
||||
|
||||
/// Returns the attestation slot and shard for a given validator index.
|
||||
@ -284,7 +273,7 @@ where
|
||||
pub fn validator_attestion_slot_and_shard(
|
||||
&self,
|
||||
validator_index: usize,
|
||||
) -> Result<Option<(u64, u64)>, CommitteesError> {
|
||||
) -> Result<Option<(Slot, u64)>, CommitteesError> {
|
||||
if let Some((slot, shard, _committee)) = self
|
||||
.state
|
||||
.read()
|
||||
@ -298,11 +287,14 @@ where
|
||||
|
||||
/// Produce an `AttestationData` that is valid for the present `slot` and given `shard`.
|
||||
pub fn produce_attestation_data(&self, shard: u64) -> Result<AttestationData, Error> {
|
||||
let justified_slot = self.justified_slot();
|
||||
let justified_epoch = self.justified_epoch();
|
||||
let justified_block_root = *self
|
||||
.state
|
||||
.read()
|
||||
.get_block_root(justified_slot, &self.spec)
|
||||
.get_block_root(
|
||||
justified_epoch.start_slot(self.spec.epoch_length),
|
||||
&self.spec,
|
||||
)
|
||||
.ok_or_else(|| Error::BadRecentBlockRoots)?;
|
||||
|
||||
let epoch_boundary_root = *self
|
||||
@ -320,8 +312,11 @@ where
|
||||
beacon_block_root: self.head().beacon_block_root,
|
||||
epoch_boundary_root,
|
||||
shard_block_root: Hash256::zero(),
|
||||
latest_crosslink_root: Hash256::zero(),
|
||||
justified_slot,
|
||||
latest_crosslink: Crosslink {
|
||||
epoch: self.state.read().slot.epoch(self.spec.epoch_length),
|
||||
shard_block_root: Hash256::zero(),
|
||||
},
|
||||
justified_epoch,
|
||||
justified_block_root,
|
||||
})
|
||||
}
|
||||
@ -450,7 +445,7 @@ where
|
||||
|
||||
// Transition the parent state to the present slot.
|
||||
let mut state = parent_state;
|
||||
for _ in state.slot..present_slot {
|
||||
for _ in state.slot.as_u64()..present_slot.as_u64() {
|
||||
if let Err(e) = state.per_slot_processing(parent_block_root, &self.spec) {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::SlotProcessingError(e),
|
||||
@ -520,7 +515,7 @@ where
|
||||
attestations.len()
|
||||
);
|
||||
|
||||
let parent_root = *state.get_block_root(state.slot.saturating_sub(1), &self.spec)?;
|
||||
let parent_root = *state.get_block_root(state.slot.saturating_sub(1_u64), &self.spec)?;
|
||||
|
||||
let mut block = BeaconBlock {
|
||||
slot: state.slot,
|
||||
@ -535,11 +530,8 @@ where
|
||||
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
|
||||
body: BeaconBlockBody {
|
||||
proposer_slashings: vec![],
|
||||
casper_slashings: vec![],
|
||||
attester_slashings: vec![],
|
||||
attestations,
|
||||
custody_reseeds: vec![],
|
||||
custody_challenges: vec![],
|
||||
custody_responses: vec![],
|
||||
deposits: vec![],
|
||||
exits: vec![],
|
||||
},
|
||||
|
@ -22,7 +22,6 @@ parking_lot = "0.7"
|
||||
failure = "0.1"
|
||||
failure_derive = "0.1"
|
||||
fork_choice = { path = "../../../eth2/fork_choice" }
|
||||
genesis = { path = "../../../eth2/genesis" }
|
||||
hashing = { path = "../../../eth2/utils/hashing" }
|
||||
log = "0.4"
|
||||
env_logger = "0.6.0"
|
||||
|
@ -1,6 +1,7 @@
|
||||
use super::TestValidator;
|
||||
use super::ValidatorHarness;
|
||||
use beacon_chain::BeaconChain;
|
||||
pub use beacon_chain::{CheckPoint, Error as BeaconChainError};
|
||||
use bls::create_proof_of_possession;
|
||||
use db::{
|
||||
stores::{BeaconBlockStore, BeaconStateStore},
|
||||
MemoryDB,
|
||||
@ -14,7 +15,10 @@ use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::iter::FromIterator;
|
||||
use std::sync::Arc;
|
||||
use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair, Validator};
|
||||
use types::{
|
||||
BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, FreeAttestation, Hash256,
|
||||
Keypair, Slot,
|
||||
};
|
||||
|
||||
/// The beacon chain harness simulates a single beacon node with `validator_count` validators connected
|
||||
/// to it. Each validator is provided a borrow to the beacon chain, where it may read
|
||||
@ -27,7 +31,7 @@ pub struct BeaconChainHarness {
|
||||
pub beacon_chain: Arc<BeaconChain<MemoryDB, TestingSlotClock, OptimisedLMDGhost<MemoryDB>>>,
|
||||
pub block_store: Arc<BeaconBlockStore<MemoryDB>>,
|
||||
pub state_store: Arc<BeaconStateStore<MemoryDB>>,
|
||||
pub validators: Vec<TestValidator>,
|
||||
pub validators: Vec<ValidatorHarness>,
|
||||
pub spec: Arc<ChainSpec>,
|
||||
}
|
||||
|
||||
@ -36,16 +40,17 @@ impl BeaconChainHarness {
|
||||
///
|
||||
/// - A keypair, `BlockProducer` and `Attester` for each validator.
|
||||
/// - A new BeaconChain struct where the given validators are in the genesis.
|
||||
pub fn new(mut spec: ChainSpec, validator_count: usize) -> Self {
|
||||
pub fn new(spec: ChainSpec, validator_count: usize) -> Self {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
|
||||
let state_store = Arc::new(BeaconStateStore::new(db.clone()));
|
||||
let slot_clock = TestingSlotClock::new(spec.genesis_slot);
|
||||
let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past).
|
||||
let slot_clock = TestingSlotClock::new(spec.genesis_slot.as_u64());
|
||||
let fork_choice = OptimisedLMDGhost::new(block_store.clone(), state_store.clone());
|
||||
|
||||
// Remove the validators present in the spec (if any).
|
||||
spec.initial_validators = Vec::with_capacity(validator_count);
|
||||
spec.initial_balances = Vec::with_capacity(validator_count);
|
||||
let latest_eth1_data = Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
};
|
||||
|
||||
debug!("Generating validator keypairs...");
|
||||
|
||||
@ -55,25 +60,25 @@ impl BeaconChainHarness {
|
||||
.map(|_| Keypair::random())
|
||||
.collect();
|
||||
|
||||
debug!("Creating validator records...");
|
||||
debug!("Creating validator deposits...");
|
||||
|
||||
spec.initial_validators = keypairs
|
||||
let initial_validator_deposits = keypairs
|
||||
.par_iter()
|
||||
.map(|keypair| Validator {
|
||||
pubkey: keypair.pk.clone(),
|
||||
activation_slot: 0,
|
||||
..std::default::Default::default()
|
||||
.map(|keypair| Deposit {
|
||||
branch: vec![], // branch verification is not specified.
|
||||
index: 0, // index verification is not specified.
|
||||
deposit_data: DepositData {
|
||||
amount: 32_000_000_000, // 32 ETH (in Gwei)
|
||||
timestamp: genesis_time - 1,
|
||||
deposit_input: DepositInput {
|
||||
pubkey: keypair.pk.clone(),
|
||||
withdrawal_credentials: Hash256::zero(), // Withdrawal not possible.
|
||||
proof_of_possession: create_proof_of_possession(&keypair),
|
||||
},
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug!("Setting validator balances...");
|
||||
|
||||
spec.initial_balances = spec
|
||||
.initial_validators
|
||||
.par_iter()
|
||||
.map(|_| 32_000_000_000) // 32 ETH
|
||||
.collect();
|
||||
|
||||
debug!("Creating the BeaconChain...");
|
||||
|
||||
// Create the Beacon Chain
|
||||
@ -82,6 +87,9 @@ impl BeaconChainHarness {
|
||||
state_store.clone(),
|
||||
block_store.clone(),
|
||||
slot_clock,
|
||||
genesis_time,
|
||||
latest_eth1_data,
|
||||
initial_validator_deposits,
|
||||
spec.clone(),
|
||||
fork_choice,
|
||||
)
|
||||
@ -93,12 +101,14 @@ impl BeaconChainHarness {
|
||||
debug!("Creating validator producer and attester instances...");
|
||||
|
||||
// Spawn the test validator instances.
|
||||
let validators: Vec<TestValidator> = keypairs
|
||||
let validators: Vec<ValidatorHarness> = keypairs
|
||||
.iter()
|
||||
.map(|keypair| TestValidator::new(keypair.clone(), beacon_chain.clone(), spec.clone()))
|
||||
.map(|keypair| {
|
||||
ValidatorHarness::new(keypair.clone(), beacon_chain.clone(), spec.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug!("Created {} TestValidators", validators.len());
|
||||
debug!("Created {} ValidatorHarnesss", validators.len());
|
||||
|
||||
Self {
|
||||
db,
|
||||
@ -115,12 +125,12 @@ impl BeaconChainHarness {
|
||||
/// This is the equivalent of advancing a system clock forward one `SLOT_DURATION`.
|
||||
///
|
||||
/// Returns the new slot.
|
||||
pub fn increment_beacon_chain_slot(&mut self) -> u64 {
|
||||
pub fn increment_beacon_chain_slot(&mut self) -> Slot {
|
||||
let slot = self.beacon_chain.present_slot() + 1;
|
||||
|
||||
debug!("Incrementing BeaconChain slot to {}.", slot);
|
||||
|
||||
self.beacon_chain.slot_clock.set_slot(slot);
|
||||
self.beacon_chain.slot_clock.set_slot(slot.as_u64());
|
||||
self.beacon_chain.advance_state(slot).unwrap();
|
||||
slot
|
||||
}
|
||||
@ -136,7 +146,7 @@ impl BeaconChainHarness {
|
||||
.beacon_chain
|
||||
.state
|
||||
.read()
|
||||
.get_crosslink_committees_at_slot(present_slot, &self.spec)
|
||||
.get_crosslink_committees_at_slot(present_slot, false, &self.spec)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.fold(vec![], |mut acc, (committee, _slot)| {
|
||||
@ -226,7 +236,7 @@ impl BeaconChainHarness {
|
||||
}
|
||||
|
||||
/// Write the output of `chain_dump` to a JSON file.
|
||||
pub fn dump_to_file(&self, filename: String, chain_dump: &Vec<CheckPoint>) {
|
||||
pub fn dump_to_file(&self, filename: String, chain_dump: &[CheckPoint]) {
|
||||
let json = serde_json::to_string(chain_dump).unwrap();
|
||||
let mut file = File::create(filename).unwrap();
|
||||
file.write_all(json.as_bytes())
|
@ -1,5 +1,5 @@
|
||||
mod harness;
|
||||
mod validator;
|
||||
mod beacon_chain_harness;
|
||||
mod validator_harness;
|
||||
|
||||
pub use self::harness::BeaconChainHarness;
|
||||
pub use self::validator::TestValidator;
|
||||
pub use self::beacon_chain_harness::BeaconChainHarness;
|
||||
pub use self::validator_harness::ValidatorHarness;
|
||||
|
@ -1,6 +0,0 @@
|
||||
mod direct_beacon_node;
|
||||
mod direct_duties;
|
||||
mod signer;
|
||||
mod validator;
|
||||
|
||||
pub use self::validator::TestValidator;
|
@ -12,7 +12,7 @@ use fork_choice::ForkChoice;
|
||||
use parking_lot::RwLock;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::{AttestationData, BeaconBlock, FreeAttestation, PublicKey, Signature};
|
||||
use types::{AttestationData, BeaconBlock, FreeAttestation, Signature, Slot};
|
||||
|
||||
// mod attester;
|
||||
// mod producer;
|
||||
@ -52,7 +52,7 @@ impl<T: ClientDB, U: SlotClock, F: ForkChoice> DirectBeaconNode<T, U, F> {
|
||||
impl<T: ClientDB, U: SlotClock, F: ForkChoice> AttesterBeaconNode for DirectBeaconNode<T, U, F> {
|
||||
fn produce_attestation_data(
|
||||
&self,
|
||||
_slot: u64,
|
||||
_slot: Slot,
|
||||
shard: u64,
|
||||
) -> Result<Option<AttestationData>, NodeError> {
|
||||
match self.beacon_chain.produce_attestation_data(shard) {
|
||||
@ -71,31 +71,17 @@ impl<T: ClientDB, U: SlotClock, F: ForkChoice> AttesterBeaconNode for DirectBeac
|
||||
}
|
||||
|
||||
impl<T: ClientDB, U: SlotClock, F: ForkChoice> BeaconBlockNode for DirectBeaconNode<T, U, F> {
|
||||
/// Requests the `proposer_nonce` from the `BeaconChain`.
|
||||
fn proposer_nonce(&self, pubkey: &PublicKey) -> Result<u64, BeaconBlockNodeError> {
|
||||
let validator_index = self
|
||||
.beacon_chain
|
||||
.validator_index(pubkey)
|
||||
.ok_or_else(|| BeaconBlockNodeError::RemoteFailure("pubkey unknown.".to_string()))?;
|
||||
|
||||
self.beacon_chain
|
||||
.proposer_slots(validator_index)
|
||||
.ok_or_else(|| {
|
||||
BeaconBlockNodeError::RemoteFailure("validator_index unknown.".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// Requests a new `BeaconBlock from the `BeaconChain`.
|
||||
fn produce_beacon_block(
|
||||
&self,
|
||||
slot: u64,
|
||||
slot: Slot,
|
||||
randao_reveal: &Signature,
|
||||
) -> Result<Option<BeaconBlock>, BeaconBlockNodeError> {
|
||||
let (block, _state) = self
|
||||
.beacon_chain
|
||||
.produce_block(randao_reveal.clone())
|
||||
.ok_or_else(|| {
|
||||
BeaconBlockNodeError::RemoteFailure(format!("Did not produce block."))
|
||||
BeaconBlockNodeError::RemoteFailure("Did not produce block.".to_string())
|
||||
})?;
|
||||
|
||||
if block.slot == slot {
|
@ -9,7 +9,7 @@ use db::ClientDB;
|
||||
use fork_choice::ForkChoice;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::PublicKey;
|
||||
use types::{PublicKey, Slot};
|
||||
|
||||
/// Connects directly to a borrowed `BeaconChain` and reads attester/proposer duties directly from
|
||||
/// it.
|
||||
@ -28,7 +28,7 @@ impl<T: ClientDB, U: SlotClock, F: ForkChoice> DirectDuties<T, U, F> {
|
||||
}
|
||||
|
||||
impl<T: ClientDB, U: SlotClock, F: ForkChoice> ProducerDutiesReader for DirectDuties<T, U, F> {
|
||||
fn is_block_production_slot(&self, slot: u64) -> Result<bool, ProducerDutiesReaderError> {
|
||||
fn is_block_production_slot(&self, slot: Slot) -> Result<bool, ProducerDutiesReaderError> {
|
||||
let validator_index = self
|
||||
.beacon_chain
|
||||
.validator_index(&self.pubkey)
|
||||
@ -50,7 +50,7 @@ impl<T: ClientDB, U: SlotClock, F: ForkChoice> AttesterDutiesReader for DirectDu
|
||||
}
|
||||
}
|
||||
|
||||
fn attestation_shard(&self, slot: u64) -> Result<Option<u64>, AttesterDutiesReaderError> {
|
||||
fn attestation_shard(&self, slot: Slot) -> Result<Option<u64>, AttesterDutiesReaderError> {
|
||||
if let Some(validator_index) = self.validator_index() {
|
||||
match self
|
||||
.beacon_chain
|
||||
@ -61,7 +61,7 @@ impl<T: ClientDB, U: SlotClock, F: ForkChoice> AttesterDutiesReader for DirectDu
|
||||
}
|
||||
Ok(Some(_)) => Ok(None),
|
||||
Ok(None) => Err(AttesterDutiesReaderError::UnknownEpoch),
|
||||
Err(_) => panic!("Error when getting validator attestation shard."),
|
||||
Err(_) => unreachable!("Error when getting validator attestation shard."),
|
||||
}
|
||||
} else {
|
||||
Err(AttesterDutiesReaderError::UnknownValidator)
|
@ -4,12 +4,12 @@ use std::sync::RwLock;
|
||||
use types::{Keypair, Signature};
|
||||
|
||||
/// A test-only struct used to perform signing for a proposer or attester.
|
||||
pub struct TestSigner {
|
||||
pub struct LocalSigner {
|
||||
keypair: Keypair,
|
||||
should_sign: RwLock<bool>,
|
||||
}
|
||||
|
||||
impl TestSigner {
|
||||
impl LocalSigner {
|
||||
/// Produce a new TestSigner with signing enabled by default.
|
||||
pub fn new(keypair: Keypair) -> Self {
|
||||
Self {
|
||||
@ -30,7 +30,7 @@ impl TestSigner {
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockProposerSigner for TestSigner {
|
||||
impl BlockProposerSigner for LocalSigner {
|
||||
fn sign_block_proposal(&self, message: &[u8]) -> Option<Signature> {
|
||||
self.bls_sign(message)
|
||||
}
|
||||
@ -40,7 +40,7 @@ impl BlockProposerSigner for TestSigner {
|
||||
}
|
||||
}
|
||||
|
||||
impl AttesterSigner for TestSigner {
|
||||
impl AttesterSigner for LocalSigner {
|
||||
fn sign_attestation_message(&self, message: &[u8]) -> Option<Signature> {
|
||||
self.bls_sign(message)
|
||||
}
|
@ -1,16 +1,20 @@
|
||||
use super::direct_beacon_node::DirectBeaconNode;
|
||||
use super::direct_duties::DirectDuties;
|
||||
use super::signer::TestSigner;
|
||||
mod direct_beacon_node;
|
||||
mod direct_duties;
|
||||
mod local_signer;
|
||||
|
||||
use attester::PollOutcome as AttestationPollOutcome;
|
||||
use attester::{Attester, Error as AttestationPollError};
|
||||
use beacon_chain::BeaconChain;
|
||||
use block_producer::PollOutcome as BlockPollOutcome;
|
||||
use block_producer::{BlockProducer, Error as BlockPollError};
|
||||
use db::MemoryDB;
|
||||
use direct_beacon_node::DirectBeaconNode;
|
||||
use direct_duties::DirectDuties;
|
||||
use fork_choice::{optimised_lmd_ghost::OptimisedLMDGhost, slow_lmd_ghost::SlowLMDGhost};
|
||||
use local_signer::LocalSigner;
|
||||
use slot_clock::TestingSlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair};
|
||||
use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair, Slot};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockProduceError {
|
||||
@ -29,29 +33,29 @@ pub enum AttestationProduceError {
|
||||
/// The test validator connects directly to a borrowed `BeaconChain` struct. It is useful for
|
||||
/// testing that the core proposer and attester logic is functioning. Also for supporting beacon
|
||||
/// chain tests.
|
||||
pub struct TestValidator {
|
||||
pub struct ValidatorHarness {
|
||||
pub block_producer: BlockProducer<
|
||||
TestingSlotClock,
|
||||
DirectBeaconNode<MemoryDB, TestingSlotClock, OptimisedLMDGhost<MemoryDB>>,
|
||||
DirectDuties<MemoryDB, TestingSlotClock, OptimisedLMDGhost<MemoryDB>>,
|
||||
TestSigner,
|
||||
LocalSigner,
|
||||
>,
|
||||
pub attester: Attester<
|
||||
TestingSlotClock,
|
||||
DirectBeaconNode<MemoryDB, TestingSlotClock, OptimisedLMDGhost<MemoryDB>>,
|
||||
DirectDuties<MemoryDB, TestingSlotClock, OptimisedLMDGhost<MemoryDB>>,
|
||||
TestSigner,
|
||||
LocalSigner,
|
||||
>,
|
||||
pub spec: Arc<ChainSpec>,
|
||||
pub epoch_map: Arc<DirectDuties<MemoryDB, TestingSlotClock, OptimisedLMDGhost<MemoryDB>>>,
|
||||
pub keypair: Keypair,
|
||||
pub beacon_node: Arc<DirectBeaconNode<MemoryDB, TestingSlotClock, OptimisedLMDGhost<MemoryDB>>>,
|
||||
pub slot_clock: Arc<TestingSlotClock>,
|
||||
pub signer: Arc<TestSigner>,
|
||||
pub signer: Arc<LocalSigner>,
|
||||
}
|
||||
|
||||
impl TestValidator {
|
||||
/// Create a new TestValidator that signs with the given keypair, operates per the given spec and connects to the
|
||||
impl ValidatorHarness {
|
||||
/// Create a new ValidatorHarness that signs with the given keypair, operates per the given spec and connects to the
|
||||
/// supplied beacon node.
|
||||
///
|
||||
/// A `BlockProducer` and `Attester` is created..
|
||||
@ -60,14 +64,13 @@ impl TestValidator {
|
||||
beacon_chain: Arc<BeaconChain<MemoryDB, TestingSlotClock, OptimisedLMDGhost<MemoryDB>>>,
|
||||
spec: Arc<ChainSpec>,
|
||||
) -> Self {
|
||||
let slot_clock = Arc::new(TestingSlotClock::new(spec.genesis_slot));
|
||||
let signer = Arc::new(TestSigner::new(keypair.clone()));
|
||||
let slot_clock = Arc::new(TestingSlotClock::new(spec.genesis_slot.as_u64()));
|
||||
let signer = Arc::new(LocalSigner::new(keypair.clone()));
|
||||
let beacon_node = Arc::new(DirectBeaconNode::new(beacon_chain.clone()));
|
||||
let epoch_map = Arc::new(DirectDuties::new(keypair.pk.clone(), beacon_chain.clone()));
|
||||
|
||||
let block_producer = BlockProducer::new(
|
||||
spec.clone(),
|
||||
keypair.pk.clone(),
|
||||
epoch_map.clone(),
|
||||
slot_clock.clone(),
|
||||
beacon_node.clone(),
|
||||
@ -128,7 +131,7 @@ impl TestValidator {
|
||||
/// Set the validators slot clock to the specified slot.
|
||||
///
|
||||
/// The validators slot clock will always read this value until it is set to something else.
|
||||
pub fn set_slot(&mut self, slot: u64) {
|
||||
self.slot_clock.set_slot(slot)
|
||||
pub fn set_slot(&mut self, slot: Slot) {
|
||||
self.slot_clock.set_slot(slot.as_u64())
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
use env_logger::{Builder, Env};
|
||||
use log::debug;
|
||||
use test_harness::BeaconChainHarness;
|
||||
use types::ChainSpec;
|
||||
use types::{ChainSpec, Slot};
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn it_can_build_on_genesis_block() {
|
||||
let mut spec = ChainSpec::foundation();
|
||||
spec.genesis_slot = spec.epoch_length * 8;
|
||||
spec.genesis_slot = Slot::new(spec.epoch_length * 8);
|
||||
|
||||
/*
|
||||
spec.shard_count = spec.shard_count / 8;
|
||||
|
@ -2,7 +2,7 @@ use super::BLOCKS_DB_COLUMN as DB_COLUMN;
|
||||
use super::{ClientDB, DBError};
|
||||
use ssz::Decodable;
|
||||
use std::sync::Arc;
|
||||
use types::{readers::BeaconBlockReader, BeaconBlock, Hash256};
|
||||
use types::{readers::BeaconBlockReader, BeaconBlock, Hash256, Slot};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum BeaconBlockAtSlotError {
|
||||
@ -71,7 +71,7 @@ impl<T: ClientDB> BeaconBlockStore<T> {
|
||||
pub fn block_at_slot(
|
||||
&self,
|
||||
head_hash: &Hash256,
|
||||
slot: u64,
|
||||
slot: Slot,
|
||||
) -> Result<Option<(Hash256, impl BeaconBlockReader)>, BeaconBlockAtSlotError> {
|
||||
let mut current_hash = *head_hash;
|
||||
|
||||
@ -119,12 +119,12 @@ mod tests {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let mut block = BeaconBlock::random_for_test(&mut rng);
|
||||
block.slot = 10;
|
||||
block.slot = Slot::from(10_u64);
|
||||
|
||||
let block_root = block.canonical_root();
|
||||
bs.put(&block_root, &ssz_encode(&block)).unwrap();
|
||||
|
||||
let result = bs.block_at_slot(&block_root, 11).unwrap();
|
||||
let result = bs.block_at_slot(&block_root, Slot::from(11_u64)).unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ mod tests {
|
||||
|
||||
db.put(DB_COLUMN, hash, ssz).unwrap();
|
||||
assert_eq!(
|
||||
store.block_at_slot(hash, 42),
|
||||
store.block_at_slot(hash, Slot::from(42_u64)),
|
||||
Err(BeaconBlockAtSlotError::DBError(
|
||||
"Bad BeaconBlock SSZ.".into()
|
||||
))
|
||||
@ -156,7 +156,7 @@ mod tests {
|
||||
|
||||
db.put(DB_COLUMN, hash, ssz).unwrap();
|
||||
assert_eq!(
|
||||
store.block_at_slot(other_hash, 42),
|
||||
store.block_at_slot(other_hash, Slot::from(42_u64)),
|
||||
Err(BeaconBlockAtSlotError::UnknownBeaconBlock(*other_hash))
|
||||
);
|
||||
}
|
||||
@ -221,7 +221,7 @@ mod tests {
|
||||
Hash256::from(&[2; 32][..]),
|
||||
Hash256::from(&[3; 32][..]),
|
||||
];
|
||||
let slots = [0, 1, 3, 4, 5];
|
||||
let slots: Vec<Slot> = vec![0, 1, 3, 4, 5].iter().map(|x| Slot::new(*x)).collect();
|
||||
|
||||
// Generate a vec of random blocks and store them in the DB.
|
||||
let block_count = 5;
|
||||
@ -249,14 +249,14 @@ mod tests {
|
||||
assert_eq!(reader.slot(), slots[slot_index]);
|
||||
}
|
||||
|
||||
let ssz = bs.block_at_slot(&hashes[4], 2).unwrap();
|
||||
let ssz = bs.block_at_slot(&hashes[4], Slot::new(2)).unwrap();
|
||||
assert_eq!(ssz, None);
|
||||
|
||||
let ssz = bs.block_at_slot(&hashes[4], 6).unwrap();
|
||||
let ssz = bs.block_at_slot(&hashes[4], Slot::new(6)).unwrap();
|
||||
assert_eq!(ssz, None);
|
||||
|
||||
let bad_hash = &Hash256::from("unknown".as_bytes());
|
||||
let ssz = bs.block_at_slot(bad_hash, 2);
|
||||
let ssz = bs.block_at_slot(bad_hash, Slot::new(2));
|
||||
assert_eq!(
|
||||
ssz,
|
||||
Err(BeaconBlockAtSlotError::UnknownBeaconBlock(*bad_hash))
|
||||
|
@ -8,6 +8,7 @@ use std::path::PathBuf;
|
||||
use crate::config::LighthouseConfig;
|
||||
use crate::rpc::start_server;
|
||||
use beacon_chain::BeaconChain;
|
||||
use bls::create_proof_of_possession;
|
||||
use clap::{App, Arg};
|
||||
use db::{
|
||||
stores::{BeaconBlockStore, BeaconStateStore},
|
||||
@ -17,7 +18,7 @@ use fork_choice::optimised_lmd_ghost::OptimisedLMDGhost;
|
||||
use slog::{error, info, o, Drain};
|
||||
use slot_clock::SystemTimeSlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::ChainSpec;
|
||||
use types::{ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, Hash256, Keypair};
|
||||
|
||||
fn main() {
|
||||
let decorator = slog_term::TermDecorator::new().build();
|
||||
@ -76,17 +77,51 @@ fn main() {
|
||||
let state_store = Arc::new(BeaconStateStore::new(db.clone()));
|
||||
|
||||
// Slot clock
|
||||
let slot_clock = SystemTimeSlotClock::new(spec.genesis_time, spec.slot_duration)
|
||||
let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past).
|
||||
let slot_clock = SystemTimeSlotClock::new(genesis_time, spec.slot_duration)
|
||||
.expect("Unable to load SystemTimeSlotClock");
|
||||
|
||||
// Choose the fork choice
|
||||
let fork_choice = OptimisedLMDGhost::new(block_store.clone(), state_store.clone());
|
||||
|
||||
/*
|
||||
* Generate some random data to start a chain with.
|
||||
*
|
||||
* This is will need to be replace for production usage.
|
||||
*/
|
||||
let latest_eth1_data = Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
};
|
||||
let keypairs: Vec<Keypair> = (0..10)
|
||||
.collect::<Vec<usize>>()
|
||||
.iter()
|
||||
.map(|_| Keypair::random())
|
||||
.collect();
|
||||
let initial_validator_deposits = keypairs
|
||||
.iter()
|
||||
.map(|keypair| Deposit {
|
||||
branch: vec![], // branch verification is not specified.
|
||||
index: 0, // index verification is not specified.
|
||||
deposit_data: DepositData {
|
||||
amount: 32_000_000_000, // 32 ETH (in Gwei)
|
||||
timestamp: genesis_time - 1,
|
||||
deposit_input: DepositInput {
|
||||
pubkey: keypair.pk.clone(),
|
||||
withdrawal_credentials: Hash256::zero(), // Withdrawal not possible.
|
||||
proof_of_possession: create_proof_of_possession(&keypair),
|
||||
},
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Genesis chain
|
||||
// TODO: persist chain to storage.
|
||||
let _chain_result = BeaconChain::genesis(
|
||||
state_store.clone(),
|
||||
block_store.clone(),
|
||||
slot_clock,
|
||||
genesis_time,
|
||||
latest_eth1_data,
|
||||
initial_validator_deposits,
|
||||
spec,
|
||||
fork_choice,
|
||||
);
|
||||
|
@ -25,7 +25,7 @@ impl BeaconBlockService for BeaconBlockServiceInstance {
|
||||
// TODO: build a legit block.
|
||||
let mut block = BeaconBlockProto::new();
|
||||
block.set_slot(req.get_slot());
|
||||
block.set_block_root("cats".as_bytes().to_vec());
|
||||
block.set_block_root(b"cats".to_vec());
|
||||
|
||||
let mut resp = ProduceBeaconBlockResponse::new();
|
||||
resp.set_block(block);
|
||||
|
@ -3,7 +3,7 @@ mod traits;
|
||||
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::{AttestationData, FreeAttestation, Signature};
|
||||
use types::{AttestationData, FreeAttestation, Signature, Slot};
|
||||
|
||||
pub use self::traits::{
|
||||
BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer,
|
||||
@ -13,14 +13,14 @@ const PHASE_0_CUSTODY_BIT: bool = false;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PollOutcome {
|
||||
AttestationProduced(u64),
|
||||
AttestationNotRequired(u64),
|
||||
SlashableAttestationNotProduced(u64),
|
||||
BeaconNodeUnableToProduceAttestation(u64),
|
||||
ProducerDutiesUnknown(u64),
|
||||
SlotAlreadyProcessed(u64),
|
||||
SignerRejection(u64),
|
||||
ValidatorIsUnknown(u64),
|
||||
AttestationProduced(Slot),
|
||||
AttestationNotRequired(Slot),
|
||||
SlashableAttestationNotProduced(Slot),
|
||||
BeaconNodeUnableToProduceAttestation(Slot),
|
||||
ProducerDutiesUnknown(Slot),
|
||||
SlotAlreadyProcessed(Slot),
|
||||
SignerRejection(Slot),
|
||||
ValidatorIsUnknown(Slot),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -40,7 +40,7 @@ pub enum Error {
|
||||
///
|
||||
/// Relies upon an external service to keep the `EpochDutiesMap` updated.
|
||||
pub struct Attester<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> {
|
||||
pub last_processed_slot: Option<u64>,
|
||||
pub last_processed_slot: Option<Slot>,
|
||||
duties: Arc<V>,
|
||||
slot_clock: Arc<T>,
|
||||
beacon_node: Arc<U>,
|
||||
@ -91,7 +91,7 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> Attester<T, U, V,
|
||||
}
|
||||
}
|
||||
|
||||
fn produce_attestation(&mut self, slot: u64, shard: u64) -> Result<PollOutcome, Error> {
|
||||
fn produce_attestation(&mut self, slot: Slot, shard: u64) -> Result<PollOutcome, Error> {
|
||||
let attestation_data = match self.beacon_node.produce_attestation_data(slot, shard)? {
|
||||
Some(attestation_data) => attestation_data,
|
||||
None => return Ok(PollOutcome::BeaconNodeUnableToProduceAttestation(slot)),
|
||||
@ -122,7 +122,7 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> Attester<T, U, V,
|
||||
Ok(PollOutcome::AttestationProduced(slot))
|
||||
}
|
||||
|
||||
fn is_processed_slot(&self, slot: u64) -> bool {
|
||||
fn is_processed_slot(&self, slot: Slot) -> bool {
|
||||
match self.last_processed_slot {
|
||||
Some(processed_slot) if slot <= processed_slot => true,
|
||||
_ => false,
|
||||
@ -170,7 +170,7 @@ impl From<BeaconNodeError> for Error {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::test_utils::{TestBeaconNode, TestEpochMap, TestSigner};
|
||||
use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode};
|
||||
use super::*;
|
||||
use slot_clock::TestingSlotClock;
|
||||
use types::{
|
||||
@ -189,11 +189,11 @@ mod tests {
|
||||
|
||||
let spec = Arc::new(ChainSpec::foundation());
|
||||
let slot_clock = Arc::new(TestingSlotClock::new(0));
|
||||
let beacon_node = Arc::new(TestBeaconNode::default());
|
||||
let signer = Arc::new(TestSigner::new(Keypair::random()));
|
||||
let beacon_node = Arc::new(SimulatedBeaconNode::default());
|
||||
let signer = Arc::new(LocalSigner::new(Keypair::random()));
|
||||
|
||||
let mut duties = TestEpochMap::new(spec.epoch_length);
|
||||
let attest_slot = 100;
|
||||
let mut duties = EpochMap::new(spec.epoch_length);
|
||||
let attest_slot = Slot::new(100);
|
||||
let attest_epoch = attest_slot / spec.epoch_length;
|
||||
let attest_shard = 12;
|
||||
duties.insert_attestation_shard(attest_slot, attest_shard);
|
||||
@ -212,28 +212,28 @@ mod tests {
|
||||
beacon_node.set_next_publish_result(Ok(PublishOutcome::ValidAttestation));
|
||||
|
||||
// One slot before attestation slot...
|
||||
slot_clock.set_slot(attest_slot - 1);
|
||||
slot_clock.set_slot(attest_slot.as_u64() - 1);
|
||||
assert_eq!(
|
||||
attester.poll(),
|
||||
Ok(PollOutcome::AttestationNotRequired(attest_slot - 1))
|
||||
);
|
||||
|
||||
// On the attest slot...
|
||||
slot_clock.set_slot(attest_slot);
|
||||
slot_clock.set_slot(attest_slot.as_u64());
|
||||
assert_eq!(
|
||||
attester.poll(),
|
||||
Ok(PollOutcome::AttestationProduced(attest_slot))
|
||||
);
|
||||
|
||||
// Trying the same attest slot again...
|
||||
slot_clock.set_slot(attest_slot);
|
||||
slot_clock.set_slot(attest_slot.as_u64());
|
||||
assert_eq!(
|
||||
attester.poll(),
|
||||
Ok(PollOutcome::SlotAlreadyProcessed(attest_slot))
|
||||
);
|
||||
|
||||
// One slot after the attest slot...
|
||||
slot_clock.set_slot(attest_slot + 1);
|
||||
slot_clock.set_slot(attest_slot.as_u64() + 1);
|
||||
assert_eq!(
|
||||
attester.poll(),
|
||||
Ok(PollOutcome::AttestationNotRequired(attest_slot + 1))
|
||||
@ -241,7 +241,7 @@ mod tests {
|
||||
|
||||
// In an epoch without known duties...
|
||||
let slot = (attest_epoch + 1) * spec.epoch_length;
|
||||
slot_clock.set_slot(slot);
|
||||
slot_clock.set_slot(slot.into());
|
||||
assert_eq!(
|
||||
attester.poll(),
|
||||
Ok(PollOutcome::ProducerDutiesUnknown(slot))
|
||||
|
@ -1,13 +1,14 @@
|
||||
use crate::{DutiesReader, DutiesReaderError};
|
||||
use std::collections::HashMap;
|
||||
use types::{Epoch, Slot};
|
||||
|
||||
pub struct TestEpochMap {
|
||||
pub struct EpochMap {
|
||||
epoch_length: u64,
|
||||
validator_index: Option<u64>,
|
||||
map: HashMap<u64, (u64, u64)>,
|
||||
map: HashMap<Epoch, (Slot, u64)>,
|
||||
}
|
||||
|
||||
impl TestEpochMap {
|
||||
impl EpochMap {
|
||||
pub fn new(epoch_length: u64) -> Self {
|
||||
Self {
|
||||
epoch_length,
|
||||
@ -16,9 +17,8 @@ impl TestEpochMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_attestation_shard(&mut self, slot: u64, shard: u64) {
|
||||
let epoch = slot / self.epoch_length;
|
||||
|
||||
pub fn insert_attestation_shard(&mut self, slot: Slot, shard: u64) {
|
||||
let epoch = slot.epoch(self.epoch_length);
|
||||
self.map.insert(epoch, (slot, shard));
|
||||
}
|
||||
|
||||
@ -27,9 +27,9 @@ impl TestEpochMap {
|
||||
}
|
||||
}
|
||||
|
||||
impl DutiesReader for TestEpochMap {
|
||||
fn attestation_shard(&self, slot: u64) -> Result<Option<u64>, DutiesReaderError> {
|
||||
let epoch = slot / self.epoch_length;
|
||||
impl DutiesReader for EpochMap {
|
||||
fn attestation_shard(&self, slot: Slot) -> Result<Option<u64>, DutiesReaderError> {
|
||||
let epoch = slot.epoch(self.epoch_length);
|
||||
|
||||
match self.map.get(&epoch) {
|
||||
Some((attest_slot, attest_shard)) if *attest_slot == slot => Ok(Some(*attest_shard)),
|
||||
|
@ -3,13 +3,13 @@ use std::sync::RwLock;
|
||||
use types::{Keypair, Signature};
|
||||
|
||||
/// A test-only struct used to simulate a Beacon Node.
|
||||
pub struct TestSigner {
|
||||
pub struct LocalSigner {
|
||||
keypair: Keypair,
|
||||
should_sign: RwLock<bool>,
|
||||
}
|
||||
|
||||
impl TestSigner {
|
||||
/// Produce a new TestSigner with signing enabled by default.
|
||||
impl LocalSigner {
|
||||
/// Produce a new LocalSigner with signing enabled by default.
|
||||
pub fn new(keypair: Keypair) -> Self {
|
||||
Self {
|
||||
keypair,
|
||||
@ -24,7 +24,7 @@ impl TestSigner {
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer for TestSigner {
|
||||
impl Signer for LocalSigner {
|
||||
fn sign_attestation_message(&self, message: &[u8]) -> Option<Signature> {
|
||||
Some(Signature::new(message, &self.keypair.sk))
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
mod beacon_node;
|
||||
mod epoch_map;
|
||||
mod signer;
|
||||
mod local_signer;
|
||||
mod simulated_beacon_node;
|
||||
|
||||
pub use self::beacon_node::TestBeaconNode;
|
||||
pub use self::epoch_map::TestEpochMap;
|
||||
pub use self::signer::TestSigner;
|
||||
pub use self::epoch_map::EpochMap;
|
||||
pub use self::local_signer::LocalSigner;
|
||||
pub use self::simulated_beacon_node::SimulatedBeaconNode;
|
||||
|
@ -1,21 +1,21 @@
|
||||
use crate::traits::{BeaconNode, BeaconNodeError, PublishOutcome};
|
||||
use std::sync::RwLock;
|
||||
use types::{AttestationData, FreeAttestation};
|
||||
use types::{AttestationData, FreeAttestation, Slot};
|
||||
|
||||
type ProduceResult = Result<Option<AttestationData>, BeaconNodeError>;
|
||||
type PublishResult = Result<PublishOutcome, BeaconNodeError>;
|
||||
|
||||
/// A test-only struct used to simulate a Beacon Node.
|
||||
#[derive(Default)]
|
||||
pub struct TestBeaconNode {
|
||||
pub produce_input: RwLock<Option<(u64, u64)>>,
|
||||
pub struct SimulatedBeaconNode {
|
||||
pub produce_input: RwLock<Option<(Slot, u64)>>,
|
||||
pub produce_result: RwLock<Option<ProduceResult>>,
|
||||
|
||||
pub publish_input: RwLock<Option<FreeAttestation>>,
|
||||
pub publish_result: RwLock<Option<PublishResult>>,
|
||||
}
|
||||
|
||||
impl TestBeaconNode {
|
||||
impl SimulatedBeaconNode {
|
||||
pub fn set_next_produce_result(&self, result: ProduceResult) {
|
||||
*self.produce_result.write().unwrap() = Some(result);
|
||||
}
|
||||
@ -25,8 +25,8 @@ impl TestBeaconNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl BeaconNode for TestBeaconNode {
|
||||
fn produce_attestation_data(&self, slot: u64, shard: u64) -> ProduceResult {
|
||||
impl BeaconNode for SimulatedBeaconNode {
|
||||
fn produce_attestation_data(&self, slot: Slot, shard: u64) -> ProduceResult {
|
||||
*self.produce_input.write().unwrap() = Some((slot, shard));
|
||||
match *self.produce_result.read().unwrap() {
|
||||
Some(ref r) => r.clone(),
|
@ -1,4 +1,4 @@
|
||||
use types::{AttestationData, FreeAttestation, Signature};
|
||||
use types::{AttestationData, FreeAttestation, Signature, Slot};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BeaconNodeError {
|
||||
@ -16,7 +16,7 @@ pub enum PublishOutcome {
|
||||
pub trait BeaconNode: Send + Sync {
|
||||
fn produce_attestation_data(
|
||||
&self,
|
||||
slot: u64,
|
||||
slot: Slot,
|
||||
shard: u64,
|
||||
) -> Result<Option<AttestationData>, BeaconNodeError>;
|
||||
|
||||
@ -37,7 +37,7 @@ pub enum DutiesReaderError {
|
||||
/// Informs a validator of their duties (e.g., block production).
|
||||
pub trait DutiesReader: Send + Sync {
|
||||
/// Returns `Some(shard)` if this slot is an attestation slot. Otherwise, returns `None.`
|
||||
fn attestation_shard(&self, slot: u64) -> Result<Option<u64>, DutiesReaderError>;
|
||||
fn attestation_shard(&self, slot: Slot) -> Result<Option<u64>, DutiesReaderError>;
|
||||
|
||||
/// Returns `Some(shard)` if this slot is an attestation slot. Otherwise, returns `None.`
|
||||
fn validator_index(&self) -> Option<u64>;
|
||||
|
@ -4,7 +4,7 @@ mod traits;
|
||||
use slot_clock::SlotClock;
|
||||
use ssz::ssz_encode;
|
||||
use std::sync::Arc;
|
||||
use types::{BeaconBlock, ChainSpec, PublicKey};
|
||||
use types::{BeaconBlock, ChainSpec, Slot};
|
||||
|
||||
pub use self::traits::{
|
||||
BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer,
|
||||
@ -13,21 +13,21 @@ pub use self::traits::{
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PollOutcome {
|
||||
/// A new block was produced.
|
||||
BlockProduced(u64),
|
||||
BlockProduced(Slot),
|
||||
/// A block was not produced as it would have been slashable.
|
||||
SlashableBlockNotProduced(u64),
|
||||
SlashableBlockNotProduced(Slot),
|
||||
/// The validator duties did not require a block to be produced.
|
||||
BlockProductionNotRequired(u64),
|
||||
BlockProductionNotRequired(Slot),
|
||||
/// The duties for the present epoch were not found.
|
||||
ProducerDutiesUnknown(u64),
|
||||
ProducerDutiesUnknown(Slot),
|
||||
/// The slot has already been processed, execution was skipped.
|
||||
SlotAlreadyProcessed(u64),
|
||||
SlotAlreadyProcessed(Slot),
|
||||
/// The Beacon Node was unable to produce a block at that slot.
|
||||
BeaconNodeUnableToProduceBlock(u64),
|
||||
BeaconNodeUnableToProduceBlock(Slot),
|
||||
/// The signer failed to sign the message.
|
||||
SignerRejection(u64),
|
||||
SignerRejection(Slot),
|
||||
/// The public key for this validator is not an active validator.
|
||||
ValidatorIsUnknown(u64),
|
||||
ValidatorIsUnknown(Slot),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -47,8 +47,7 @@ pub enum Error {
|
||||
///
|
||||
/// Relies upon an external service to keep the `EpochDutiesMap` updated.
|
||||
pub struct BlockProducer<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> {
|
||||
pub last_processed_slot: Option<u64>,
|
||||
pubkey: PublicKey,
|
||||
pub last_processed_slot: Option<Slot>,
|
||||
spec: Arc<ChainSpec>,
|
||||
epoch_map: Arc<V>,
|
||||
slot_clock: Arc<T>,
|
||||
@ -60,7 +59,6 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
|
||||
/// Returns a new instance where `last_processed_slot == 0`.
|
||||
pub fn new(
|
||||
spec: Arc<ChainSpec>,
|
||||
pubkey: PublicKey,
|
||||
epoch_map: Arc<V>,
|
||||
slot_clock: Arc<T>,
|
||||
beacon_node: Arc<U>,
|
||||
@ -68,7 +66,6 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
|
||||
) -> Self {
|
||||
Self {
|
||||
last_processed_slot: None,
|
||||
pubkey,
|
||||
spec,
|
||||
epoch_map,
|
||||
slot_clock,
|
||||
@ -115,7 +112,7 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
|
||||
}
|
||||
}
|
||||
|
||||
fn is_processed_slot(&self, slot: u64) -> bool {
|
||||
fn is_processed_slot(&self, slot: Slot) -> bool {
|
||||
match self.last_processed_slot {
|
||||
Some(processed_slot) if processed_slot >= slot => true,
|
||||
_ => false,
|
||||
@ -132,12 +129,10 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
|
||||
///
|
||||
/// The slash-protection code is not yet implemented. There is zero protection against
|
||||
/// slashing.
|
||||
fn produce_block(&mut self, slot: u64) -> Result<PollOutcome, Error> {
|
||||
fn produce_block(&mut self, slot: Slot) -> Result<PollOutcome, Error> {
|
||||
let randao_reveal = {
|
||||
let producer_nonce = self.beacon_node.proposer_nonce(&self.pubkey)?;
|
||||
|
||||
// TODO: add domain, etc to this message.
|
||||
let message = ssz_encode(&producer_nonce);
|
||||
// TODO: add domain, etc to this message. Also ensure result matches `into_to_bytes32`.
|
||||
let message = ssz_encode(&slot.epoch(self.spec.epoch_length));
|
||||
|
||||
match self.signer.sign_randao_reveal(&message) {
|
||||
None => return Ok(PollOutcome::SignerRejection(slot)),
|
||||
@ -213,7 +208,7 @@ impl From<BeaconNodeError> for Error {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::test_utils::{TestBeaconNode, TestEpochMap, TestSigner};
|
||||
use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode};
|
||||
use super::*;
|
||||
use slot_clock::TestingSlotClock;
|
||||
use types::{
|
||||
@ -232,19 +227,17 @@ mod tests {
|
||||
|
||||
let spec = Arc::new(ChainSpec::foundation());
|
||||
let slot_clock = Arc::new(TestingSlotClock::new(0));
|
||||
let beacon_node = Arc::new(TestBeaconNode::default());
|
||||
let signer = Arc::new(TestSigner::new(Keypair::random()));
|
||||
let beacon_node = Arc::new(SimulatedBeaconNode::default());
|
||||
let signer = Arc::new(LocalSigner::new(Keypair::random()));
|
||||
|
||||
let mut epoch_map = TestEpochMap::new(spec.epoch_length);
|
||||
let produce_slot = 100;
|
||||
let produce_epoch = produce_slot / spec.epoch_length;
|
||||
let mut epoch_map = EpochMap::new(spec.epoch_length);
|
||||
let produce_slot = Slot::new(100);
|
||||
let produce_epoch = produce_slot.epoch(spec.epoch_length);
|
||||
epoch_map.map.insert(produce_epoch, produce_slot);
|
||||
let epoch_map = Arc::new(epoch_map);
|
||||
let keypair = Keypair::random();
|
||||
|
||||
let mut block_producer = BlockProducer::new(
|
||||
spec.clone(),
|
||||
keypair.pk.clone(),
|
||||
epoch_map.clone(),
|
||||
slot_clock.clone(),
|
||||
beacon_node.clone(),
|
||||
@ -254,42 +247,41 @@ mod tests {
|
||||
// Configure responses from the BeaconNode.
|
||||
beacon_node.set_next_produce_result(Ok(Some(BeaconBlock::random_for_test(&mut rng))));
|
||||
beacon_node.set_next_publish_result(Ok(PublishOutcome::ValidBlock));
|
||||
beacon_node.set_next_nonce_result(Ok(0));
|
||||
|
||||
// One slot before production slot...
|
||||
slot_clock.set_slot(produce_slot - 1);
|
||||
slot_clock.set_slot(produce_slot.as_u64() - 1);
|
||||
assert_eq!(
|
||||
block_producer.poll(),
|
||||
Ok(PollOutcome::BlockProductionNotRequired(produce_slot - 1))
|
||||
);
|
||||
|
||||
// On the produce slot...
|
||||
slot_clock.set_slot(produce_slot);
|
||||
slot_clock.set_slot(produce_slot.as_u64());
|
||||
assert_eq!(
|
||||
block_producer.poll(),
|
||||
Ok(PollOutcome::BlockProduced(produce_slot))
|
||||
Ok(PollOutcome::BlockProduced(produce_slot.into()))
|
||||
);
|
||||
|
||||
// Trying the same produce slot again...
|
||||
slot_clock.set_slot(produce_slot);
|
||||
slot_clock.set_slot(produce_slot.as_u64());
|
||||
assert_eq!(
|
||||
block_producer.poll(),
|
||||
Ok(PollOutcome::SlotAlreadyProcessed(produce_slot))
|
||||
);
|
||||
|
||||
// One slot after the produce slot...
|
||||
slot_clock.set_slot(produce_slot + 1);
|
||||
slot_clock.set_slot(produce_slot.as_u64() + 1);
|
||||
assert_eq!(
|
||||
block_producer.poll(),
|
||||
Ok(PollOutcome::BlockProductionNotRequired(produce_slot + 1))
|
||||
);
|
||||
|
||||
// In an epoch without known duties...
|
||||
let slot = (produce_epoch + 1) * spec.epoch_length;
|
||||
let slot = (produce_epoch.as_u64() + 1) * spec.epoch_length;
|
||||
slot_clock.set_slot(slot);
|
||||
assert_eq!(
|
||||
block_producer.poll(),
|
||||
Ok(PollOutcome::ProducerDutiesUnknown(slot))
|
||||
Ok(PollOutcome::ProducerDutiesUnknown(Slot::new(slot)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
use crate::{DutiesReader, DutiesReaderError};
|
||||
use std::collections::HashMap;
|
||||
use types::{Epoch, Slot};
|
||||
|
||||
pub struct TestEpochMap {
|
||||
pub struct EpochMap {
|
||||
epoch_length: u64,
|
||||
pub map: HashMap<u64, u64>,
|
||||
pub map: HashMap<Epoch, Slot>,
|
||||
}
|
||||
|
||||
impl TestEpochMap {
|
||||
impl EpochMap {
|
||||
pub fn new(epoch_length: u64) -> Self {
|
||||
Self {
|
||||
epoch_length,
|
||||
@ -15,9 +16,9 @@ impl TestEpochMap {
|
||||
}
|
||||
}
|
||||
|
||||
impl DutiesReader for TestEpochMap {
|
||||
fn is_block_production_slot(&self, slot: u64) -> Result<bool, DutiesReaderError> {
|
||||
let epoch = slot / self.epoch_length;
|
||||
impl DutiesReader for EpochMap {
|
||||
fn is_block_production_slot(&self, slot: Slot) -> Result<bool, DutiesReaderError> {
|
||||
let epoch = slot.epoch(self.epoch_length);
|
||||
match self.map.get(&epoch) {
|
||||
Some(s) if *s == slot => Ok(true),
|
||||
Some(s) if *s != slot => Ok(false),
|
||||
|
@ -3,13 +3,13 @@ use std::sync::RwLock;
|
||||
use types::{Keypair, Signature};
|
||||
|
||||
/// A test-only struct used to simulate a Beacon Node.
|
||||
pub struct TestSigner {
|
||||
pub struct LocalSigner {
|
||||
keypair: Keypair,
|
||||
should_sign: RwLock<bool>,
|
||||
}
|
||||
|
||||
impl TestSigner {
|
||||
/// Produce a new TestSigner with signing enabled by default.
|
||||
impl LocalSigner {
|
||||
/// Produce a new LocalSigner with signing enabled by default.
|
||||
pub fn new(keypair: Keypair) -> Self {
|
||||
Self {
|
||||
keypair,
|
||||
@ -24,7 +24,7 @@ impl TestSigner {
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer for TestSigner {
|
||||
impl Signer for LocalSigner {
|
||||
fn sign_block_proposal(&self, message: &[u8]) -> Option<Signature> {
|
||||
Some(Signature::new(message, &self.keypair.sk))
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
mod beacon_node;
|
||||
mod epoch_map;
|
||||
mod signer;
|
||||
mod local_signer;
|
||||
mod simulated_beacon_node;
|
||||
|
||||
pub use self::beacon_node::TestBeaconNode;
|
||||
pub use self::epoch_map::TestEpochMap;
|
||||
pub use self::signer::TestSigner;
|
||||
pub use self::epoch_map::EpochMap;
|
||||
pub use self::local_signer::LocalSigner;
|
||||
pub use self::simulated_beacon_node::SimulatedBeaconNode;
|
||||
|
@ -1,30 +1,21 @@
|
||||
use crate::traits::{BeaconNode, BeaconNodeError, PublishOutcome};
|
||||
use std::sync::RwLock;
|
||||
use types::{BeaconBlock, PublicKey, Signature};
|
||||
use types::{BeaconBlock, Signature, Slot};
|
||||
|
||||
type NonceResult = Result<u64, BeaconNodeError>;
|
||||
type ProduceResult = Result<Option<BeaconBlock>, BeaconNodeError>;
|
||||
type PublishResult = Result<PublishOutcome, BeaconNodeError>;
|
||||
|
||||
/// A test-only struct used to simulate a Beacon Node.
|
||||
#[derive(Default)]
|
||||
pub struct TestBeaconNode {
|
||||
pub nonce_input: RwLock<Option<PublicKey>>,
|
||||
pub nonce_result: RwLock<Option<NonceResult>>,
|
||||
|
||||
pub produce_input: RwLock<Option<(u64, Signature)>>,
|
||||
pub struct SimulatedBeaconNode {
|
||||
pub produce_input: RwLock<Option<(Slot, Signature)>>,
|
||||
pub produce_result: RwLock<Option<ProduceResult>>,
|
||||
|
||||
pub publish_input: RwLock<Option<BeaconBlock>>,
|
||||
pub publish_result: RwLock<Option<PublishResult>>,
|
||||
}
|
||||
|
||||
impl TestBeaconNode {
|
||||
/// Set the result to be returned when `produce_beacon_block` is called.
|
||||
pub fn set_next_nonce_result(&self, result: NonceResult) {
|
||||
*self.nonce_result.write().unwrap() = Some(result);
|
||||
}
|
||||
|
||||
impl SimulatedBeaconNode {
|
||||
/// Set the result to be returned when `produce_beacon_block` is called.
|
||||
pub fn set_next_produce_result(&self, result: ProduceResult) {
|
||||
*self.produce_result.write().unwrap() = Some(result);
|
||||
@ -36,21 +27,13 @@ impl TestBeaconNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl BeaconNode for TestBeaconNode {
|
||||
fn proposer_nonce(&self, pubkey: &PublicKey) -> NonceResult {
|
||||
*self.nonce_input.write().unwrap() = Some(pubkey.clone());
|
||||
match *self.nonce_result.read().unwrap() {
|
||||
Some(ref r) => r.clone(),
|
||||
None => panic!("TestBeaconNode: nonce_result == None"),
|
||||
}
|
||||
}
|
||||
|
||||
impl BeaconNode for SimulatedBeaconNode {
|
||||
/// Returns the value specified by the `set_next_produce_result`.
|
||||
fn produce_beacon_block(&self, slot: u64, randao_reveal: &Signature) -> ProduceResult {
|
||||
fn produce_beacon_block(&self, slot: Slot, randao_reveal: &Signature) -> ProduceResult {
|
||||
*self.produce_input.write().unwrap() = Some((slot, randao_reveal.clone()));
|
||||
match *self.produce_result.read().unwrap() {
|
||||
Some(ref r) => r.clone(),
|
||||
None => panic!("TestBeaconNode: produce_result == None"),
|
||||
None => panic!("SimulatedBeaconNode: produce_result == None"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +42,7 @@ impl BeaconNode for TestBeaconNode {
|
||||
*self.publish_input.write().unwrap() = Some(block);
|
||||
match *self.publish_result.read().unwrap() {
|
||||
Some(ref r) => r.clone(),
|
||||
None => panic!("TestBeaconNode: publish_result == None"),
|
||||
None => panic!("SimulatedBeaconNode: publish_result == None"),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use types::{BeaconBlock, PublicKey, Signature};
|
||||
use types::{BeaconBlock, Signature, Slot};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BeaconNodeError {
|
||||
@ -14,15 +14,12 @@ pub enum PublishOutcome {
|
||||
|
||||
/// Defines the methods required to produce and publish blocks on a Beacon Node.
|
||||
pub trait BeaconNode: Send + Sync {
|
||||
/// Requests the proposer nonce (presently named `proposer_slots`).
|
||||
fn proposer_nonce(&self, pubkey: &PublicKey) -> Result<u64, BeaconNodeError>;
|
||||
|
||||
/// Request that the node produces a block.
|
||||
///
|
||||
/// Returns Ok(None) if the Beacon Node is unable to produce at the given slot.
|
||||
fn produce_beacon_block(
|
||||
&self,
|
||||
slot: u64,
|
||||
slot: Slot,
|
||||
randao_reveal: &Signature,
|
||||
) -> Result<Option<BeaconBlock>, BeaconNodeError>;
|
||||
|
||||
@ -42,7 +39,7 @@ pub enum DutiesReaderError {
|
||||
|
||||
/// Informs a validator of their duties (e.g., block production).
|
||||
pub trait DutiesReader: Send + Sync {
|
||||
fn is_block_production_slot(&self, slot: u64) -> Result<bool, DutiesReaderError>;
|
||||
fn is_block_production_slot(&self, slot: Slot) -> Result<bool, DutiesReaderError>;
|
||||
}
|
||||
|
||||
/// Signs message using an internally-maintained private key.
|
||||
|
@ -1,3 +1,23 @@
|
||||
// Copyright 2019 Sigma Prime Pty Ltd.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
//! This crate stores the various implementations of fork-choice rules that can be used for the
|
||||
//! beacon blockchain.
|
||||
//!
|
||||
@ -7,17 +27,21 @@
|
||||
//! The current implementations are:
|
||||
//! - [`longest-chain`]: Simplistic longest-chain fork choice - primarily for testing, **not for
|
||||
//! production**.
|
||||
//! - [`basic_lmd_ghost`]: This is a simple and very inefficient implementation given in the ethereum 2.0
|
||||
//! - [`slow_lmd_ghost`]: This is a simple and very inefficient implementation given in the ethereum 2.0
|
||||
//! specifications (https://github.com/ethereum/eth2.0-specs/blob/v0.1/specs/core/0_beacon-chain.md#get_block_root).
|
||||
//! - [`optimised_lmd_ghost`]: This is an optimised version of the naive implementation as proposed
|
||||
//! by Vitalik. The reference implementation can be found at: https://github.com/ethereum/research/blob/master/ghost/ghost.py
|
||||
//! - [`protolambda_lmd_ghost`]: Another optimised version of LMD-GHOST designed by @protolambda.
|
||||
//! The go implementation can be found here: https://github.com/protolambda/lmd-ghost.
|
||||
//!
|
||||
//! [`basic_lmd_ghost`]: struct.BasicLmdGhost.html
|
||||
//! [`slow_lmd_ghost`]: struct.SlowLmdGhost.html
|
||||
//! [`optimised_lmd_ghost`]: struct.OptimisedLmdGhost.html
|
||||
//! [`protolambda_lmd_ghost`]: struct.ProtolambdaLmdGhost.html
|
||||
|
||||
extern crate db;
|
||||
extern crate ssz;
|
||||
extern crate types;
|
||||
|
||||
pub mod longest_chain;
|
||||
pub mod optimised_lmd_ghost;
|
||||
pub mod protolambda_lmd_ghost;
|
||||
|
@ -1,12 +1,8 @@
|
||||
extern crate db;
|
||||
extern crate ssz;
|
||||
extern crate types;
|
||||
|
||||
use db::stores::BeaconBlockStore;
|
||||
use db::{ClientDB, DBError};
|
||||
use ssz::{Decodable, DecodeError};
|
||||
use std::sync::Arc;
|
||||
use types::{BeaconBlock, Hash256};
|
||||
use types::{BeaconBlock, Hash256, Slot};
|
||||
|
||||
pub enum ForkChoiceError {
|
||||
BadSszInDatabase,
|
||||
@ -37,7 +33,7 @@ where
|
||||
/*
|
||||
* Loop through all the head blocks and find the highest slot.
|
||||
*/
|
||||
let highest_slot: Option<u64> = None;
|
||||
let highest_slot: Option<Slot> = None;
|
||||
for (_, block) in &head_blocks {
|
||||
let slot = block.slot;
|
||||
|
||||
|
@ -1,3 +1,23 @@
|
||||
// Copyright 2019 Sigma Prime Pty Ltd.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate fast_math;
|
||||
use crate::{ForkChoice, ForkChoiceError};
|
||||
@ -11,6 +31,7 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
slot_epoch_height::{Height, Slot},
|
||||
validator_registry::get_active_validator_indices,
|
||||
BeaconBlock, Hash256,
|
||||
};
|
||||
@ -22,6 +43,7 @@ use types::{
|
||||
const GENESIS_SLOT: u64 = 0;
|
||||
const FORK_CHOICE_BALANCE_INCREMENT: u64 = 1e9 as u64;
|
||||
const MAX_DEPOSIT_AMOUNT: u64 = 32e9 as u64;
|
||||
const EPOCH_LENGTH: u64 = 64;
|
||||
|
||||
/// The optimised LMD-GHOST fork choice rule.
|
||||
/// NOTE: This uses u32 to represent difference between block heights. Thus this is only
|
||||
@ -55,7 +77,7 @@ pub struct OptimisedLMDGhost<T: ClientDB + Sized> {
|
||||
block_store: Arc<BeaconBlockStore<T>>,
|
||||
/// State storage access.
|
||||
state_store: Arc<BeaconStateStore<T>>,
|
||||
max_known_height: u64,
|
||||
max_known_height: Height,
|
||||
}
|
||||
|
||||
impl<T> OptimisedLMDGhost<T>
|
||||
@ -71,7 +93,7 @@ where
|
||||
ancestors: vec![HashMap::new(); 16],
|
||||
latest_attestation_targets: HashMap::new(),
|
||||
children: HashMap::new(),
|
||||
max_known_height: 0,
|
||||
max_known_height: Height::new(0),
|
||||
block_store,
|
||||
state_store,
|
||||
}
|
||||
@ -82,7 +104,7 @@ where
|
||||
pub fn get_latest_votes(
|
||||
&self,
|
||||
state_root: &Hash256,
|
||||
block_slot: u64,
|
||||
block_slot: Slot,
|
||||
) -> Result<HashMap<Hash256, u64>, ForkChoiceError> {
|
||||
// get latest votes
|
||||
// Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) //
|
||||
@ -97,8 +119,10 @@ where
|
||||
.into_beacon_state()
|
||||
.ok_or_else(|| ForkChoiceError::IncorrectBeaconState(*state_root))?;
|
||||
|
||||
let active_validator_indices =
|
||||
get_active_validator_indices(¤t_state.validator_registry, block_slot);
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
¤t_state.validator_registry,
|
||||
block_slot.epoch(EPOCH_LENGTH),
|
||||
);
|
||||
|
||||
for index in active_validator_indices {
|
||||
let balance =
|
||||
@ -115,7 +139,7 @@ where
|
||||
}
|
||||
|
||||
/// Gets the ancestor at a given height `at_height` of a block specified by `block_hash`.
|
||||
fn get_ancestor(&mut self, block_hash: Hash256, at_height: u32) -> Option<Hash256> {
|
||||
fn get_ancestor(&mut self, block_hash: Hash256, at_height: Height) -> Option<Hash256> {
|
||||
// return None if we can't get the block from the db.
|
||||
let block_height = {
|
||||
let block_slot = self
|
||||
@ -126,7 +150,7 @@ where
|
||||
.into_beacon_block()?
|
||||
.slot;
|
||||
|
||||
(block_slot - GENESIS_SLOT) as u32
|
||||
block_slot.height(Slot::from(GENESIS_SLOT))
|
||||
};
|
||||
|
||||
// verify we haven't exceeded the block height
|
||||
@ -138,7 +162,7 @@ where
|
||||
}
|
||||
}
|
||||
// check if the result is stored in our cache
|
||||
let cache_key = CacheKey::new(&block_hash, at_height);
|
||||
let cache_key = CacheKey::new(&block_hash, at_height.as_u32());
|
||||
if let Some(ancestor) = self.cache.get(&cache_key) {
|
||||
return Some(*ancestor);
|
||||
}
|
||||
@ -147,7 +171,7 @@ where
|
||||
|
||||
if let Some(ancestor) = {
|
||||
let ancestor_lookup = self.ancestors
|
||||
[log2_int((block_height - at_height - 1) as u32) as usize]
|
||||
[log2_int((block_height - at_height - 1u64).as_u32()) as usize]
|
||||
.get(&block_hash)
|
||||
//TODO: Panic if we can't lookup and fork choice fails
|
||||
.expect("All blocks should be added to the ancestor log lookup table");
|
||||
@ -165,7 +189,7 @@ where
|
||||
fn get_clear_winner(
|
||||
&mut self,
|
||||
latest_votes: &HashMap<Hash256, u64>,
|
||||
height: u64,
|
||||
block_height: Height,
|
||||
) -> Option<Hash256> {
|
||||
// map of vote counts for every hash at this height
|
||||
let mut current_votes: HashMap<Hash256, u64> = HashMap::new();
|
||||
@ -174,7 +198,7 @@ where
|
||||
// loop through the latest votes and count all votes
|
||||
// these have already been weighted by balance
|
||||
for (hash, votes) in latest_votes.iter() {
|
||||
if let Some(ancestor) = self.get_ancestor(*hash, height as u32) {
|
||||
if let Some(ancestor) = self.get_ancestor(*hash, block_height) {
|
||||
let current_vote_value = current_votes.get(&ancestor).unwrap_or_else(|| &0);
|
||||
current_votes.insert(ancestor, current_vote_value + *votes);
|
||||
total_vote_count += votes;
|
||||
@ -246,7 +270,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimisedLMDGhost<T> {
|
||||
.get_reader(&block.parent_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))?
|
||||
.slot()
|
||||
- GENESIS_SLOT;
|
||||
.height(Slot::from(GENESIS_SLOT));
|
||||
|
||||
let parent_hash = &block.parent_root;
|
||||
|
||||
@ -316,7 +340,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimisedLMDGhost<T> {
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?;
|
||||
|
||||
let block_slot = block.slot();
|
||||
let block_height = block_slot - GENESIS_SLOT;
|
||||
let block_height = block_slot.height(Slot::from(GENESIS_SLOT));
|
||||
let state_root = block.state_root();
|
||||
|
||||
let mut current_head = *justified_block_start;
|
||||
@ -324,8 +348,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimisedLMDGhost<T> {
|
||||
let mut latest_votes = self.get_latest_votes(&state_root, block_slot)?;
|
||||
|
||||
// remove any votes that don't relate to our current head.
|
||||
latest_votes
|
||||
.retain(|hash, _| self.get_ancestor(*hash, block_height as u32) == Some(current_head));
|
||||
latest_votes.retain(|hash, _| self.get_ancestor(*hash, block_height) == Some(current_head));
|
||||
|
||||
// begin searching for the head
|
||||
loop {
|
||||
@ -337,7 +360,8 @@ impl<T: ClientDB + Sized> ForkChoice for OptimisedLMDGhost<T> {
|
||||
|
||||
// logarithmic lookup blocks to see if there are obvious winners, if so,
|
||||
// progress to the next iteration.
|
||||
let mut step = power_of_2_below(self.max_known_height as u32 - block_height as u32) / 2;
|
||||
let mut step =
|
||||
power_of_2_below(self.max_known_height.saturating_sub(block_height).as_u32()) / 2;
|
||||
while step > 0 {
|
||||
if let Some(clear_winner) = self.get_clear_winner(
|
||||
&latest_votes,
|
||||
@ -359,7 +383,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimisedLMDGhost<T> {
|
||||
let mut child_votes = HashMap::new();
|
||||
for (voted_hash, vote) in latest_votes.iter() {
|
||||
// if the latest votes correspond to a child
|
||||
if let Some(child) = self.get_ancestor(*voted_hash, (block_height + 1) as u32) {
|
||||
if let Some(child) = self.get_ancestor(*voted_hash, block_height + 1) {
|
||||
// add up the votes for each child
|
||||
*child_votes.entry(child).or_insert_with(|| 0) += vote;
|
||||
}
|
||||
@ -378,13 +402,12 @@ impl<T: ClientDB + Sized> ForkChoice for OptimisedLMDGhost<T> {
|
||||
.get_reader(¤t_head)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?
|
||||
.slot()
|
||||
- GENESIS_SLOT;
|
||||
.height(Slot::from(GENESIS_SLOT));
|
||||
|
||||
// prune the latest votes for votes that are not part of current chosen chain
|
||||
// more specifically, only keep votes that have head as an ancestor
|
||||
latest_votes.retain(|hash, _| {
|
||||
self.get_ancestor(*hash, block_height as u32) == Some(current_head)
|
||||
});
|
||||
latest_votes
|
||||
.retain(|hash, _| self.get_ancestor(*hash, block_height) == Some(current_head));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,23 @@
|
||||
// Copyright 2019 Sigma Prime Pty Ltd.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
extern crate db;
|
||||
|
||||
use crate::{ForkChoice, ForkChoiceError};
|
||||
@ -9,6 +29,7 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
slot_epoch_height::Slot,
|
||||
validator_registry::get_active_validator_indices,
|
||||
BeaconBlock, Hash256,
|
||||
};
|
||||
@ -19,6 +40,7 @@ use types::{
|
||||
const GENESIS_SLOT: u64 = 0;
|
||||
const FORK_CHOICE_BALANCE_INCREMENT: u64 = 1e9 as u64;
|
||||
const MAX_DEPOSIT_AMOUNT: u64 = 32e9 as u64;
|
||||
const EPOCH_LENGTH: u64 = 64;
|
||||
|
||||
pub struct SlowLMDGhost<T: ClientDB + Sized> {
|
||||
/// The latest attestation targets as a map of validator index to block hash.
|
||||
@ -50,7 +72,7 @@ where
|
||||
pub fn get_latest_votes(
|
||||
&self,
|
||||
state_root: &Hash256,
|
||||
block_slot: u64,
|
||||
block_slot: Slot,
|
||||
) -> Result<HashMap<Hash256, u64>, ForkChoiceError> {
|
||||
// get latest votes
|
||||
// Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) //
|
||||
@ -65,8 +87,10 @@ where
|
||||
.into_beacon_state()
|
||||
.ok_or_else(|| ForkChoiceError::IncorrectBeaconState(*state_root))?;
|
||||
|
||||
let active_validator_indices =
|
||||
get_active_validator_indices(¤t_state.validator_registry, block_slot);
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
¤t_state.validator_registry,
|
||||
block_slot.epoch(EPOCH_LENGTH),
|
||||
);
|
||||
|
||||
for index in active_validator_indices {
|
||||
let balance =
|
||||
@ -148,7 +172,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
|
||||
.get_reader(&target_block_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))?
|
||||
.slot()
|
||||
- GENESIS_SLOT;
|
||||
.height(Slot::from(GENESIS_SLOT));
|
||||
|
||||
// get the height of the past target block
|
||||
let past_block_height = self
|
||||
@ -156,7 +180,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
|
||||
.get_reader(&attestation_target)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))?
|
||||
.slot()
|
||||
- GENESIS_SLOT;
|
||||
.height(Slot::from(GENESIS_SLOT));
|
||||
// update the attestation only if the new target is higher
|
||||
if past_block_height < block_height {
|
||||
*attestation_target = *target_block_root;
|
||||
|
@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "genesis"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../utils/bls" }
|
||||
ssz = { path = "../utils/ssz" }
|
||||
types = { path = "../types" }
|
||||
validator_induction = { path = "../validator_induction" }
|
@ -1,93 +0,0 @@
|
||||
use types::{BeaconBlock, BeaconBlockBody, ChainSpec, Eth1Data, Hash256};
|
||||
|
||||
/// Generate a genesis BeaconBlock.
|
||||
pub fn genesis_beacon_block(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock {
|
||||
BeaconBlock {
|
||||
slot: spec.genesis_slot,
|
||||
parent_root: spec.zero_hash,
|
||||
state_root,
|
||||
randao_reveal: spec.empty_signature.clone(),
|
||||
eth1_data: Eth1Data {
|
||||
deposit_root: spec.zero_hash,
|
||||
block_hash: spec.zero_hash,
|
||||
},
|
||||
signature: spec.empty_signature.clone(),
|
||||
body: BeaconBlockBody {
|
||||
proposer_slashings: vec![],
|
||||
casper_slashings: vec![],
|
||||
attestations: vec![],
|
||||
custody_reseeds: vec![],
|
||||
custody_challenges: vec![],
|
||||
custody_responses: vec![],
|
||||
deposits: vec![],
|
||||
exits: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bls::Signature;
|
||||
|
||||
#[test]
|
||||
fn test_state_root() {
|
||||
let spec = ChainSpec::foundation();
|
||||
let state_root = Hash256::from("cats".as_bytes());
|
||||
|
||||
let block = genesis_beacon_block(state_root, &spec);
|
||||
|
||||
assert_eq!(block.state_root, state_root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_items() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state_root = Hash256::zero();
|
||||
|
||||
let genesis_block = genesis_beacon_block(state_root, &spec);
|
||||
|
||||
assert!(genesis_block.slot == 0);
|
||||
assert!(genesis_block.parent_root.is_zero());
|
||||
assert_eq!(genesis_block.randao_reveal, Signature::empty_signature());
|
||||
assert!(genesis_block.eth1_data.deposit_root.is_zero());
|
||||
assert!(genesis_block.eth1_data.block_hash.is_zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_beacon_body() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state_root = Hash256::zero();
|
||||
|
||||
let genesis_block = genesis_beacon_block(state_root, &spec);
|
||||
|
||||
// Custody items are not being implemented until phase 1 so tests to be added later
|
||||
|
||||
assert!(genesis_block.body.proposer_slashings.is_empty());
|
||||
assert!(genesis_block.body.casper_slashings.is_empty());
|
||||
assert!(genesis_block.body.attestations.is_empty());
|
||||
assert!(genesis_block.body.deposits.is_empty());
|
||||
assert!(genesis_block.body.exits.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signature() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state_root = Hash256::zero();
|
||||
|
||||
let genesis_block = genesis_beacon_block(state_root, &spec);
|
||||
|
||||
// Signature should consist of [bytes48(0), bytes48(0)]
|
||||
// Note this is implemented using Apache Milagro BLS which requires one extra byte -> 97bytes
|
||||
let raw_sig = genesis_block.signature.as_raw();
|
||||
let raw_sig_bytes = raw_sig.as_bytes();
|
||||
|
||||
for item in raw_sig_bytes.iter() {
|
||||
assert!(*item == 0);
|
||||
}
|
||||
assert_eq!(genesis_block.signature, Signature::empty_signature());
|
||||
}
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
use types::{BeaconState, ChainSpec, Crosslink, Fork};
|
||||
|
||||
pub fn genesis_beacon_state(spec: &ChainSpec) -> BeaconState {
|
||||
let initial_crosslink = Crosslink {
|
||||
slot: spec.genesis_slot,
|
||||
shard_block_root: spec.zero_hash,
|
||||
};
|
||||
|
||||
BeaconState {
|
||||
/*
|
||||
* Misc
|
||||
*/
|
||||
slot: spec.genesis_slot,
|
||||
genesis_time: spec.genesis_time,
|
||||
fork_data: Fork {
|
||||
pre_fork_version: spec.genesis_fork_version,
|
||||
post_fork_version: spec.genesis_fork_version,
|
||||
fork_slot: spec.genesis_slot,
|
||||
},
|
||||
/*
|
||||
* Validator registry
|
||||
*/
|
||||
validator_registry: spec.initial_validators.clone(),
|
||||
validator_balances: spec.initial_balances.clone(),
|
||||
validator_registry_update_slot: spec.genesis_slot,
|
||||
validator_registry_exit_count: 0,
|
||||
validator_registry_delta_chain_tip: spec.zero_hash,
|
||||
/*
|
||||
* Randomness and committees
|
||||
*/
|
||||
latest_randao_mixes: vec![spec.zero_hash; spec.latest_randao_mixes_length as usize],
|
||||
latest_vdf_outputs: vec![
|
||||
spec.zero_hash;
|
||||
(spec.latest_randao_mixes_length / spec.epoch_length) as usize
|
||||
],
|
||||
previous_epoch_start_shard: spec.genesis_start_shard,
|
||||
current_epoch_start_shard: spec.genesis_start_shard,
|
||||
previous_epoch_calculation_slot: spec.genesis_slot,
|
||||
current_epoch_calculation_slot: spec.genesis_slot,
|
||||
previous_epoch_seed: spec.zero_hash,
|
||||
current_epoch_seed: spec.zero_hash,
|
||||
/*
|
||||
* Custody challenges
|
||||
*/
|
||||
custody_challenges: vec![],
|
||||
/*
|
||||
* Finality
|
||||
*/
|
||||
previous_justified_slot: spec.genesis_slot,
|
||||
justified_slot: spec.genesis_slot,
|
||||
justification_bitfield: 0,
|
||||
finalized_slot: spec.genesis_slot,
|
||||
/*
|
||||
* Recent state
|
||||
*/
|
||||
latest_crosslinks: vec![initial_crosslink; spec.shard_count as usize],
|
||||
latest_block_roots: vec![spec.zero_hash; spec.latest_block_roots_length as usize],
|
||||
latest_penalized_balances: vec![0; spec.latest_penalized_exit_length as usize],
|
||||
latest_attestations: vec![],
|
||||
batched_block_roots: vec![],
|
||||
/*
|
||||
* PoW receipt root
|
||||
*/
|
||||
latest_eth1_data: spec.intial_eth1_data.clone(),
|
||||
eth1_data_votes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use types::Hash256;
|
||||
|
||||
#[test]
|
||||
fn test_genesis_state() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state = genesis_beacon_state(&spec);
|
||||
|
||||
assert_eq!(
|
||||
state.validator_registry.len(),
|
||||
spec.initial_validators.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_genesis_state_misc() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state = genesis_beacon_state(&spec);
|
||||
|
||||
assert_eq!(state.slot, 0);
|
||||
assert_eq!(state.genesis_time, spec.genesis_time);
|
||||
assert_eq!(state.fork_data.pre_fork_version, 0);
|
||||
assert_eq!(state.fork_data.post_fork_version, 0);
|
||||
assert_eq!(state.fork_data.fork_slot, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_genesis_state_validators() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state = genesis_beacon_state(&spec);
|
||||
|
||||
assert_eq!(state.validator_registry, spec.initial_validators);
|
||||
assert_eq!(state.validator_balances, spec.initial_balances);
|
||||
assert!(state.validator_registry_update_slot == 0);
|
||||
assert!(state.validator_registry_exit_count == 0);
|
||||
assert_eq!(state.validator_registry_delta_chain_tip, Hash256::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_genesis_state_randomness_committees() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state = genesis_beacon_state(&spec);
|
||||
|
||||
// Array of size 8,192 each being zero_hash
|
||||
assert_eq!(state.latest_randao_mixes.len(), 8_192);
|
||||
for item in state.latest_randao_mixes.iter() {
|
||||
assert_eq!(*item, Hash256::zero());
|
||||
}
|
||||
|
||||
// Array of size 8,192 each being a zero hash
|
||||
assert_eq!(state.latest_vdf_outputs.len(), (8_192 / 64));
|
||||
for item in state.latest_vdf_outputs.iter() {
|
||||
assert_eq!(*item, Hash256::zero());
|
||||
}
|
||||
|
||||
// TODO: Check shard and committee shuffling requires solving issue:
|
||||
// https://github.com/sigp/lighthouse/issues/151
|
||||
|
||||
// initial_shuffling = get_shuffling(Hash256::zero(), &state.validator_registry, 0, 0)
|
||||
// initial_shuffling = initial_shuffling.append(initial_shuffling.clone());
|
||||
}
|
||||
|
||||
// Custody not implemented until Phase 1
|
||||
#[test]
|
||||
fn test_genesis_state_custody() {}
|
||||
|
||||
#[test]
|
||||
fn test_genesis_state_finanilty() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state = genesis_beacon_state(&spec);
|
||||
|
||||
assert_eq!(state.previous_justified_slot, 0);
|
||||
assert_eq!(state.justified_slot, 0);
|
||||
assert_eq!(state.justification_bitfield, 0);
|
||||
assert_eq!(state.finalized_slot, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_genesis_state_recent_state() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state = genesis_beacon_state(&spec);
|
||||
|
||||
// Test latest_crosslinks
|
||||
assert_eq!(state.latest_crosslinks.len(), 1_024);
|
||||
for link in state.latest_crosslinks.iter() {
|
||||
assert_eq!(link.slot, 0);
|
||||
assert_eq!(link.shard_block_root, Hash256::zero());
|
||||
}
|
||||
|
||||
// Test latest_block_roots
|
||||
assert_eq!(state.latest_block_roots.len(), 8_192);
|
||||
for block in state.latest_block_roots.iter() {
|
||||
assert_eq!(*block, Hash256::zero());
|
||||
}
|
||||
|
||||
// Test latest_penalized_balances
|
||||
assert_eq!(state.latest_penalized_balances.len(), 8_192);
|
||||
for item in state.latest_penalized_balances.iter() {
|
||||
assert!(*item == 0);
|
||||
}
|
||||
|
||||
// Test latest_attestations
|
||||
assert!(state.latest_attestations.is_empty());
|
||||
|
||||
// batched_block_roots
|
||||
assert!(state.batched_block_roots.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_genesis_state_deposit_root() {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state = genesis_beacon_state(&spec);
|
||||
|
||||
assert_eq!(&state.latest_eth1_data, &spec.intial_eth1_data);
|
||||
assert!(state.eth1_data_votes.is_empty());
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
mod beacon_block;
|
||||
mod beacon_state;
|
||||
|
||||
pub use crate::beacon_block::genesis_beacon_block;
|
||||
pub use crate::beacon_state::genesis_beacon_state;
|
@ -1,10 +1,13 @@
|
||||
[package]
|
||||
name = "validator_induction"
|
||||
name = "state_processing"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../utils/bls" }
|
||||
hashing = { path = "../utils/hashing" }
|
||||
integer-sqrt = "0.1"
|
||||
log = "0.4"
|
||||
ssz = { path = "../utils/ssz" }
|
||||
types = { path = "../types" }
|
||||
rayon = "1.0"
|
403
eth2/state_processing/src/block_processable.rs
Normal file
403
eth2/state_processing/src/block_processable.rs
Normal file
@ -0,0 +1,403 @@
|
||||
use crate::SlotProcessingError;
|
||||
use hashing::hash;
|
||||
use log::debug;
|
||||
use ssz::{ssz_encode, TreeHash};
|
||||
use types::{
|
||||
beacon_state::{AttestationValidationError, CommitteesError},
|
||||
AggregatePublicKey, Attestation, BeaconBlock, BeaconState, ChainSpec, Crosslink, Epoch, Exit,
|
||||
Fork, Hash256, PendingAttestation, PublicKey, Signature,
|
||||
};
|
||||
|
||||
// TODO: define elsehwere.
|
||||
const DOMAIN_PROPOSAL: u64 = 2;
|
||||
const DOMAIN_EXIT: u64 = 3;
|
||||
const DOMAIN_RANDAO: u64 = 4;
|
||||
const PHASE_0_CUSTODY_BIT: bool = false;
|
||||
const DOMAIN_ATTESTATION: u64 = 1;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
DBError(String),
|
||||
StateAlreadyTransitioned,
|
||||
PresentSlotIsNone,
|
||||
UnableToDecodeBlock,
|
||||
MissingParentState(Hash256),
|
||||
InvalidParentState(Hash256),
|
||||
MissingBeaconBlock(Hash256),
|
||||
InvalidBeaconBlock(Hash256),
|
||||
MissingParentBlock(Hash256),
|
||||
NoBlockProducer,
|
||||
StateSlotMismatch,
|
||||
BadBlockSignature,
|
||||
BadRandaoSignature,
|
||||
MaxProposerSlashingsExceeded,
|
||||
BadProposerSlashing,
|
||||
MaxAttestationsExceeded,
|
||||
InvalidAttestation(AttestationValidationError),
|
||||
NoBlockRoot,
|
||||
MaxDepositsExceeded,
|
||||
MaxExitsExceeded,
|
||||
BadExit,
|
||||
BadCustodyReseeds,
|
||||
BadCustodyChallenges,
|
||||
BadCustodyResponses,
|
||||
CommitteesError(CommitteesError),
|
||||
SlotProcessingError(SlotProcessingError),
|
||||
}
|
||||
|
||||
macro_rules! ensure {
|
||||
($condition: expr, $result: expr) => {
|
||||
if !$condition {
|
||||
return Err($result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait BlockProcessable {
|
||||
fn per_block_processing(&mut self, block: &BeaconBlock, spec: &ChainSpec) -> Result<(), Error>;
|
||||
fn per_block_processing_without_verifying_block_signature(
|
||||
&mut self,
|
||||
block: &BeaconBlock,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl BlockProcessable for BeaconState {
|
||||
fn per_block_processing(&mut self, block: &BeaconBlock, spec: &ChainSpec) -> Result<(), Error> {
|
||||
per_block_processing_signature_optional(self, block, true, spec)
|
||||
}
|
||||
|
||||
fn per_block_processing_without_verifying_block_signature(
|
||||
&mut self,
|
||||
block: &BeaconBlock,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
per_block_processing_signature_optional(self, block, false, spec)
|
||||
}
|
||||
}
|
||||
|
||||
fn per_block_processing_signature_optional(
|
||||
state: &mut BeaconState,
|
||||
block: &BeaconBlock,
|
||||
verify_block_signature: bool,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
ensure!(block.slot == state.slot, Error::StateSlotMismatch);
|
||||
|
||||
/*
|
||||
* Proposer Signature
|
||||
*/
|
||||
let block_proposer_index = state
|
||||
.get_beacon_proposer_index(block.slot, spec)
|
||||
.map_err(|_| Error::NoBlockProducer)?;
|
||||
let block_proposer = &state.validator_registry[block_proposer_index];
|
||||
|
||||
if verify_block_signature {
|
||||
ensure!(
|
||||
bls_verify(
|
||||
&block_proposer.pubkey,
|
||||
&block.proposal_root(spec)[..],
|
||||
&block.signature,
|
||||
get_domain(&state.fork, state.current_epoch(spec), DOMAIN_PROPOSAL)
|
||||
),
|
||||
Error::BadBlockSignature
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* RANDAO
|
||||
*/
|
||||
ensure!(
|
||||
bls_verify(
|
||||
&block_proposer.pubkey,
|
||||
&ssz_encode(&state.current_epoch(spec)),
|
||||
&block.randao_reveal,
|
||||
get_domain(&state.fork, state.current_epoch(spec), DOMAIN_RANDAO)
|
||||
),
|
||||
Error::BadRandaoSignature
|
||||
);
|
||||
|
||||
// TODO: check this is correct.
|
||||
let new_mix = {
|
||||
let mut mix = state.latest_randao_mixes
|
||||
[state.slot.as_usize() % spec.latest_randao_mixes_length]
|
||||
.to_vec();
|
||||
mix.append(&mut ssz_encode(&block.randao_reveal));
|
||||
Hash256::from(&hash(&mix)[..])
|
||||
};
|
||||
|
||||
state.latest_randao_mixes[state.slot.as_usize() % spec.latest_randao_mixes_length] = new_mix;
|
||||
|
||||
/*
|
||||
* Eth1 data
|
||||
*/
|
||||
// TODO: Eth1 data processing.
|
||||
|
||||
/*
|
||||
* Proposer slashings
|
||||
*/
|
||||
ensure!(
|
||||
block.body.proposer_slashings.len() as u64 <= spec.max_proposer_slashings,
|
||||
Error::MaxProposerSlashingsExceeded
|
||||
);
|
||||
for proposer_slashing in &block.body.proposer_slashings {
|
||||
let proposer = state
|
||||
.validator_registry
|
||||
.get(proposer_slashing.proposer_index as usize)
|
||||
.ok_or(Error::BadProposerSlashing)?;
|
||||
ensure!(
|
||||
proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot,
|
||||
Error::BadProposerSlashing
|
||||
);
|
||||
ensure!(
|
||||
proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard,
|
||||
Error::BadProposerSlashing
|
||||
);
|
||||
ensure!(
|
||||
proposer_slashing.proposal_data_1.block_root
|
||||
!= proposer_slashing.proposal_data_2.block_root,
|
||||
Error::BadProposerSlashing
|
||||
);
|
||||
ensure!(
|
||||
proposer.penalized_epoch > state.current_epoch(spec),
|
||||
Error::BadProposerSlashing
|
||||
);
|
||||
ensure!(
|
||||
bls_verify(
|
||||
&proposer.pubkey,
|
||||
&proposer_slashing.proposal_data_1.hash_tree_root(),
|
||||
&proposer_slashing.proposal_signature_1,
|
||||
get_domain(
|
||||
&state.fork,
|
||||
proposer_slashing
|
||||
.proposal_data_1
|
||||
.slot
|
||||
.epoch(spec.epoch_length),
|
||||
DOMAIN_PROPOSAL
|
||||
)
|
||||
),
|
||||
Error::BadProposerSlashing
|
||||
);
|
||||
ensure!(
|
||||
bls_verify(
|
||||
&proposer.pubkey,
|
||||
&proposer_slashing.proposal_data_2.hash_tree_root(),
|
||||
&proposer_slashing.proposal_signature_2,
|
||||
get_domain(
|
||||
&state.fork,
|
||||
proposer_slashing
|
||||
.proposal_data_2
|
||||
.slot
|
||||
.epoch(spec.epoch_length),
|
||||
DOMAIN_PROPOSAL
|
||||
)
|
||||
),
|
||||
Error::BadProposerSlashing
|
||||
);
|
||||
state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attestations
|
||||
*/
|
||||
ensure!(
|
||||
block.body.attestations.len() as u64 <= spec.max_attestations,
|
||||
Error::MaxAttestationsExceeded
|
||||
);
|
||||
|
||||
for attestation in &block.body.attestations {
|
||||
validate_attestation(&state, attestation, spec)?;
|
||||
|
||||
let pending_attestation = PendingAttestation {
|
||||
data: attestation.data.clone(),
|
||||
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
||||
custody_bitfield: attestation.custody_bitfield.clone(),
|
||||
inclusion_slot: state.slot,
|
||||
};
|
||||
state.latest_attestations.push(pending_attestation);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"{} attestations verified & processed.",
|
||||
block.body.attestations.len()
|
||||
);
|
||||
|
||||
/*
|
||||
* Deposits
|
||||
*/
|
||||
ensure!(
|
||||
block.body.deposits.len() as u64 <= spec.max_deposits,
|
||||
Error::MaxDepositsExceeded
|
||||
);
|
||||
|
||||
// TODO: process deposits.
|
||||
|
||||
/*
|
||||
* Exits
|
||||
*/
|
||||
ensure!(
|
||||
block.body.exits.len() as u64 <= spec.max_exits,
|
||||
Error::MaxExitsExceeded
|
||||
);
|
||||
|
||||
for exit in &block.body.exits {
|
||||
let validator = state
|
||||
.validator_registry
|
||||
.get(exit.validator_index as usize)
|
||||
.ok_or(Error::BadExit)?;
|
||||
ensure!(
|
||||
validator.exit_epoch
|
||||
> state.get_entry_exit_effect_epoch(state.current_epoch(spec), spec),
|
||||
Error::BadExit
|
||||
);
|
||||
ensure!(state.current_epoch(spec) >= exit.epoch, Error::BadExit);
|
||||
let exit_message = {
|
||||
let exit_struct = Exit {
|
||||
epoch: exit.epoch,
|
||||
validator_index: exit.validator_index,
|
||||
signature: spec.empty_signature.clone(),
|
||||
};
|
||||
exit_struct.hash_tree_root()
|
||||
};
|
||||
ensure!(
|
||||
bls_verify(
|
||||
&validator.pubkey,
|
||||
&exit_message,
|
||||
&exit.signature,
|
||||
get_domain(&state.fork, exit.epoch, DOMAIN_EXIT)
|
||||
),
|
||||
Error::BadProposerSlashing
|
||||
);
|
||||
state.initiate_validator_exit(exit.validator_index as usize);
|
||||
}
|
||||
|
||||
debug!("State transition complete.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_attestation(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), AttestationValidationError> {
|
||||
validate_attestation_signature_optional(state, attestation, spec, true)
|
||||
}
|
||||
|
||||
pub fn validate_attestation_without_signature(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), AttestationValidationError> {
|
||||
validate_attestation_signature_optional(state, attestation, spec, false)
|
||||
}
|
||||
|
||||
fn validate_attestation_signature_optional(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
verify_signature: bool,
|
||||
) -> Result<(), AttestationValidationError> {
|
||||
ensure!(
|
||||
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||
AttestationValidationError::IncludedTooEarly
|
||||
);
|
||||
ensure!(
|
||||
attestation.data.slot + spec.epoch_length >= state.slot,
|
||||
AttestationValidationError::IncludedTooLate
|
||||
);
|
||||
if attestation.data.slot >= state.current_epoch_start_slot(spec) {
|
||||
ensure!(
|
||||
attestation.data.justified_epoch == state.justified_epoch,
|
||||
AttestationValidationError::WrongJustifiedSlot
|
||||
);
|
||||
} else {
|
||||
ensure!(
|
||||
attestation.data.justified_epoch == state.previous_justified_epoch,
|
||||
AttestationValidationError::WrongJustifiedSlot
|
||||
);
|
||||
}
|
||||
ensure!(
|
||||
attestation.data.justified_block_root
|
||||
== *state
|
||||
.get_block_root(
|
||||
attestation
|
||||
.data
|
||||
.justified_epoch
|
||||
.start_slot(spec.epoch_length),
|
||||
&spec
|
||||
)
|
||||
.ok_or(AttestationValidationError::NoBlockRoot)?,
|
||||
AttestationValidationError::WrongJustifiedRoot
|
||||
);
|
||||
let potential_crosslink = Crosslink {
|
||||
shard_block_root: attestation.data.shard_block_root,
|
||||
epoch: attestation.data.slot.epoch(spec.epoch_length),
|
||||
};
|
||||
ensure!(
|
||||
(attestation.data.latest_crosslink
|
||||
== state.latest_crosslinks[attestation.data.shard as usize])
|
||||
| (attestation.data.latest_crosslink == potential_crosslink),
|
||||
AttestationValidationError::BadLatestCrosslinkRoot
|
||||
);
|
||||
if verify_signature {
|
||||
let participants = state.get_attestation_participants(
|
||||
&attestation.data,
|
||||
&attestation.aggregation_bitfield,
|
||||
spec,
|
||||
)?;
|
||||
let mut group_public_key = AggregatePublicKey::new();
|
||||
for participant in participants {
|
||||
group_public_key.add(
|
||||
state.validator_registry[participant as usize]
|
||||
.pubkey
|
||||
.as_raw(),
|
||||
)
|
||||
}
|
||||
ensure!(
|
||||
attestation.verify_signature(
|
||||
&group_public_key,
|
||||
PHASE_0_CUSTODY_BIT,
|
||||
get_domain(
|
||||
&state.fork,
|
||||
attestation.data.slot.epoch(spec.epoch_length),
|
||||
DOMAIN_ATTESTATION,
|
||||
)
|
||||
),
|
||||
AttestationValidationError::BadSignature
|
||||
);
|
||||
}
|
||||
ensure!(
|
||||
attestation.data.shard_block_root == spec.zero_hash,
|
||||
AttestationValidationError::ShardBlockRootNotZero
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_domain(_fork: &Fork, _epoch: Epoch, _domain_type: u64) -> u64 {
|
||||
// TODO: stubbed out.
|
||||
0
|
||||
}
|
||||
|
||||
fn bls_verify(pubkey: &PublicKey, message: &[u8], signature: &Signature, _domain: u64) -> bool {
|
||||
// TODO: add domain
|
||||
signature.verify(message, pubkey)
|
||||
}
|
||||
|
||||
impl From<AttestationValidationError> for Error {
|
||||
fn from(e: AttestationValidationError) -> Error {
|
||||
Error::InvalidAttestation(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommitteesError> for Error {
|
||||
fn from(e: CommitteesError) -> Error {
|
||||
Error::CommitteesError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlotProcessingError> for Error {
|
||||
fn from(e: SlotProcessingError) -> Error {
|
||||
Error::SlotProcessingError(e)
|
||||
}
|
||||
}
|
716
eth2/state_processing/src/epoch_processable.rs
Normal file
716
eth2/state_processing/src/epoch_processable.rs
Normal file
@ -0,0 +1,716 @@
|
||||
use integer_sqrt::IntegerSquareRoot;
|
||||
use log::{debug, trace};
|
||||
use rayon::prelude::*;
|
||||
use ssz::TreeHash;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::iter::FromIterator;
|
||||
use types::{
|
||||
beacon_state::{AttestationParticipantsError, CommitteesError, InclusionError},
|
||||
validator_registry::get_active_validator_indices,
|
||||
BeaconState, ChainSpec, Crosslink, Epoch, Hash256, PendingAttestation,
|
||||
};
|
||||
|
||||
macro_rules! safe_add_assign {
|
||||
($a: expr, $b: expr) => {
|
||||
$a = $a.saturating_add($b);
|
||||
};
|
||||
}
|
||||
macro_rules! safe_sub_assign {
|
||||
($a: expr, $b: expr) => {
|
||||
$a = $a.saturating_sub($b);
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
UnableToDetermineProducer,
|
||||
NoBlockRoots,
|
||||
BaseRewardQuotientIsZero,
|
||||
NoRandaoSeed,
|
||||
CommitteesError(CommitteesError),
|
||||
AttestationParticipantsError(AttestationParticipantsError),
|
||||
InclusionError(InclusionError),
|
||||
WinningRootError(WinningRootError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum WinningRootError {
|
||||
NoWinningRoot,
|
||||
AttestationParticipantsError(AttestationParticipantsError),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WinningRoot {
|
||||
pub shard_block_root: Hash256,
|
||||
pub attesting_validator_indices: Vec<usize>,
|
||||
pub total_balance: u64,
|
||||
pub total_attesting_balance: u64,
|
||||
}
|
||||
|
||||
pub trait EpochProcessable {
|
||||
fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl EpochProcessable for BeaconState {
|
||||
// Cyclomatic complexity is ignored. It would be ideal to split this function apart, however it
|
||||
// remains monolithic to allow for easier spec updates. Once the spec is more stable we can
|
||||
// optimise.
|
||||
#[allow(clippy::cyclomatic_complexity)]
|
||||
fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), Error> {
|
||||
let current_epoch = self.current_epoch(spec);
|
||||
let previous_epoch = self.previous_epoch(spec);
|
||||
let next_epoch = self.next_epoch(spec);
|
||||
|
||||
debug!(
|
||||
"Starting per-epoch processing on epoch {}...",
|
||||
self.current_epoch(spec)
|
||||
);
|
||||
|
||||
/*
|
||||
* Validators attesting during the current epoch.
|
||||
*/
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
&self.validator_registry,
|
||||
self.slot.epoch(spec.epoch_length),
|
||||
);
|
||||
let current_total_balance = self.get_total_balance(&active_validator_indices[..], spec);
|
||||
|
||||
trace!(
|
||||
"{} validators with a total balance of {} wei.",
|
||||
active_validator_indices.len(),
|
||||
current_total_balance
|
||||
);
|
||||
|
||||
let current_epoch_attestations: Vec<&PendingAttestation> = self
|
||||
.latest_attestations
|
||||
.par_iter()
|
||||
.filter(|a| {
|
||||
(a.data.slot / spec.epoch_length).epoch(spec.epoch_length)
|
||||
== self.current_epoch(spec)
|
||||
})
|
||||
.collect();
|
||||
|
||||
trace!(
|
||||
"Current epoch attestations: {}",
|
||||
current_epoch_attestations.len()
|
||||
);
|
||||
|
||||
let current_epoch_boundary_attestations: Vec<&PendingAttestation> =
|
||||
current_epoch_attestations
|
||||
.par_iter()
|
||||
.filter(
|
||||
|a| match self.get_block_root(self.current_epoch_start_slot(spec), spec) {
|
||||
Some(block_root) => {
|
||||
(a.data.epoch_boundary_root == *block_root)
|
||||
&& (a.data.justified_epoch == self.justified_epoch)
|
||||
}
|
||||
None => unreachable!(),
|
||||
},
|
||||
)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let current_epoch_boundary_attester_indices = self
|
||||
.get_attestation_participants_union(¤t_epoch_boundary_attestations[..], spec)?;
|
||||
let current_epoch_boundary_attesting_balance =
|
||||
self.get_total_balance(¤t_epoch_boundary_attester_indices[..], spec);
|
||||
|
||||
trace!(
|
||||
"Current epoch boundary attesters: {}",
|
||||
current_epoch_boundary_attester_indices.len()
|
||||
);
|
||||
|
||||
/*
|
||||
* Validators attesting during the previous epoch
|
||||
*/
|
||||
|
||||
/*
|
||||
* Validators that made an attestation during the previous epoch
|
||||
*/
|
||||
let previous_epoch_attestations: Vec<&PendingAttestation> = self
|
||||
.latest_attestations
|
||||
.par_iter()
|
||||
.filter(|a| {
|
||||
//TODO: ensure these saturating subs are correct.
|
||||
(a.data.slot / spec.epoch_length).epoch(spec.epoch_length)
|
||||
== self.previous_epoch(spec)
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug!(
|
||||
"previous epoch attestations: {}",
|
||||
previous_epoch_attestations.len()
|
||||
);
|
||||
|
||||
let previous_epoch_attester_indices =
|
||||
self.get_attestation_participants_union(&previous_epoch_attestations[..], spec)?;
|
||||
let previous_total_balance =
|
||||
self.get_total_balance(&previous_epoch_attester_indices[..], spec);
|
||||
|
||||
/*
|
||||
* Validators targetting the previous justified slot
|
||||
*/
|
||||
let previous_epoch_justified_attestations: Vec<&PendingAttestation> = {
|
||||
let mut a: Vec<&PendingAttestation> = current_epoch_attestations
|
||||
.iter()
|
||||
.filter(|a| a.data.justified_epoch == self.previous_justified_epoch)
|
||||
.cloned()
|
||||
.collect();
|
||||
let mut b: Vec<&PendingAttestation> = previous_epoch_attestations
|
||||
.iter()
|
||||
.filter(|a| a.data.justified_epoch == self.previous_justified_epoch)
|
||||
.cloned()
|
||||
.collect();
|
||||
a.append(&mut b);
|
||||
a
|
||||
};
|
||||
|
||||
let previous_epoch_justified_attester_indices = self
|
||||
.get_attestation_participants_union(&previous_epoch_justified_attestations[..], spec)?;
|
||||
let previous_epoch_justified_attesting_balance =
|
||||
self.get_total_balance(&previous_epoch_justified_attester_indices[..], spec);
|
||||
|
||||
/*
|
||||
* Validators justifying the epoch boundary block at the start of the previous epoch
|
||||
*/
|
||||
let previous_epoch_boundary_attestations: Vec<&PendingAttestation> =
|
||||
previous_epoch_justified_attestations
|
||||
.iter()
|
||||
.filter(
|
||||
|a| match self.get_block_root(self.previous_epoch_start_slot(spec), spec) {
|
||||
Some(block_root) => a.data.epoch_boundary_root == *block_root,
|
||||
None => unreachable!(),
|
||||
},
|
||||
)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let previous_epoch_boundary_attester_indices = self
|
||||
.get_attestation_participants_union(&previous_epoch_boundary_attestations[..], spec)?;
|
||||
let previous_epoch_boundary_attesting_balance =
|
||||
self.get_total_balance(&previous_epoch_boundary_attester_indices[..], spec);
|
||||
|
||||
/*
|
||||
* Validators attesting to the expected beacon chain head during the previous epoch.
|
||||
*/
|
||||
let previous_epoch_head_attestations: Vec<&PendingAttestation> =
|
||||
previous_epoch_attestations
|
||||
.iter()
|
||||
.filter(|a| match self.get_block_root(a.data.slot, spec) {
|
||||
Some(block_root) => a.data.beacon_block_root == *block_root,
|
||||
None => unreachable!(),
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let previous_epoch_head_attester_indices =
|
||||
self.get_attestation_participants_union(&previous_epoch_head_attestations[..], spec)?;
|
||||
let previous_epoch_head_attesting_balance =
|
||||
self.get_total_balance(&previous_epoch_head_attester_indices[..], spec);
|
||||
|
||||
debug!(
|
||||
"previous_epoch_head_attester_balance of {} wei.",
|
||||
previous_epoch_head_attesting_balance
|
||||
);
|
||||
|
||||
/*
|
||||
* Eth1 Data
|
||||
*/
|
||||
if self.next_epoch(spec) % spec.eth1_data_voting_period == 0 {
|
||||
for eth1_data_vote in &self.eth1_data_votes {
|
||||
if eth1_data_vote.vote_count * 2 > spec.eth1_data_voting_period {
|
||||
self.latest_eth1_data = eth1_data_vote.eth1_data.clone();
|
||||
}
|
||||
}
|
||||
self.eth1_data_votes = vec![];
|
||||
}
|
||||
|
||||
/*
|
||||
* Justification
|
||||
*/
|
||||
|
||||
let mut new_justified_epoch = self.justified_epoch;
|
||||
self.justification_bitfield <<= 1;
|
||||
|
||||
// If > 2/3 of the total balance attested to the previous epoch boundary
|
||||
//
|
||||
// - Set the 2nd bit of the bitfield.
|
||||
// - Set the previous epoch to be justified.
|
||||
if (3 * previous_epoch_boundary_attesting_balance) >= (2 * current_total_balance) {
|
||||
self.justification_bitfield |= 2;
|
||||
new_justified_epoch = previous_epoch;
|
||||
trace!(">= 2/3 voted for previous epoch boundary");
|
||||
}
|
||||
// If > 2/3 of the total balance attested to the previous epoch boundary
|
||||
//
|
||||
// - Set the 1st bit of the bitfield.
|
||||
// - Set the current epoch to be justified.
|
||||
if (3 * current_epoch_boundary_attesting_balance) >= (2 * current_total_balance) {
|
||||
self.justification_bitfield |= 1;
|
||||
new_justified_epoch = current_epoch;
|
||||
trace!(">= 2/3 voted for current epoch boundary");
|
||||
}
|
||||
|
||||
// If:
|
||||
//
|
||||
// - All three epochs prior to this epoch have been justified.
|
||||
// - The previous justified justified epoch was three epochs ago.
|
||||
//
|
||||
// Then, set the finalized epoch to be three epochs ago.
|
||||
if ((self.justification_bitfield >> 1) % 8 == 0b111)
|
||||
& (self.previous_justified_epoch == previous_epoch - 2)
|
||||
{
|
||||
self.finalized_epoch = self.previous_justified_epoch;
|
||||
trace!("epoch - 3 was finalized (1st condition).");
|
||||
}
|
||||
// If:
|
||||
//
|
||||
// - Both two epochs prior to this epoch have been justified.
|
||||
// - The previous justified epoch was two epochs ago.
|
||||
//
|
||||
// Then, set the finalized epoch to two epochs ago.
|
||||
if ((self.justification_bitfield >> 1) % 4 == 0b11)
|
||||
& (self.previous_justified_epoch == previous_epoch - 1)
|
||||
{
|
||||
self.finalized_epoch = self.previous_justified_epoch;
|
||||
trace!("epoch - 2 was finalized (2nd condition).");
|
||||
}
|
||||
// If:
|
||||
//
|
||||
// - This epoch and the two prior have been justified.
|
||||
// - The presently justified epoch was two epochs ago.
|
||||
//
|
||||
// Then, set the finalized epoch to two epochs ago.
|
||||
if (self.justification_bitfield % 8 == 0b111) & (self.justified_epoch == previous_epoch - 1)
|
||||
{
|
||||
self.finalized_epoch = self.justified_epoch;
|
||||
trace!("epoch - 2 was finalized (3rd condition).");
|
||||
}
|
||||
// If:
|
||||
//
|
||||
// - This epoch and the epoch prior to it have been justified.
|
||||
// - Set the previous epoch to be justified.
|
||||
//
|
||||
// Then, set the finalized epoch to be the previous epoch.
|
||||
if (self.justification_bitfield % 4 == 0b11) & (self.justified_epoch == previous_epoch) {
|
||||
self.finalized_epoch = self.justified_epoch;
|
||||
trace!("epoch - 1 was finalized (4th condition).");
|
||||
}
|
||||
|
||||
self.previous_justified_epoch = self.justified_epoch;
|
||||
self.justified_epoch = new_justified_epoch;
|
||||
|
||||
debug!(
|
||||
"Finalized epoch {}, justified epoch {}.",
|
||||
self.finalized_epoch, self.justified_epoch
|
||||
);
|
||||
|
||||
/*
|
||||
* Crosslinks
|
||||
*/
|
||||
|
||||
// Cached for later lookups.
|
||||
let mut winning_root_for_shards: HashMap<u64, Result<WinningRoot, WinningRootError>> =
|
||||
HashMap::new();
|
||||
|
||||
// for slot in self.slot.saturating_sub(2 * spec.epoch_length)..self.slot {
|
||||
for slot in self.previous_epoch(spec).slot_iter(spec.epoch_length) {
|
||||
let crosslink_committees_at_slot =
|
||||
self.get_crosslink_committees_at_slot(slot, false, spec)?;
|
||||
|
||||
for (crosslink_committee, shard) in crosslink_committees_at_slot {
|
||||
let shard = shard as u64;
|
||||
|
||||
let winning_root = winning_root(
|
||||
self,
|
||||
shard,
|
||||
¤t_epoch_attestations,
|
||||
&previous_epoch_attestations,
|
||||
spec,
|
||||
);
|
||||
|
||||
if let Ok(winning_root) = &winning_root {
|
||||
let total_committee_balance =
|
||||
self.get_total_balance(&crosslink_committee[..], spec);
|
||||
|
||||
if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) {
|
||||
self.latest_crosslinks[shard as usize] = Crosslink {
|
||||
epoch: current_epoch,
|
||||
shard_block_root: winning_root.shard_block_root,
|
||||
}
|
||||
}
|
||||
}
|
||||
winning_root_for_shards.insert(shard, winning_root);
|
||||
}
|
||||
}
|
||||
|
||||
trace!(
|
||||
"Found {} winning shard roots.",
|
||||
winning_root_for_shards.len()
|
||||
);
|
||||
|
||||
/*
|
||||
* Rewards and Penalities
|
||||
*/
|
||||
let base_reward_quotient = previous_total_balance.integer_sqrt();
|
||||
if base_reward_quotient == 0 {
|
||||
return Err(Error::BaseRewardQuotientIsZero);
|
||||
}
|
||||
|
||||
/*
|
||||
* Justification and finalization
|
||||
*/
|
||||
let epochs_since_finality = next_epoch - self.finalized_epoch;
|
||||
|
||||
let previous_epoch_justified_attester_indices_hashset: HashSet<usize> =
|
||||
HashSet::from_iter(previous_epoch_justified_attester_indices.iter().cloned());
|
||||
let previous_epoch_boundary_attester_indices_hashset: HashSet<usize> =
|
||||
HashSet::from_iter(previous_epoch_boundary_attester_indices.iter().cloned());
|
||||
let previous_epoch_head_attester_indices_hashset: HashSet<usize> =
|
||||
HashSet::from_iter(previous_epoch_head_attester_indices.iter().cloned());
|
||||
let previous_epoch_attester_indices_hashset: HashSet<usize> =
|
||||
HashSet::from_iter(previous_epoch_attester_indices.iter().cloned());
|
||||
let active_validator_indices_hashset: HashSet<usize> =
|
||||
HashSet::from_iter(active_validator_indices.iter().cloned());
|
||||
|
||||
debug!("previous epoch justified attesters: {}, previous epoch boundary attesters: {}, previous epoch head attesters: {}, previous epoch attesters: {}", previous_epoch_justified_attester_indices.len(), previous_epoch_boundary_attester_indices.len(), previous_epoch_head_attester_indices.len(), previous_epoch_attester_indices.len());
|
||||
|
||||
debug!("{} epochs since finality.", epochs_since_finality);
|
||||
|
||||
if epochs_since_finality <= 4 {
|
||||
for index in 0..self.validator_balances.len() {
|
||||
let base_reward = self.base_reward(index, base_reward_quotient, spec);
|
||||
|
||||
if previous_epoch_justified_attester_indices_hashset.contains(&index) {
|
||||
safe_add_assign!(
|
||||
self.validator_balances[index],
|
||||
base_reward * previous_epoch_justified_attesting_balance
|
||||
/ previous_total_balance
|
||||
);
|
||||
} else if active_validator_indices_hashset.contains(&index) {
|
||||
safe_sub_assign!(self.validator_balances[index], base_reward);
|
||||
}
|
||||
|
||||
if previous_epoch_boundary_attester_indices_hashset.contains(&index) {
|
||||
safe_add_assign!(
|
||||
self.validator_balances[index],
|
||||
base_reward * previous_epoch_boundary_attesting_balance
|
||||
/ previous_total_balance
|
||||
);
|
||||
} else if active_validator_indices_hashset.contains(&index) {
|
||||
safe_sub_assign!(self.validator_balances[index], base_reward);
|
||||
}
|
||||
|
||||
if previous_epoch_head_attester_indices_hashset.contains(&index) {
|
||||
safe_add_assign!(
|
||||
self.validator_balances[index],
|
||||
base_reward * previous_epoch_head_attesting_balance
|
||||
/ previous_total_balance
|
||||
);
|
||||
} else if active_validator_indices_hashset.contains(&index) {
|
||||
safe_sub_assign!(self.validator_balances[index], base_reward);
|
||||
}
|
||||
}
|
||||
|
||||
for index in previous_epoch_attester_indices {
|
||||
let base_reward = self.base_reward(index, base_reward_quotient, spec);
|
||||
let inclusion_distance =
|
||||
self.inclusion_distance(&previous_epoch_attestations, index, spec)?;
|
||||
|
||||
safe_add_assign!(
|
||||
self.validator_balances[index],
|
||||
base_reward * spec.min_attestation_inclusion_delay / inclusion_distance
|
||||
)
|
||||
}
|
||||
} else {
|
||||
for index in 0..self.validator_balances.len() {
|
||||
let inactivity_penalty = self.inactivity_penalty(
|
||||
index,
|
||||
epochs_since_finality,
|
||||
base_reward_quotient,
|
||||
spec,
|
||||
);
|
||||
if active_validator_indices_hashset.contains(&index) {
|
||||
if !previous_epoch_justified_attester_indices_hashset.contains(&index) {
|
||||
safe_sub_assign!(self.validator_balances[index], inactivity_penalty);
|
||||
}
|
||||
if !previous_epoch_boundary_attester_indices_hashset.contains(&index) {
|
||||
safe_sub_assign!(self.validator_balances[index], inactivity_penalty);
|
||||
}
|
||||
if !previous_epoch_head_attester_indices_hashset.contains(&index) {
|
||||
safe_sub_assign!(self.validator_balances[index], inactivity_penalty);
|
||||
}
|
||||
|
||||
if self.validator_registry[index].penalized_epoch <= current_epoch {
|
||||
let base_reward = self.base_reward(index, base_reward_quotient, spec);
|
||||
safe_sub_assign!(
|
||||
self.validator_balances[index],
|
||||
2 * inactivity_penalty + base_reward
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for index in previous_epoch_attester_indices {
|
||||
let base_reward = self.base_reward(index, base_reward_quotient, spec);
|
||||
let inclusion_distance =
|
||||
self.inclusion_distance(&previous_epoch_attestations, index, spec)?;
|
||||
|
||||
safe_sub_assign!(
|
||||
self.validator_balances[index],
|
||||
base_reward
|
||||
- base_reward * spec.min_attestation_inclusion_delay / inclusion_distance
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
trace!("Processed validator justification and finalization rewards/penalities.");
|
||||
|
||||
/*
|
||||
* Attestation inclusion
|
||||
*/
|
||||
for &index in &previous_epoch_attester_indices_hashset {
|
||||
let inclusion_slot =
|
||||
self.inclusion_slot(&previous_epoch_attestations[..], index, spec)?;
|
||||
let proposer_index = self
|
||||
.get_beacon_proposer_index(inclusion_slot, spec)
|
||||
.map_err(|_| Error::UnableToDetermineProducer)?;
|
||||
let base_reward = self.base_reward(proposer_index, base_reward_quotient, spec);
|
||||
safe_add_assign!(
|
||||
self.validator_balances[proposer_index],
|
||||
base_reward / spec.includer_reward_quotient
|
||||
);
|
||||
}
|
||||
|
||||
trace!(
|
||||
"Previous epoch attesters: {}.",
|
||||
previous_epoch_attester_indices_hashset.len()
|
||||
);
|
||||
|
||||
/*
|
||||
* Crosslinks
|
||||
*/
|
||||
for slot in self.previous_epoch(spec).slot_iter(spec.epoch_length) {
|
||||
let crosslink_committees_at_slot =
|
||||
self.get_crosslink_committees_at_slot(slot, false, spec)?;
|
||||
|
||||
for (_crosslink_committee, shard) in crosslink_committees_at_slot {
|
||||
let shard = shard as u64;
|
||||
|
||||
if let Some(Ok(winning_root)) = winning_root_for_shards.get(&shard) {
|
||||
// TODO: remove the map.
|
||||
let attesting_validator_indices: HashSet<usize> = HashSet::from_iter(
|
||||
winning_root.attesting_validator_indices.iter().cloned(),
|
||||
);
|
||||
|
||||
for index in 0..self.validator_balances.len() {
|
||||
let base_reward = self.base_reward(index, base_reward_quotient, spec);
|
||||
|
||||
if attesting_validator_indices.contains(&index) {
|
||||
safe_add_assign!(
|
||||
self.validator_balances[index],
|
||||
base_reward * winning_root.total_attesting_balance
|
||||
/ winning_root.total_balance
|
||||
);
|
||||
} else {
|
||||
safe_sub_assign!(self.validator_balances[index], base_reward);
|
||||
}
|
||||
}
|
||||
|
||||
for index in &winning_root.attesting_validator_indices {
|
||||
let base_reward = self.base_reward(*index, base_reward_quotient, spec);
|
||||
safe_add_assign!(
|
||||
self.validator_balances[*index],
|
||||
base_reward * winning_root.total_attesting_balance
|
||||
/ winning_root.total_balance
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Ejections
|
||||
*/
|
||||
self.process_ejections(spec);
|
||||
|
||||
/*
|
||||
* Validator Registry
|
||||
*/
|
||||
self.previous_calculation_epoch = self.current_calculation_epoch;
|
||||
self.previous_epoch_start_shard = self.current_epoch_start_shard;
|
||||
self.previous_epoch_seed = self.current_epoch_seed;
|
||||
|
||||
let should_update_validator_registy = if self.finalized_epoch
|
||||
> self.validator_registry_update_epoch
|
||||
{
|
||||
(0..self.get_current_epoch_committee_count(spec)).all(|i| {
|
||||
let shard = (self.current_epoch_start_shard + i as u64) % spec.shard_count;
|
||||
self.latest_crosslinks[shard as usize].epoch > self.validator_registry_update_epoch
|
||||
})
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if should_update_validator_registy {
|
||||
self.update_validator_registry(spec);
|
||||
|
||||
self.current_calculation_epoch = next_epoch;
|
||||
self.current_epoch_start_shard = (self.current_epoch_start_shard
|
||||
+ self.get_current_epoch_committee_count(spec) as u64)
|
||||
% spec.shard_count;
|
||||
self.current_epoch_seed = self
|
||||
.generate_seed(self.current_calculation_epoch, spec)
|
||||
.ok_or_else(|| Error::NoRandaoSeed)?;
|
||||
} else {
|
||||
let epochs_since_last_registry_update =
|
||||
current_epoch - self.validator_registry_update_epoch;
|
||||
if (epochs_since_last_registry_update > 1)
|
||||
& epochs_since_last_registry_update.is_power_of_two()
|
||||
{
|
||||
self.current_calculation_epoch = next_epoch;
|
||||
self.current_epoch_seed = self
|
||||
.generate_seed(self.current_calculation_epoch, spec)
|
||||
.ok_or_else(|| Error::NoRandaoSeed)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.process_penalties_and_exits(spec);
|
||||
|
||||
self.latest_index_roots[(next_epoch.as_usize() + spec.entry_exit_delay as usize)
|
||||
% spec.latest_index_roots_length] = hash_tree_root(get_active_validator_indices(
|
||||
&self.validator_registry,
|
||||
next_epoch + Epoch::from(spec.entry_exit_delay),
|
||||
));
|
||||
self.latest_penalized_balances[next_epoch.as_usize() % spec.latest_penalized_exit_length] =
|
||||
self.latest_penalized_balances
|
||||
[current_epoch.as_usize() % spec.latest_penalized_exit_length];
|
||||
self.latest_randao_mixes[next_epoch.as_usize() % spec.latest_randao_mixes_length] = self
|
||||
.get_randao_mix(current_epoch, spec)
|
||||
.and_then(|x| Some(*x))
|
||||
.ok_or_else(|| Error::NoRandaoSeed)?;
|
||||
self.latest_attestations = self
|
||||
.latest_attestations
|
||||
.iter()
|
||||
.filter(|a| a.data.slot.epoch(spec.epoch_length) >= current_epoch)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
debug!("Epoch transition complete.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_tree_root<T: TreeHash>(input: Vec<T>) -> Hash256 {
|
||||
Hash256::from(&input.hash_tree_root()[..])
|
||||
}
|
||||
|
||||
fn winning_root(
|
||||
state: &BeaconState,
|
||||
shard: u64,
|
||||
current_epoch_attestations: &[&PendingAttestation],
|
||||
previous_epoch_attestations: &[&PendingAttestation],
|
||||
spec: &ChainSpec,
|
||||
) -> Result<WinningRoot, WinningRootError> {
|
||||
let mut attestations = current_epoch_attestations.to_vec();
|
||||
attestations.append(&mut previous_epoch_attestations.to_vec());
|
||||
|
||||
let mut candidates: HashMap<Hash256, WinningRoot> = HashMap::new();
|
||||
|
||||
let mut highest_seen_balance = 0;
|
||||
|
||||
for a in &attestations {
|
||||
if a.data.shard != shard {
|
||||
continue;
|
||||
}
|
||||
|
||||
let shard_block_root = &a.data.shard_block_root;
|
||||
|
||||
if candidates.contains_key(shard_block_root) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: `cargo fmt` makes this rather ugly; tidy up.
|
||||
let attesting_validator_indices = attestations.iter().try_fold::<_, _, Result<
|
||||
_,
|
||||
AttestationParticipantsError,
|
||||
>>(vec![], |mut acc, a| {
|
||||
if (a.data.shard == shard) && (a.data.shard_block_root == *shard_block_root) {
|
||||
acc.append(&mut state.get_attestation_participants(
|
||||
&a.data,
|
||||
&a.aggregation_bitfield,
|
||||
spec,
|
||||
)?);
|
||||
}
|
||||
Ok(acc)
|
||||
})?;
|
||||
|
||||
let total_balance: u64 = attesting_validator_indices
|
||||
.iter()
|
||||
.fold(0, |acc, i| acc + state.get_effective_balance(*i, spec));
|
||||
|
||||
let total_attesting_balance: u64 = attesting_validator_indices
|
||||
.iter()
|
||||
.fold(0, |acc, i| acc + state.get_effective_balance(*i, spec));
|
||||
|
||||
if total_attesting_balance > highest_seen_balance {
|
||||
highest_seen_balance = total_attesting_balance;
|
||||
}
|
||||
|
||||
let candidate_root = WinningRoot {
|
||||
shard_block_root: *shard_block_root,
|
||||
attesting_validator_indices,
|
||||
total_attesting_balance,
|
||||
total_balance,
|
||||
};
|
||||
|
||||
candidates.insert(*shard_block_root, candidate_root);
|
||||
}
|
||||
|
||||
Ok(candidates
|
||||
.iter()
|
||||
.filter_map(|(_hash, candidate)| {
|
||||
if candidate.total_attesting_balance == highest_seen_balance {
|
||||
Some(candidate)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.min_by_key(|candidate| candidate.shard_block_root)
|
||||
.ok_or_else(|| WinningRootError::NoWinningRoot)?
|
||||
// TODO: avoid clone.
|
||||
.clone())
|
||||
}
|
||||
|
||||
impl From<InclusionError> for Error {
|
||||
fn from(e: InclusionError) -> Error {
|
||||
Error::InclusionError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommitteesError> for Error {
|
||||
fn from(e: CommitteesError) -> Error {
|
||||
Error::CommitteesError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttestationParticipantsError> for Error {
|
||||
fn from(e: AttestationParticipantsError) -> Error {
|
||||
Error::AttestationParticipantsError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AttestationParticipantsError> for WinningRootError {
|
||||
fn from(e: AttestationParticipantsError) -> WinningRootError {
|
||||
WinningRootError::AttestationParticipantsError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
10
eth2/state_processing/src/lib.rs
Normal file
10
eth2/state_processing/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
||||
mod block_processable;
|
||||
mod epoch_processable;
|
||||
mod slot_processable;
|
||||
|
||||
pub use block_processable::{
|
||||
validate_attestation, validate_attestation_without_signature, BlockProcessable,
|
||||
Error as BlockProcessingError,
|
||||
};
|
||||
pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError};
|
||||
pub use slot_processable::{Error as SlotProcessingError, SlotProcessable};
|
70
eth2/state_processing/src/slot_processable.rs
Normal file
70
eth2/state_processing/src/slot_processable.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use crate::{EpochProcessable, EpochProcessingError};
|
||||
use types::{beacon_state::CommitteesError, BeaconState, ChainSpec, Hash256};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
CommitteesError(CommitteesError),
|
||||
EpochProcessingError(EpochProcessingError),
|
||||
}
|
||||
|
||||
pub trait SlotProcessable {
|
||||
fn per_slot_processing(
|
||||
&mut self,
|
||||
previous_block_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl SlotProcessable for BeaconState
|
||||
where
|
||||
BeaconState: EpochProcessable,
|
||||
{
|
||||
fn per_slot_processing(
|
||||
&mut self,
|
||||
previous_block_root: Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if (self.slot + 1) % spec.epoch_length == 0 {
|
||||
self.per_epoch_processing(spec)?;
|
||||
}
|
||||
|
||||
self.slot += 1;
|
||||
|
||||
self.latest_randao_mixes[self.slot.as_usize() % spec.latest_randao_mixes_length] =
|
||||
self.latest_randao_mixes[(self.slot.as_usize() - 1) % spec.latest_randao_mixes_length];
|
||||
|
||||
// Block roots.
|
||||
self.latest_block_roots[(self.slot.as_usize() - 1) % spec.latest_block_roots_length] =
|
||||
previous_block_root;
|
||||
|
||||
if self.slot.as_usize() % spec.latest_block_roots_length == 0 {
|
||||
let root = merkle_root(&self.latest_block_roots[..]);
|
||||
self.batched_block_roots.push(root);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn merkle_root(_input: &[Hash256]) -> Hash256 {
|
||||
Hash256::zero()
|
||||
}
|
||||
|
||||
impl From<CommitteesError> for Error {
|
||||
fn from(e: CommitteesError) -> Error {
|
||||
Error::CommitteesError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EpochProcessingError> for Error {
|
||||
fn from(e: EpochProcessingError) -> Error {
|
||||
Error::EpochProcessingError(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
@ -10,12 +10,12 @@ boolean-bitfield = { path = "../utils/boolean-bitfield" }
|
||||
ethereum-types = "0.4.0"
|
||||
hashing = { path = "../utils/hashing" }
|
||||
honey-badger-split = { path = "../utils/honey-badger-split" }
|
||||
integer-sqrt = "0.1"
|
||||
log = "0.4"
|
||||
rayon = "1.0"
|
||||
rand = "0.5.5"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
slog = "^2.2.3"
|
||||
ssz = { path = "../utils/ssz" }
|
||||
vec_shuffle = { path = "../utils/vec_shuffle" }
|
||||
|
@ -1,14 +1,13 @@
|
||||
use super::{AttestationData, Bitfield, Hash256};
|
||||
use super::{AggregatePublicKey, AggregateSignature, AttestationData, Bitfield, Hash256};
|
||||
use crate::test_utils::TestRandom;
|
||||
use bls::AggregateSignature;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct Attestation {
|
||||
pub data: AttestationData,
|
||||
pub aggregation_bitfield: Bitfield,
|
||||
pub data: AttestationData,
|
||||
pub custody_bitfield: Bitfield,
|
||||
pub aggregate_signature: AggregateSignature,
|
||||
}
|
||||
@ -21,12 +20,23 @@ impl Attestation {
|
||||
pub fn signable_message(&self, custody_bit: bool) -> Vec<u8> {
|
||||
self.data.signable_message(custody_bit)
|
||||
}
|
||||
|
||||
pub fn verify_signature(
|
||||
&self,
|
||||
group_public_key: &AggregatePublicKey,
|
||||
custody_bit: bool,
|
||||
// TODO: use domain.
|
||||
_domain: u64,
|
||||
) -> bool {
|
||||
self.aggregate_signature
|
||||
.verify(&self.signable_message(custody_bit), group_public_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for Attestation {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.data);
|
||||
s.append(&self.aggregation_bitfield);
|
||||
s.append(&self.data);
|
||||
s.append(&self.custody_bitfield);
|
||||
s.append(&self.aggregate_signature);
|
||||
}
|
||||
@ -34,14 +44,14 @@ impl Encodable for Attestation {
|
||||
|
||||
impl Decodable for Attestation {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (data, i) = AttestationData::ssz_decode(bytes, i)?;
|
||||
let (aggregation_bitfield, i) = Bitfield::ssz_decode(bytes, i)?;
|
||||
let (data, i) = AttestationData::ssz_decode(bytes, i)?;
|
||||
let (custody_bitfield, i) = Bitfield::ssz_decode(bytes, i)?;
|
||||
let (aggregate_signature, i) = AggregateSignature::ssz_decode(bytes, i)?;
|
||||
|
||||
let attestation_record = Self {
|
||||
data,
|
||||
aggregation_bitfield,
|
||||
data,
|
||||
custody_bitfield,
|
||||
aggregate_signature,
|
||||
};
|
||||
@ -49,22 +59,11 @@ impl Decodable for Attestation {
|
||||
}
|
||||
}
|
||||
|
||||
impl Attestation {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
data: AttestationData::zero(),
|
||||
aggregation_bitfield: Bitfield::new(),
|
||||
custody_bitfield: Bitfield::new(),
|
||||
aggregate_signature: AggregateSignature::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeHash for Attestation {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.data.hash_tree_root());
|
||||
result.append(&mut self.aggregation_bitfield.hash_tree_root());
|
||||
result.append(&mut self.data.hash_tree_root());
|
||||
result.append(&mut self.custody_bitfield.hash_tree_root());
|
||||
result.append(&mut self.aggregate_signature.hash_tree_root());
|
||||
hash(&result)
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::{AttestationDataAndCustodyBit, Hash256};
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{AttestationDataAndCustodyBit, Crosslink, Epoch, Hash256, Slot};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
@ -11,38 +11,25 @@ pub const SSZ_ATTESTION_DATA_LENGTH: usize = {
|
||||
32 + // epoch_boundary_root
|
||||
32 + // shard_block_hash
|
||||
32 + // latest_crosslink_hash
|
||||
8 + // justified_slot
|
||||
8 + // justified_epoch
|
||||
32 // justified_block_root
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Hash)]
|
||||
pub struct AttestationData {
|
||||
pub slot: u64,
|
||||
pub slot: Slot,
|
||||
pub shard: u64,
|
||||
pub beacon_block_root: Hash256,
|
||||
pub epoch_boundary_root: Hash256,
|
||||
pub shard_block_root: Hash256,
|
||||
pub latest_crosslink_root: Hash256,
|
||||
pub justified_slot: u64,
|
||||
pub latest_crosslink: Crosslink,
|
||||
pub justified_epoch: Epoch,
|
||||
pub justified_block_root: Hash256,
|
||||
}
|
||||
|
||||
impl Eq for AttestationData {}
|
||||
|
||||
impl AttestationData {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
slot: 0,
|
||||
shard: 0,
|
||||
beacon_block_root: Hash256::zero(),
|
||||
epoch_boundary_root: Hash256::zero(),
|
||||
shard_block_root: Hash256::zero(),
|
||||
latest_crosslink_root: Hash256::zero(),
|
||||
justified_slot: 0,
|
||||
justified_block_root: Hash256::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canonical_root(&self) -> Hash256 {
|
||||
Hash256::from(&self.hash_tree_root()[..])
|
||||
}
|
||||
@ -63,22 +50,22 @@ impl Encodable for AttestationData {
|
||||
s.append(&self.beacon_block_root);
|
||||
s.append(&self.epoch_boundary_root);
|
||||
s.append(&self.shard_block_root);
|
||||
s.append(&self.latest_crosslink_root);
|
||||
s.append(&self.justified_slot);
|
||||
s.append(&self.latest_crosslink);
|
||||
s.append(&self.justified_epoch);
|
||||
s.append(&self.justified_block_root);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for AttestationData {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (slot, i) = u64::ssz_decode(bytes, i)?;
|
||||
let (shard, i) = u64::ssz_decode(bytes, i)?;
|
||||
let (beacon_block_root, i) = Hash256::ssz_decode(bytes, i)?;
|
||||
let (epoch_boundary_root, i) = Hash256::ssz_decode(bytes, i)?;
|
||||
let (shard_block_root, i) = Hash256::ssz_decode(bytes, i)?;
|
||||
let (latest_crosslink_root, i) = Hash256::ssz_decode(bytes, i)?;
|
||||
let (justified_slot, i) = u64::ssz_decode(bytes, i)?;
|
||||
let (justified_block_root, i) = Hash256::ssz_decode(bytes, i)?;
|
||||
let (slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (shard, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (beacon_block_root, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (epoch_boundary_root, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (shard_block_root, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (latest_crosslink, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (justified_epoch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (justified_block_root, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
let attestation_data = AttestationData {
|
||||
slot,
|
||||
@ -86,8 +73,8 @@ impl Decodable for AttestationData {
|
||||
beacon_block_root,
|
||||
epoch_boundary_root,
|
||||
shard_block_root,
|
||||
latest_crosslink_root,
|
||||
justified_slot,
|
||||
latest_crosslink,
|
||||
justified_epoch,
|
||||
justified_block_root,
|
||||
};
|
||||
Ok((attestation_data, i))
|
||||
@ -102,8 +89,8 @@ impl TreeHash for AttestationData {
|
||||
result.append(&mut self.beacon_block_root.hash_tree_root());
|
||||
result.append(&mut self.epoch_boundary_root.hash_tree_root());
|
||||
result.append(&mut self.shard_block_root.hash_tree_root());
|
||||
result.append(&mut self.latest_crosslink_root.hash_tree_root());
|
||||
result.append(&mut self.justified_slot.hash_tree_root());
|
||||
result.append(&mut self.latest_crosslink.hash_tree_root());
|
||||
result.append(&mut self.justified_epoch.hash_tree_root());
|
||||
result.append(&mut self.justified_block_root.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
@ -117,8 +104,8 @@ impl<T: RngCore> TestRandom<T> for AttestationData {
|
||||
beacon_block_root: <_>::random_for_test(rng),
|
||||
epoch_boundary_root: <_>::random_for_test(rng),
|
||||
shard_block_root: <_>::random_for_test(rng),
|
||||
latest_crosslink_root: <_>::random_for_test(rng),
|
||||
justified_slot: <_>::random_for_test(rng),
|
||||
latest_crosslink: <_>::random_for_test(rng),
|
||||
justified_epoch: <_>::random_for_test(rng),
|
||||
justified_block_root: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use super::AttestationData;
|
||||
use crate::test_utils::TestRandom;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
use ssz::{Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
pub struct AttestationDataAndCustodyBit {
|
||||
|
80
eth2/types/src/attester_slashing.rs
Normal file
80
eth2/types/src/attester_slashing.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::{test_utils::TestRandom, SlashableAttestation};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
pub struct AttesterSlashing {
|
||||
pub slashable_attestation_1: SlashableAttestation,
|
||||
pub slashable_attestation_2: SlashableAttestation,
|
||||
}
|
||||
|
||||
impl Encodable for AttesterSlashing {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.slashable_attestation_1);
|
||||
s.append(&self.slashable_attestation_2);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for AttesterSlashing {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (slashable_attestation_1, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (slashable_attestation_2, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
AttesterSlashing {
|
||||
slashable_attestation_1,
|
||||
slashable_attestation_2,
|
||||
},
|
||||
i,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeHash for AttesterSlashing {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.slashable_attestation_1.hash_tree_root());
|
||||
result.append(&mut self.slashable_attestation_2.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RngCore> TestRandom<T> for AttesterSlashing {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
Self {
|
||||
slashable_attestation_1: <_>::random_for_test(rng),
|
||||
slashable_attestation_2: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use ssz::ssz_encode;
|
||||
|
||||
#[test]
|
||||
pub fn test_ssz_round_trip() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = AttesterSlashing::random_for_test(&mut rng);
|
||||
|
||||
let bytes = ssz_encode(&original);
|
||||
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
|
||||
|
||||
assert_eq!(original, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_hash_tree_root() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = AttesterSlashing::random_for_test(&mut rng);
|
||||
|
||||
let result = original.hash_tree_root();
|
||||
|
||||
assert_eq!(result.len(), 32);
|
||||
// TODO: Add further tests
|
||||
// https://github.com/sigp/lighthouse/issues/170
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use super::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, ProposalSignedData};
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, ProposalSignedData, Slot};
|
||||
use bls::Signature;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
@ -7,7 +7,7 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
pub struct BeaconBlock {
|
||||
pub slot: u64,
|
||||
pub slot: Slot,
|
||||
pub parent_root: Hash256,
|
||||
pub state_root: Hash256,
|
||||
pub randao_reveal: Signature,
|
||||
@ -17,6 +17,28 @@ pub struct BeaconBlock {
|
||||
}
|
||||
|
||||
impl BeaconBlock {
|
||||
/// Produce the first block of the Beacon Chain.
|
||||
pub fn genesis(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock {
|
||||
BeaconBlock {
|
||||
slot: spec.genesis_slot,
|
||||
parent_root: spec.zero_hash,
|
||||
state_root,
|
||||
randao_reveal: spec.empty_signature.clone(),
|
||||
eth1_data: Eth1Data {
|
||||
deposit_root: spec.zero_hash,
|
||||
block_hash: spec.zero_hash,
|
||||
},
|
||||
signature: spec.empty_signature.clone(),
|
||||
body: BeaconBlockBody {
|
||||
proposer_slashings: vec![],
|
||||
attester_slashings: vec![],
|
||||
attestations: vec![],
|
||||
deposits: vec![],
|
||||
exits: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canonical_root(&self) -> Hash256 {
|
||||
Hash256::from(&self.hash_tree_root()[..])
|
||||
}
|
||||
@ -33,7 +55,7 @@ impl BeaconBlock {
|
||||
shard: spec.beacon_chain_shard_number,
|
||||
block_root: block_without_signature_root,
|
||||
};
|
||||
Hash256::from_slice(&proposal.hash_tree_root()[..])
|
||||
Hash256::from(&proposal.hash_tree_root()[..])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,23 +1,14 @@
|
||||
use super::{Attestation, CasperSlashing, Deposit, Exit, ProposerSlashing};
|
||||
use super::{Attestation, AttesterSlashing, Deposit, Exit, ProposerSlashing};
|
||||
use crate::test_utils::TestRandom;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
// The following types are just dummy classes as they will not be defined until
|
||||
// Phase 1 (Sharding phase)
|
||||
type CustodyReseed = usize;
|
||||
type CustodyChallenge = usize;
|
||||
type CustodyResponse = usize;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize)]
|
||||
pub struct BeaconBlockBody {
|
||||
pub proposer_slashings: Vec<ProposerSlashing>,
|
||||
pub casper_slashings: Vec<CasperSlashing>,
|
||||
pub attester_slashings: Vec<AttesterSlashing>,
|
||||
pub attestations: Vec<Attestation>,
|
||||
pub custody_reseeds: Vec<CustodyReseed>,
|
||||
pub custody_challenges: Vec<CustodyChallenge>,
|
||||
pub custody_responses: Vec<CustodyResponse>,
|
||||
pub deposits: Vec<Deposit>,
|
||||
pub exits: Vec<Exit>,
|
||||
}
|
||||
@ -25,11 +16,8 @@ pub struct BeaconBlockBody {
|
||||
impl Encodable for BeaconBlockBody {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append_vec(&self.proposer_slashings);
|
||||
s.append_vec(&self.casper_slashings);
|
||||
s.append_vec(&self.attester_slashings);
|
||||
s.append_vec(&self.attestations);
|
||||
s.append_vec(&self.custody_reseeds);
|
||||
s.append_vec(&self.custody_challenges);
|
||||
s.append_vec(&self.custody_responses);
|
||||
s.append_vec(&self.deposits);
|
||||
s.append_vec(&self.exits);
|
||||
}
|
||||
@ -38,22 +26,16 @@ impl Encodable for BeaconBlockBody {
|
||||
impl Decodable for BeaconBlockBody {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (proposer_slashings, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (casper_slashings, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (attester_slashings, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (attestations, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (custody_reseeds, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (custody_challenges, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (custody_responses, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (deposits, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (exits, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
proposer_slashings,
|
||||
casper_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
custody_reseeds,
|
||||
custody_challenges,
|
||||
custody_responses,
|
||||
deposits,
|
||||
exits,
|
||||
},
|
||||
@ -66,11 +48,8 @@ impl TreeHash for BeaconBlockBody {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.proposer_slashings.hash_tree_root());
|
||||
result.append(&mut self.casper_slashings.hash_tree_root());
|
||||
result.append(&mut self.attester_slashings.hash_tree_root());
|
||||
result.append(&mut self.attestations.hash_tree_root());
|
||||
result.append(&mut self.custody_reseeds.hash_tree_root());
|
||||
result.append(&mut self.custody_challenges.hash_tree_root());
|
||||
result.append(&mut self.custody_responses.hash_tree_root());
|
||||
result.append(&mut self.deposits.hash_tree_root());
|
||||
result.append(&mut self.exits.hash_tree_root());
|
||||
hash(&result)
|
||||
@ -81,11 +60,8 @@ impl<T: RngCore> TestRandom<T> for BeaconBlockBody {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
Self {
|
||||
proposer_slashings: <_>::random_for_test(rng),
|
||||
casper_slashings: <_>::random_for_test(rng),
|
||||
attester_slashings: <_>::random_for_test(rng),
|
||||
attestations: <_>::random_for_test(rng),
|
||||
custody_reseeds: <_>::random_for_test(rng),
|
||||
custody_challenges: <_>::random_for_test(rng),
|
||||
custody_responses: <_>::random_for_test(rng),
|
||||
deposits: <_>::random_for_test(rng),
|
||||
exits: <_>::random_for_test(rng),
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,12 @@
|
||||
use super::Hash256;
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{Epoch, Hash256};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Hash)]
|
||||
pub struct Crosslink {
|
||||
pub slot: u64,
|
||||
pub epoch: Epoch,
|
||||
pub shard_block_root: Hash256,
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ impl Crosslink {
|
||||
/// Generates a new instance where `dynasty` and `hash` are both zero.
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
slot: 0,
|
||||
epoch: Epoch::new(0),
|
||||
shard_block_root: Hash256::zero(),
|
||||
}
|
||||
}
|
||||
@ -22,19 +22,19 @@ impl Crosslink {
|
||||
|
||||
impl Encodable for Crosslink {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.slot);
|
||||
s.append(&self.epoch);
|
||||
s.append(&self.shard_block_root);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Crosslink {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (epoch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (shard_block_root, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
slot,
|
||||
epoch,
|
||||
shard_block_root,
|
||||
},
|
||||
i,
|
||||
@ -45,7 +45,7 @@ impl Decodable for Crosslink {
|
||||
impl TreeHash for Crosslink {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.slot.hash_tree_root());
|
||||
result.append(&mut self.epoch.hash_tree_root());
|
||||
result.append(&mut self.shard_block_root.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
@ -54,7 +54,7 @@ impl TreeHash for Crosslink {
|
||||
impl<T: RngCore> TestRandom<T> for Crosslink {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
Self {
|
||||
slot: <_>::random_for_test(rng),
|
||||
epoch: <_>::random_for_test(rng),
|
||||
shard_block_root: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
|
@ -6,29 +6,29 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
pub struct Deposit {
|
||||
pub merkle_branch: Vec<Hash256>,
|
||||
pub merkle_tree_index: u64,
|
||||
pub branch: Vec<Hash256>,
|
||||
pub index: u64,
|
||||
pub deposit_data: DepositData,
|
||||
}
|
||||
|
||||
impl Encodable for Deposit {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append_vec(&self.merkle_branch);
|
||||
s.append(&self.merkle_tree_index);
|
||||
s.append_vec(&self.branch);
|
||||
s.append(&self.index);
|
||||
s.append(&self.deposit_data);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Deposit {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (merkle_branch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (merkle_tree_index, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (branch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (index, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (deposit_data, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
merkle_branch,
|
||||
merkle_tree_index,
|
||||
branch,
|
||||
index,
|
||||
deposit_data,
|
||||
},
|
||||
i,
|
||||
@ -39,8 +39,8 @@ impl Decodable for Deposit {
|
||||
impl TreeHash for Deposit {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.merkle_branch.hash_tree_root());
|
||||
result.append(&mut self.merkle_tree_index.hash_tree_root());
|
||||
result.append(&mut self.branch.hash_tree_root());
|
||||
result.append(&mut self.index.hash_tree_root());
|
||||
result.append(&mut self.deposit_data.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
@ -49,8 +49,8 @@ impl TreeHash for Deposit {
|
||||
impl<T: RngCore> TestRandom<T> for Deposit {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
Self {
|
||||
merkle_branch: <_>::random_for_test(rng),
|
||||
merkle_tree_index: <_>::random_for_test(rng),
|
||||
branch: <_>::random_for_test(rng),
|
||||
index: <_>::random_for_test(rng),
|
||||
deposit_data: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{test_utils::TestRandom, Epoch};
|
||||
use bls::Signature;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
@ -6,14 +6,14 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
pub struct Exit {
|
||||
pub slot: u64,
|
||||
pub validator_index: u32,
|
||||
pub epoch: Epoch,
|
||||
pub validator_index: u64,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl Encodable for Exit {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.slot);
|
||||
s.append(&self.epoch);
|
||||
s.append(&self.validator_index);
|
||||
s.append(&self.signature);
|
||||
}
|
||||
@ -21,13 +21,13 @@ impl Encodable for Exit {
|
||||
|
||||
impl Decodable for Exit {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (epoch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (validator_index, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (signature, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
slot,
|
||||
epoch,
|
||||
validator_index,
|
||||
signature,
|
||||
},
|
||||
@ -39,7 +39,7 @@ impl Decodable for Exit {
|
||||
impl TreeHash for Exit {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.slot.hash_tree_root());
|
||||
result.append(&mut self.epoch.hash_tree_root());
|
||||
result.append(&mut self.validator_index.hash_tree_root());
|
||||
result.append(&mut self.signature.hash_tree_root());
|
||||
hash(&result)
|
||||
@ -49,7 +49,7 @@ impl TreeHash for Exit {
|
||||
impl<T: RngCore> TestRandom<T> for Exit {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
Self {
|
||||
slot: <_>::random_for_test(rng),
|
||||
epoch: <_>::random_for_test(rng),
|
||||
validator_index: <_>::random_for_test(rng),
|
||||
signature: <_>::random_for_test(rng),
|
||||
}
|
||||
|
@ -1,34 +1,34 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{test_utils::TestRandom, Epoch};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
pub struct Fork {
|
||||
pub pre_fork_version: u64,
|
||||
pub post_fork_version: u64,
|
||||
pub fork_slot: u64,
|
||||
pub previous_version: u64,
|
||||
pub current_version: u64,
|
||||
pub epoch: Epoch,
|
||||
}
|
||||
|
||||
impl Encodable for Fork {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.pre_fork_version);
|
||||
s.append(&self.post_fork_version);
|
||||
s.append(&self.fork_slot);
|
||||
s.append(&self.previous_version);
|
||||
s.append(&self.current_version);
|
||||
s.append(&self.epoch);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Fork {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (pre_fork_version, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (post_fork_version, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (fork_slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (previous_version, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (current_version, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (epoch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
pre_fork_version,
|
||||
post_fork_version,
|
||||
fork_slot,
|
||||
previous_version,
|
||||
current_version,
|
||||
epoch,
|
||||
},
|
||||
i,
|
||||
))
|
||||
@ -38,9 +38,9 @@ impl Decodable for Fork {
|
||||
impl TreeHash for Fork {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.pre_fork_version.hash_tree_root());
|
||||
result.append(&mut self.post_fork_version.hash_tree_root());
|
||||
result.append(&mut self.fork_slot.hash_tree_root());
|
||||
result.append(&mut self.previous_version.hash_tree_root());
|
||||
result.append(&mut self.current_version.hash_tree_root());
|
||||
result.append(&mut self.epoch.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
}
|
||||
@ -48,9 +48,9 @@ impl TreeHash for Fork {
|
||||
impl<T: RngCore> TestRandom<T> for Fork {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
Self {
|
||||
pre_fork_version: <_>::random_for_test(rng),
|
||||
post_fork_version: <_>::random_for_test(rng),
|
||||
fork_slot: <_>::random_for_test(rng),
|
||||
previous_version: <_>::random_for_test(rng),
|
||||
current_version: <_>::random_for_test(rng),
|
||||
epoch: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ pub mod test_utils;
|
||||
pub mod attestation;
|
||||
pub mod attestation_data;
|
||||
pub mod attestation_data_and_custody_bit;
|
||||
pub mod attester_slashing;
|
||||
pub mod beacon_block;
|
||||
pub mod beacon_block_body;
|
||||
pub mod beacon_state;
|
||||
@ -19,23 +20,23 @@ pub mod free_attestation;
|
||||
pub mod pending_attestation;
|
||||
pub mod proposal_signed_data;
|
||||
pub mod proposer_slashing;
|
||||
pub mod shard_committee;
|
||||
pub mod readers;
|
||||
pub mod shard_reassignment_record;
|
||||
pub mod slashable_attestation;
|
||||
pub mod slashable_vote_data;
|
||||
pub mod slot_epoch_height;
|
||||
pub mod spec;
|
||||
pub mod special_record;
|
||||
pub mod validator;
|
||||
pub mod validator_registry;
|
||||
pub mod validator_registry_delta_block;
|
||||
|
||||
pub mod readers;
|
||||
|
||||
use ethereum_types::{H160, H256, U256};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use crate::attestation::Attestation;
|
||||
pub use crate::attestation_data::AttestationData;
|
||||
pub use crate::attestation_data_and_custody_bit::AttestationDataAndCustodyBit;
|
||||
pub use crate::attester_slashing::AttesterSlashing;
|
||||
pub use crate::beacon_block::BeaconBlock;
|
||||
pub use crate::beacon_block_body::BeaconBlockBody;
|
||||
pub use crate::beacon_state::BeaconState;
|
||||
@ -52,10 +53,10 @@ pub use crate::free_attestation::FreeAttestation;
|
||||
pub use crate::pending_attestation::PendingAttestation;
|
||||
pub use crate::proposal_signed_data::ProposalSignedData;
|
||||
pub use crate::proposer_slashing::ProposerSlashing;
|
||||
pub use crate::shard_committee::ShardCommittee;
|
||||
pub use crate::slashable_attestation::SlashableAttestation;
|
||||
pub use crate::slashable_vote_data::SlashableVoteData;
|
||||
pub use crate::slot_epoch_height::{Epoch, Slot};
|
||||
pub use crate::spec::ChainSpec;
|
||||
pub use crate::special_record::{SpecialRecord, SpecialRecordKind};
|
||||
pub use crate::validator::{StatusFlags as ValidatorStatusFlags, Validator};
|
||||
pub use crate::validator_registry_delta_block::ValidatorRegistryDeltaBlock;
|
||||
|
||||
|
@ -1,39 +1,39 @@
|
||||
use super::{AttestationData, Bitfield};
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{AttestationData, Bitfield, Slot};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct PendingAttestation {
|
||||
pub data: AttestationData,
|
||||
pub aggregation_bitfield: Bitfield,
|
||||
pub data: AttestationData,
|
||||
pub custody_bitfield: Bitfield,
|
||||
pub slot_included: u64,
|
||||
pub inclusion_slot: Slot,
|
||||
}
|
||||
|
||||
impl Encodable for PendingAttestation {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.data);
|
||||
s.append(&self.aggregation_bitfield);
|
||||
s.append(&self.data);
|
||||
s.append(&self.custody_bitfield);
|
||||
s.append(&self.slot_included);
|
||||
s.append(&self.inclusion_slot);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for PendingAttestation {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (data, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (aggregation_bitfield, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (data, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (custody_bitfield, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (slot_included, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (inclusion_slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
data,
|
||||
aggregation_bitfield,
|
||||
custody_bitfield,
|
||||
slot_included,
|
||||
inclusion_slot,
|
||||
},
|
||||
i,
|
||||
))
|
||||
@ -43,10 +43,10 @@ impl Decodable for PendingAttestation {
|
||||
impl TreeHash for PendingAttestation {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.data.hash_tree_root());
|
||||
result.append(&mut self.aggregation_bitfield.hash_tree_root());
|
||||
result.append(&mut self.data.hash_tree_root());
|
||||
result.append(&mut self.custody_bitfield.hash_tree_root());
|
||||
result.append(&mut self.custody_bitfield.hash_tree_root());
|
||||
result.append(&mut self.inclusion_slot.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
}
|
||||
@ -57,7 +57,7 @@ impl<T: RngCore> TestRandom<T> for PendingAttestation {
|
||||
data: <_>::random_for_test(rng),
|
||||
aggregation_bitfield: <_>::random_for_test(rng),
|
||||
custody_bitfield: <_>::random_for_test(rng),
|
||||
slot_included: <_>::random_for_test(rng),
|
||||
inclusion_slot: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
use super::Hash256;
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{Hash256, Slot};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize)]
|
||||
pub struct ProposalSignedData {
|
||||
pub slot: u64,
|
||||
pub slot: Slot,
|
||||
pub shard: u64,
|
||||
pub block_root: Hash256,
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
pub struct ProposerSlashing {
|
||||
pub proposer_index: u32,
|
||||
pub proposer_index: u64,
|
||||
pub proposal_data_1: ProposalSignedData,
|
||||
pub proposal_signature_1: Signature,
|
||||
pub proposal_data_2: ProposalSignedData,
|
||||
|
@ -1,5 +1,4 @@
|
||||
use super::state_reader::BeaconStateReader;
|
||||
use crate::{BeaconBlock, Hash256};
|
||||
use crate::{BeaconBlock, Hash256, Slot};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// The `BeaconBlockReader` provides interfaces for reading a subset of fields of a `BeaconBlock`.
|
||||
@ -11,7 +10,7 @@ use std::fmt::Debug;
|
||||
/// Note: presently, direct SSZ reading has not been implemented so this trait is being used for
|
||||
/// "future proofing".
|
||||
pub trait BeaconBlockReader: Debug + PartialEq {
|
||||
fn slot(&self) -> u64;
|
||||
fn slot(&self) -> Slot;
|
||||
fn parent_root(&self) -> Hash256;
|
||||
fn state_root(&self) -> Hash256;
|
||||
fn canonical_root(&self) -> Hash256;
|
||||
@ -19,7 +18,7 @@ pub trait BeaconBlockReader: Debug + PartialEq {
|
||||
}
|
||||
|
||||
impl BeaconBlockReader for BeaconBlock {
|
||||
fn slot(&self) -> u64 {
|
||||
fn slot(&self) -> Slot {
|
||||
self.slot
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{BeaconState, Hash256};
|
||||
use crate::{BeaconState, Hash256, Slot};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// The `BeaconStateReader` provides interfaces for reading a subset of fields of a `BeaconState`.
|
||||
@ -10,13 +10,13 @@ use std::fmt::Debug;
|
||||
/// Note: presently, direct SSZ reading has not been implemented so this trait is being used for
|
||||
/// "future proofing".
|
||||
pub trait BeaconStateReader: Debug + PartialEq {
|
||||
fn slot(&self) -> u64;
|
||||
fn slot(&self) -> Slot;
|
||||
fn canonical_root(&self) -> Hash256;
|
||||
fn into_beacon_state(self) -> Option<BeaconState>;
|
||||
}
|
||||
|
||||
impl BeaconStateReader for BeaconState {
|
||||
fn slot(&self) -> u64 {
|
||||
fn slot(&self) -> Slot {
|
||||
self.slot
|
||||
}
|
||||
|
||||
|
@ -1,74 +0,0 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
pub struct ShardCommittee {
|
||||
pub shard: u64,
|
||||
pub committee: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Encodable for ShardCommittee {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.shard);
|
||||
s.append(&self.committee);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for ShardCommittee {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (shard, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (committee, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((Self { shard, committee }, i))
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeHash for ShardCommittee {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.shard.hash_tree_root());
|
||||
result.append(&mut self.committee.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RngCore> TestRandom<T> for ShardCommittee {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
Self {
|
||||
shard: <_>::random_for_test(rng),
|
||||
committee: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use ssz::ssz_encode;
|
||||
|
||||
#[test]
|
||||
pub fn test_ssz_round_trip() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = ShardCommittee::random_for_test(&mut rng);
|
||||
|
||||
let bytes = ssz_encode(&original);
|
||||
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
|
||||
|
||||
assert_eq!(original, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_hash_tree_root() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = ShardCommittee::random_for_test(&mut rng);
|
||||
|
||||
let result = original.hash_tree_root();
|
||||
|
||||
assert_eq!(result.len(), 32);
|
||||
// TODO: Add further tests
|
||||
// https://github.com/sigp/lighthouse/issues/170
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{test_utils::TestRandom, Slot};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
@ -7,7 +7,7 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
pub struct ShardReassignmentRecord {
|
||||
pub validator_index: u64,
|
||||
pub shard: u64,
|
||||
pub slot: u64,
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
impl Encodable for ShardReassignmentRecord {
|
||||
|
92
eth2/types/src/slashable_attestation.rs
Normal file
92
eth2/types/src/slashable_attestation.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
pub struct SlashableAttestation {
|
||||
pub validator_indices: Vec<u64>,
|
||||
pub data: AttestationData,
|
||||
pub custody_bitfield: Bitfield,
|
||||
pub aggregate_signature: AggregateSignature,
|
||||
}
|
||||
|
||||
impl Encodable for SlashableAttestation {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append_vec(&self.validator_indices);
|
||||
s.append(&self.data);
|
||||
s.append(&self.custody_bitfield);
|
||||
s.append(&self.aggregate_signature);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for SlashableAttestation {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (validator_indices, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (data, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (custody_bitfield, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (aggregate_signature, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
SlashableAttestation {
|
||||
validator_indices,
|
||||
data,
|
||||
custody_bitfield,
|
||||
aggregate_signature,
|
||||
},
|
||||
i,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeHash for SlashableAttestation {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.validator_indices.hash_tree_root());
|
||||
result.append(&mut self.data.hash_tree_root());
|
||||
result.append(&mut self.custody_bitfield.hash_tree_root());
|
||||
result.append(&mut self.aggregate_signature.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RngCore> TestRandom<T> for SlashableAttestation {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
Self {
|
||||
validator_indices: <_>::random_for_test(rng),
|
||||
data: <_>::random_for_test(rng),
|
||||
custody_bitfield: <_>::random_for_test(rng),
|
||||
aggregate_signature: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use ssz::ssz_encode;
|
||||
|
||||
#[test]
|
||||
pub fn test_ssz_round_trip() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = SlashableAttestation::random_for_test(&mut rng);
|
||||
|
||||
let bytes = ssz_encode(&original);
|
||||
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
|
||||
|
||||
assert_eq!(original, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_hash_tree_root() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = SlashableAttestation::random_for_test(&mut rng);
|
||||
|
||||
let result = original.hash_tree_root();
|
||||
|
||||
assert_eq!(result.len(), 32);
|
||||
// TODO: Add further tests
|
||||
// https://github.com/sigp/lighthouse/issues/170
|
||||
}
|
||||
}
|
763
eth2/types/src/slot_epoch_height.rs
Normal file
763
eth2/types/src/slot_epoch_height.rs
Normal file
@ -0,0 +1,763 @@
|
||||
/// The `Slot` `Epoch`, `Height` types are defined as newtypes over u64 to enforce type-safety between
|
||||
/// the three types.
|
||||
///
|
||||
/// `Slot`, `Epoch` and `Height` have implementations which permit conversion, comparison and math operations
|
||||
/// between each and `u64`, however specifically not between each other.
|
||||
///
|
||||
/// All math operations on `Slot` and `Epoch` are saturating, they never wrap.
|
||||
///
|
||||
/// It would be easy to define `PartialOrd` and other traits generically across all types which
|
||||
/// implement `Into<u64>`, however this would allow operations between `Slots`, `Epochs` and
|
||||
/// `Heights` which may lead to programming errors which are not detected by the compiler.
|
||||
use crate::test_utils::TestRandom;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use slog;
|
||||
use ssz::{hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
use std::cmp::{Ord, Ordering};
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter::Iterator;
|
||||
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign};
|
||||
|
||||
macro_rules! impl_from_into_u64 {
|
||||
($main: ident) => {
|
||||
impl From<u64> for $main {
|
||||
fn from(n: u64) -> $main {
|
||||
$main(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u64> for $main {
|
||||
fn into(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl $main {
|
||||
pub fn as_u64(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// need to truncate for some fork-choice algorithms
|
||||
macro_rules! impl_into_u32 {
|
||||
($main: ident) => {
|
||||
impl Into<u32> for $main {
|
||||
fn into(self) -> u32 {
|
||||
self.0 as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl $main {
|
||||
pub fn as_u32(&self) -> u32 {
|
||||
self.0 as u32
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_from_into_usize {
|
||||
($main: ident) => {
|
||||
impl From<usize> for $main {
|
||||
fn from(n: usize) -> $main {
|
||||
$main(n as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<usize> for $main {
|
||||
fn into(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl $main {
|
||||
pub fn as_usize(&self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_math_between {
|
||||
($main: ident, $other: ident) => {
|
||||
impl PartialOrd<$other> for $main {
|
||||
/// Utilizes `partial_cmp` on the underlying `u64`.
|
||||
fn partial_cmp(&self, other: &$other) -> Option<Ordering> {
|
||||
Some(self.0.cmp(&(*other).into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<$other> for $main {
|
||||
fn eq(&self, other: &$other) -> bool {
|
||||
let other: u64 = (*other).into();
|
||||
self.0 == other
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn add(self, other: $other) -> $main {
|
||||
$main::from(self.0.saturating_add(other.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<$other> for $main {
|
||||
fn add_assign(&mut self, other: $other) {
|
||||
self.0 = self.0.saturating_add(other.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn sub(self, other: $other) -> $main {
|
||||
$main::from(self.0.saturating_sub(other.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<$other> for $main {
|
||||
fn sub_assign(&mut self, other: $other) {
|
||||
self.0 = self.0.saturating_sub(other.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn mul(self, rhs: $other) -> $main {
|
||||
let rhs: u64 = rhs.into();
|
||||
$main::from(self.0.saturating_mul(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<$other> for $main {
|
||||
fn mul_assign(&mut self, rhs: $other) {
|
||||
let rhs: u64 = rhs.into();
|
||||
self.0 = self.0.saturating_mul(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn div(self, rhs: $other) -> $main {
|
||||
let rhs: u64 = rhs.into();
|
||||
if rhs == 0 {
|
||||
panic!("Cannot divide by zero-valued Slot/Epoch")
|
||||
}
|
||||
$main::from(self.0 / rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<$other> for $main {
|
||||
fn div_assign(&mut self, rhs: $other) {
|
||||
let rhs: u64 = rhs.into();
|
||||
if rhs == 0 {
|
||||
panic!("Cannot divide by zero-valued Slot/Epoch")
|
||||
}
|
||||
self.0 = self.0 / rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem<$other> for $main {
|
||||
type Output = $main;
|
||||
|
||||
fn rem(self, modulus: $other) -> $main {
|
||||
let modulus: u64 = modulus.into();
|
||||
$main::from(self.0 % modulus)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_math {
|
||||
($type: ident) => {
|
||||
impl $type {
|
||||
pub fn saturating_sub<T: Into<$type>>(&self, other: T) -> $type {
|
||||
*self - other.into()
|
||||
}
|
||||
|
||||
pub fn saturating_add<T: Into<$type>>(&self, other: T) -> $type {
|
||||
*self + other.into()
|
||||
}
|
||||
|
||||
pub fn checked_div<T: Into<$type>>(&self, rhs: T) -> Option<$type> {
|
||||
let rhs: $type = rhs.into();
|
||||
if rhs == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(*self / rhs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_power_of_two(&self) -> bool {
|
||||
self.0.is_power_of_two()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for $type {
|
||||
fn cmp(&self, other: &$type) -> Ordering {
|
||||
let other: u64 = (*other).into();
|
||||
self.0.cmp(&other)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_display {
|
||||
($type: ident) => {
|
||||
impl fmt::Display for $type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl slog::Value for $type {
|
||||
fn serialize(
|
||||
&self,
|
||||
record: &slog::Record,
|
||||
key: slog::Key,
|
||||
serializer: &mut slog::Serializer,
|
||||
) -> slog::Result {
|
||||
self.0.serialize(record, key, serializer)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_ssz {
|
||||
($type: ident) => {
|
||||
impl Encodable for $type {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for $type {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (value, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok(($type(value), i))
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeHash for $type {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.0.hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RngCore> TestRandom<T> for $type {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
$type::from(u64::random_for_test(rng))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_hash {
|
||||
($type: ident) => {
|
||||
// Implemented to stop clippy lint:
|
||||
// https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
|
||||
impl Hash for $type {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
ssz_encode(self).hash(state)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_common {
|
||||
($type: ident) => {
|
||||
impl_from_into_u64!($type);
|
||||
impl_from_into_usize!($type);
|
||||
impl_math_between!($type, $type);
|
||||
impl_math_between!($type, u64);
|
||||
impl_math!($type);
|
||||
impl_display!($type);
|
||||
impl_ssz!($type);
|
||||
impl_hash!($type);
|
||||
};
|
||||
}
|
||||
|
||||
/// Beacon block slot.
|
||||
#[derive(Eq, Debug, Clone, Copy, Default, Serialize)]
|
||||
pub struct Slot(u64);
|
||||
|
||||
/// Beacon block height, effectively `Slot/GENESIS_START_BLOCK`.
|
||||
#[derive(Eq, Debug, Clone, Copy, Default, Serialize)]
|
||||
pub struct Height(u64);
|
||||
|
||||
/// Beacon Epoch, effectively `Slot / EPOCH_LENGTH`.
|
||||
#[derive(Eq, Debug, Clone, Copy, Default, Serialize)]
|
||||
pub struct Epoch(u64);
|
||||
|
||||
impl_common!(Slot);
|
||||
impl_common!(Height);
|
||||
impl_into_u32!(Height); // height can be converted to u32
|
||||
impl_common!(Epoch);
|
||||
|
||||
impl Slot {
|
||||
pub fn new(slot: u64) -> Slot {
|
||||
Slot(slot)
|
||||
}
|
||||
|
||||
pub fn epoch(self, epoch_length: u64) -> Epoch {
|
||||
Epoch::from(self.0 / epoch_length)
|
||||
}
|
||||
|
||||
pub fn height(self, genesis_slot: Slot) -> Height {
|
||||
Height::from(self.0.saturating_sub(genesis_slot.as_u64()))
|
||||
}
|
||||
|
||||
pub fn max_value() -> Slot {
|
||||
Slot(u64::max_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl Height {
|
||||
pub fn new(slot: u64) -> Height {
|
||||
Height(slot)
|
||||
}
|
||||
|
||||
pub fn slot(self, genesis_slot: Slot) -> Slot {
|
||||
Slot::from(self.0.saturating_add(genesis_slot.as_u64()))
|
||||
}
|
||||
|
||||
pub fn epoch(self, genesis_slot: u64, epoch_length: u64) -> Epoch {
|
||||
Epoch::from(self.0.saturating_add(genesis_slot) / epoch_length)
|
||||
}
|
||||
|
||||
pub fn max_value() -> Height {
|
||||
Height(u64::max_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl Epoch {
|
||||
pub fn new(slot: u64) -> Epoch {
|
||||
Epoch(slot)
|
||||
}
|
||||
|
||||
pub fn max_value() -> Epoch {
|
||||
Epoch(u64::max_value())
|
||||
}
|
||||
|
||||
pub fn start_slot(self, epoch_length: u64) -> Slot {
|
||||
Slot::from(self.0.saturating_mul(epoch_length))
|
||||
}
|
||||
|
||||
pub fn end_slot(self, epoch_length: u64) -> Slot {
|
||||
Slot::from(
|
||||
self.0
|
||||
.saturating_add(1)
|
||||
.saturating_mul(epoch_length)
|
||||
.saturating_sub(1),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn slot_iter(&self, epoch_length: u64) -> SlotIter {
|
||||
SlotIter {
|
||||
current: self.start_slot(epoch_length),
|
||||
epoch: self,
|
||||
epoch_length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SlotIter<'a> {
|
||||
current: Slot,
|
||||
epoch: &'a Epoch,
|
||||
epoch_length: u64,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SlotIter<'a> {
|
||||
type Item = Slot;
|
||||
|
||||
fn next(&mut self) -> Option<Slot> {
|
||||
if self.current == self.epoch.end_slot(self.epoch_length) {
|
||||
None
|
||||
} else {
|
||||
let previous = self.current;
|
||||
self.current += 1;
|
||||
Some(previous)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! new_tests {
|
||||
($type: ident) => {
|
||||
#[test]
|
||||
fn new() {
|
||||
assert_eq!($type(0), $type::new(0));
|
||||
assert_eq!($type(3), $type::new(3));
|
||||
assert_eq!($type(u64::max_value()), $type::new(u64::max_value()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! from_into_tests {
|
||||
($type: ident, $other: ident) => {
|
||||
#[test]
|
||||
fn into() {
|
||||
let x: $other = $type(0).into();
|
||||
assert_eq!(x, 0);
|
||||
|
||||
let x: $other = $type(3).into();
|
||||
assert_eq!(x, 3);
|
||||
|
||||
let x: $other = $type(u64::max_value()).into();
|
||||
// Note: this will fail on 32 bit systems. This is expected as we don't have a proper
|
||||
// 32-bit system strategy in place.
|
||||
assert_eq!(x, $other::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from() {
|
||||
assert_eq!($type(0), $type::from(0_u64));
|
||||
assert_eq!($type(3), $type::from(3_u64));
|
||||
assert_eq!($type(u64::max_value()), $type::from($other::max_value()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! math_between_tests {
|
||||
($type: ident, $other: ident) => {
|
||||
#[test]
|
||||
fn partial_ord() {
|
||||
let assert_partial_ord = |a: u64, partial_ord: Ordering, b: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a).partial_cmp(&other), Some(partial_ord));
|
||||
};
|
||||
|
||||
assert_partial_ord(1, Ordering::Less, 2);
|
||||
assert_partial_ord(2, Ordering::Greater, 1);
|
||||
assert_partial_ord(0, Ordering::Less, u64::max_value());
|
||||
assert_partial_ord(u64::max_value(), Ordering::Greater, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_eq() {
|
||||
let assert_partial_eq = |a: u64, b: u64, is_equal: bool| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a).eq(&other), is_equal);
|
||||
};
|
||||
|
||||
assert_partial_eq(0, 0, true);
|
||||
assert_partial_eq(0, 1, false);
|
||||
assert_partial_eq(1, 0, false);
|
||||
assert_partial_eq(1, 1, true);
|
||||
|
||||
assert_partial_eq(u64::max_value(), u64::max_value(), true);
|
||||
assert_partial_eq(0, u64::max_value(), false);
|
||||
assert_partial_eq(u64::max_value(), 0, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_and_add_assign() {
|
||||
let assert_add = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) + other, $type(result));
|
||||
|
||||
let mut add_assigned = $type(a);
|
||||
add_assigned += other;
|
||||
|
||||
assert_eq!(add_assigned, $type(result));
|
||||
};
|
||||
|
||||
assert_add(0, 1, 1);
|
||||
assert_add(1, 0, 1);
|
||||
assert_add(1, 2, 3);
|
||||
assert_add(2, 1, 3);
|
||||
assert_add(7, 7, 14);
|
||||
|
||||
// Addition should be saturating.
|
||||
assert_add(u64::max_value(), 1, u64::max_value());
|
||||
assert_add(u64::max_value(), u64::max_value(), u64::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_and_sub_assign() {
|
||||
let assert_sub = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) - other, $type(result));
|
||||
|
||||
let mut sub_assigned = $type(a);
|
||||
sub_assigned -= other;
|
||||
|
||||
assert_eq!(sub_assigned, $type(result));
|
||||
};
|
||||
|
||||
assert_sub(1, 0, 1);
|
||||
assert_sub(2, 1, 1);
|
||||
assert_sub(14, 7, 7);
|
||||
assert_sub(u64::max_value(), 1, u64::max_value() - 1);
|
||||
assert_sub(u64::max_value(), u64::max_value(), 0);
|
||||
|
||||
// Subtraction should be saturating
|
||||
assert_sub(0, 1, 0);
|
||||
assert_sub(1, 2, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_and_mul_assign() {
|
||||
let assert_mul = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) * other, $type(result));
|
||||
|
||||
let mut mul_assigned = $type(a);
|
||||
mul_assigned *= other;
|
||||
|
||||
assert_eq!(mul_assigned, $type(result));
|
||||
};
|
||||
|
||||
assert_mul(2, 2, 4);
|
||||
assert_mul(1, 2, 2);
|
||||
assert_mul(0, 2, 0);
|
||||
|
||||
// Multiplication should be saturating.
|
||||
assert_mul(u64::max_value(), 2, u64::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_and_div_assign() {
|
||||
let assert_div = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) / other, $type(result));
|
||||
|
||||
let mut div_assigned = $type(a);
|
||||
div_assigned /= other;
|
||||
|
||||
assert_eq!(div_assigned, $type(result));
|
||||
};
|
||||
|
||||
assert_div(0, 2, 0);
|
||||
assert_div(2, 2, 1);
|
||||
assert_div(100, 50, 2);
|
||||
assert_div(128, 2, 64);
|
||||
assert_div(u64::max_value(), 2, 2_u64.pow(63) - 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn div_panics_with_divide_by_zero() {
|
||||
let other: $other = $type(0).into();
|
||||
let _ = $type(2) / other;
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn div_assign_panics_with_divide_by_zero() {
|
||||
let other: $other = $type(0).into();
|
||||
let mut assigned = $type(2);
|
||||
assigned /= other;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rem() {
|
||||
let assert_rem = |a: u64, b: u64, result: u64| {
|
||||
let other: $other = $type(b).into();
|
||||
assert_eq!($type(a) % other, $type(result));
|
||||
};
|
||||
|
||||
assert_rem(3, 2, 1);
|
||||
assert_rem(40, 2, 0);
|
||||
assert_rem(10, 100, 10);
|
||||
assert_rem(302042, 3293, 2379);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! math_tests {
|
||||
($type: ident) => {
|
||||
#[test]
|
||||
fn saturating_sub() {
|
||||
let assert_saturating_sub = |a: u64, b: u64, result: u64| {
|
||||
assert_eq!($type(a).saturating_sub($type(b)), $type(result));
|
||||
};
|
||||
|
||||
assert_saturating_sub(1, 0, 1);
|
||||
assert_saturating_sub(2, 1, 1);
|
||||
assert_saturating_sub(14, 7, 7);
|
||||
assert_saturating_sub(u64::max_value(), 1, u64::max_value() - 1);
|
||||
assert_saturating_sub(u64::max_value(), u64::max_value(), 0);
|
||||
|
||||
// Subtraction should be saturating
|
||||
assert_saturating_sub(0, 1, 0);
|
||||
assert_saturating_sub(1, 2, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saturating_add() {
|
||||
let assert_saturating_add = |a: u64, b: u64, result: u64| {
|
||||
assert_eq!($type(a).saturating_add($type(b)), $type(result));
|
||||
};
|
||||
|
||||
assert_saturating_add(0, 1, 1);
|
||||
assert_saturating_add(1, 0, 1);
|
||||
assert_saturating_add(1, 2, 3);
|
||||
assert_saturating_add(2, 1, 3);
|
||||
assert_saturating_add(7, 7, 14);
|
||||
|
||||
// Addition should be saturating.
|
||||
assert_saturating_add(u64::max_value(), 1, u64::max_value());
|
||||
assert_saturating_add(u64::max_value(), u64::max_value(), u64::max_value());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_div() {
|
||||
let assert_checked_div = |a: u64, b: u64, result: Option<u64>| {
|
||||
let division_result_as_u64 = match $type(a).checked_div($type(b)) {
|
||||
None => None,
|
||||
Some(val) => Some(val.as_u64()),
|
||||
};
|
||||
assert_eq!(division_result_as_u64, result);
|
||||
};
|
||||
|
||||
assert_checked_div(0, 2, Some(0));
|
||||
assert_checked_div(2, 2, Some(1));
|
||||
assert_checked_div(100, 50, Some(2));
|
||||
assert_checked_div(128, 2, Some(64));
|
||||
assert_checked_div(u64::max_value(), 2, Some(2_u64.pow(63) - 1));
|
||||
|
||||
assert_checked_div(2, 0, None);
|
||||
assert_checked_div(0, 0, None);
|
||||
assert_checked_div(u64::max_value(), 0, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_power_of_two() {
|
||||
let assert_is_power_of_two = |a: u64, result: bool| {
|
||||
assert_eq!(
|
||||
$type(a).is_power_of_two(),
|
||||
result,
|
||||
"{}.is_power_of_two() != {}",
|
||||
a,
|
||||
result
|
||||
);
|
||||
};
|
||||
|
||||
assert_is_power_of_two(0, false);
|
||||
assert_is_power_of_two(1, true);
|
||||
assert_is_power_of_two(2, true);
|
||||
assert_is_power_of_two(3, false);
|
||||
assert_is_power_of_two(4, true);
|
||||
|
||||
assert_is_power_of_two(2_u64.pow(4), true);
|
||||
assert_is_power_of_two(u64::max_value(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ord() {
|
||||
let assert_ord = |a: u64, ord: Ordering, b: u64| {
|
||||
assert_eq!($type(a).cmp(&$type(b)), ord);
|
||||
};
|
||||
|
||||
assert_ord(1, Ordering::Less, 2);
|
||||
assert_ord(2, Ordering::Greater, 1);
|
||||
assert_ord(0, Ordering::Less, u64::max_value());
|
||||
assert_ord(u64::max_value(), Ordering::Greater, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ssz_tests {
|
||||
($type: ident) => {
|
||||
#[test]
|
||||
pub fn test_ssz_round_trip() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = $type::random_for_test(&mut rng);
|
||||
|
||||
let bytes = ssz_encode(&original);
|
||||
let (decoded, _) = $type::ssz_decode(&bytes, 0).unwrap();
|
||||
|
||||
assert_eq!(original, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_hash_tree_root() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = $type::random_for_test(&mut rng);
|
||||
|
||||
let result = original.hash_tree_root();
|
||||
|
||||
assert_eq!(result.len(), 32);
|
||||
// TODO: Add further tests
|
||||
// https://github.com/sigp/lighthouse/issues/170
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! all_tests {
|
||||
($type: ident) => {
|
||||
new_tests!($type);
|
||||
math_between_tests!($type, $type);
|
||||
math_tests!($type);
|
||||
ssz_tests!($type);
|
||||
|
||||
mod u64_tests {
|
||||
use super::*;
|
||||
|
||||
from_into_tests!($type, u64);
|
||||
math_between_tests!($type, u64);
|
||||
|
||||
#[test]
|
||||
pub fn as_64() {
|
||||
let x = $type(0).as_u64();
|
||||
assert_eq!(x, 0);
|
||||
|
||||
let x = $type(3).as_u64();
|
||||
assert_eq!(x, 3);
|
||||
|
||||
let x = $type(u64::max_value()).as_u64();
|
||||
assert_eq!(x, u64::max_value());
|
||||
}
|
||||
}
|
||||
|
||||
mod usize_tests {
|
||||
use super::*;
|
||||
|
||||
from_into_tests!($type, usize);
|
||||
|
||||
#[test]
|
||||
pub fn as_usize() {
|
||||
let x = $type(0).as_usize();
|
||||
assert_eq!(x, 0);
|
||||
|
||||
let x = $type(3).as_usize();
|
||||
assert_eq!(x, 3);
|
||||
|
||||
let x = $type(u64::max_value()).as_usize();
|
||||
assert_eq!(x, usize::max_value());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod slot_tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use ssz::ssz_encode;
|
||||
|
||||
all_tests!(Slot);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod epoch_tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use ssz::ssz_encode;
|
||||
|
||||
all_tests!(Epoch);
|
||||
}
|
||||
}
|
@ -1,134 +1,105 @@
|
||||
use super::ChainSpec;
|
||||
use bls::{Keypair, PublicKey, SecretKey, Signature};
|
||||
use crate::{Address, ChainSpec, Epoch, Hash256, Signature, Slot};
|
||||
|
||||
use crate::{Address, Eth1Data, Hash256, Validator};
|
||||
|
||||
/// The size of a validators deposit in GWei.
|
||||
pub const DEPOSIT_GWEI: u64 = 32_000_000_000;
|
||||
const GWEI: u64 = 1_000_000_000;
|
||||
|
||||
impl ChainSpec {
|
||||
/// Returns a `ChainSpec` compatible with the specification from Ethereum Foundation.
|
||||
///
|
||||
/// Of course, the actual foundation specs are unknown at this point so these are just a rough
|
||||
/// estimate.
|
||||
///
|
||||
/// Spec v0.2.0
|
||||
pub fn foundation() -> Self {
|
||||
let genesis_slot = Slot::new(2_u64.pow(19));
|
||||
let epoch_length = 64;
|
||||
let genesis_epoch = genesis_slot.epoch(epoch_length);
|
||||
|
||||
Self {
|
||||
/*
|
||||
* Misc
|
||||
*/
|
||||
shard_count: 1_024,
|
||||
target_committee_size: 128,
|
||||
ejection_balance: 16 * u64::pow(10, 9),
|
||||
max_balance_churn_quotient: 32,
|
||||
beacon_chain_shard_number: u64::max_value(),
|
||||
max_casper_votes: 1_024,
|
||||
latest_block_roots_length: 8_192,
|
||||
latest_randao_mixes_length: 8_192,
|
||||
latest_penalized_exit_length: 8_192,
|
||||
max_indices_per_slashable_vote: 4_096,
|
||||
max_withdrawals_per_epoch: 4,
|
||||
shuffle_round_count: 90,
|
||||
|
||||
/*
|
||||
* Deposit contract
|
||||
*/
|
||||
deposit_contract_address: Address::from("TBD".as_bytes()),
|
||||
deposit_contract_address: Address::zero(),
|
||||
deposit_contract_tree_depth: 32,
|
||||
min_deposit: 1 * u64::pow(10, 9),
|
||||
max_deposit: 32 * u64::pow(10, 9),
|
||||
|
||||
/*
|
||||
* Gwei values
|
||||
*/
|
||||
min_deposit_amount: u64::pow(2, 0) * GWEI,
|
||||
max_deposit_amount: u64::pow(2, 5) * GWEI,
|
||||
fork_choice_balance_increment: u64::pow(2, 0) * GWEI,
|
||||
ejection_balance: u64::pow(2, 4) * GWEI,
|
||||
|
||||
/*
|
||||
* Initial Values
|
||||
*/
|
||||
genesis_fork_version: 0,
|
||||
genesis_slot: 0,
|
||||
genesis_slot: Slot::new(2_u64.pow(19)),
|
||||
genesis_epoch,
|
||||
genesis_start_shard: 0,
|
||||
far_future_slot: u64::max_value(),
|
||||
far_future_epoch: Epoch::new(u64::max_value()),
|
||||
zero_hash: Hash256::zero(),
|
||||
empty_signature: Signature::empty_signature(),
|
||||
bls_withdrawal_prefix_byte: 0x00,
|
||||
bls_withdrawal_prefix_byte: 0,
|
||||
|
||||
/*
|
||||
* Time parameters
|
||||
*/
|
||||
slot_duration: 6,
|
||||
min_attestation_inclusion_delay: 4,
|
||||
epoch_length: 64,
|
||||
seed_lookahead: 64,
|
||||
entry_exit_delay: 256,
|
||||
eth1_data_voting_period: 1_024,
|
||||
min_validator_withdrawal_time: u64::pow(2, 14),
|
||||
epoch_length,
|
||||
seed_lookahead: Epoch::new(1),
|
||||
entry_exit_delay: 4,
|
||||
eth1_data_voting_period: 16,
|
||||
min_validator_withdrawal_epochs: Epoch::new(256),
|
||||
|
||||
/*
|
||||
* State list lengths
|
||||
*/
|
||||
latest_block_roots_length: 8_192,
|
||||
latest_randao_mixes_length: 8_192,
|
||||
latest_index_roots_length: 8_192,
|
||||
latest_penalized_exit_length: 8_192,
|
||||
|
||||
/*
|
||||
* Reward and penalty quotients
|
||||
*/
|
||||
base_reward_quotient: 32,
|
||||
whistleblower_reward_quotient: 512,
|
||||
includer_reward_quotient: 8,
|
||||
inactivity_penalty_quotient: u64::pow(2, 24),
|
||||
inactivity_penalty_quotient: 16_777_216,
|
||||
|
||||
/*
|
||||
* Max operations per block
|
||||
*/
|
||||
max_proposer_slashings: 16,
|
||||
max_casper_slashings: 16,
|
||||
max_attester_slashings: 1,
|
||||
max_attestations: 128,
|
||||
max_deposits: 16,
|
||||
max_exits: 16,
|
||||
|
||||
/*
|
||||
* Intialization parameters
|
||||
* Signature domains
|
||||
*/
|
||||
initial_validators: initial_validators_for_testing(),
|
||||
initial_balances: initial_balances_for_testing(),
|
||||
genesis_time: 1_544_672_897,
|
||||
intial_eth1_data: Eth1Data {
|
||||
deposit_root: Hash256::from("deposit_root".as_bytes()),
|
||||
block_hash: Hash256::from("block_hash".as_bytes()),
|
||||
},
|
||||
domain_deposit: 0,
|
||||
domain_attestation: 1,
|
||||
domain_proposal: 2,
|
||||
domain_exit: 3,
|
||||
domain_randao: 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a set of validator records to use with testing until the real chain starts.
|
||||
fn initial_validators_for_testing() -> Vec<Validator> {
|
||||
// Some dummy private keys to start with.
|
||||
let key_strings = vec![
|
||||
"jzjxxgjajfjrmgodszzsgqccmhnyvetcuxobhtynojtpdtbj",
|
||||
"gpeehcjudxdijzhjgirfuhahmnjutlchjmoffxmimbdejakd",
|
||||
"ntrrdwwebodokuwaclhoqreqyodngoyhurvesghjfxeswoaj",
|
||||
"cibmzkqrzdgdlrvqaxinwpvyhcgjkeysrsjkqtkcxvznsvth",
|
||||
"erqrfuahdwprsstkawggounxmihzhrvbhchcyiwtaypqcedr",
|
||||
];
|
||||
|
||||
let mut initial_validators = Vec::with_capacity(key_strings.len());
|
||||
for key_string in key_strings {
|
||||
let keypair = {
|
||||
let secret_key = match SecretKey::from_bytes(&key_string.as_bytes()) {
|
||||
Ok(key) => key,
|
||||
Err(_) => unreachable!(), // Keys are static and should not fail.
|
||||
};
|
||||
let public_key = PublicKey::from_secret_key(&secret_key);
|
||||
Keypair {
|
||||
sk: secret_key,
|
||||
pk: public_key,
|
||||
}
|
||||
};
|
||||
let validator = Validator {
|
||||
pubkey: keypair.pk.clone(),
|
||||
withdrawal_credentials: Hash256::zero(),
|
||||
proposer_slots: 0,
|
||||
activation_slot: u64::max_value(),
|
||||
exit_slot: u64::max_value(),
|
||||
withdrawal_slot: u64::max_value(),
|
||||
penalized_slot: u64::max_value(),
|
||||
exit_count: 0,
|
||||
status_flags: None,
|
||||
latest_custody_reseed_slot: 0,
|
||||
penultimate_custody_reseed_slot: 0,
|
||||
};
|
||||
initial_validators.push(validator);
|
||||
}
|
||||
|
||||
initial_validators
|
||||
}
|
||||
|
||||
fn initial_balances_for_testing() -> Vec<u64> {
|
||||
vec![DEPOSIT_GWEI; 4]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -1,8 +1,11 @@
|
||||
mod foundation;
|
||||
|
||||
use crate::{Address, Eth1Data, Hash256, Validator};
|
||||
use crate::{Address, Epoch, Hash256, Slot};
|
||||
use bls::Signature;
|
||||
|
||||
/// Holds all the "constants" for a BeaconChain.
|
||||
///
|
||||
/// Spec v0.2.0
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct ChainSpec {
|
||||
/*
|
||||
@ -10,41 +13,57 @@ pub struct ChainSpec {
|
||||
*/
|
||||
pub shard_count: u64,
|
||||
pub target_committee_size: u64,
|
||||
pub ejection_balance: u64,
|
||||
pub max_balance_churn_quotient: u64,
|
||||
pub beacon_chain_shard_number: u64,
|
||||
pub max_casper_votes: u64,
|
||||
pub latest_block_roots_length: u64,
|
||||
pub latest_randao_mixes_length: u64,
|
||||
pub latest_penalized_exit_length: u64,
|
||||
pub max_indices_per_slashable_vote: u64,
|
||||
pub max_withdrawals_per_epoch: u64,
|
||||
pub shuffle_round_count: u64,
|
||||
|
||||
/*
|
||||
* Deposit contract
|
||||
*/
|
||||
pub deposit_contract_address: Address,
|
||||
pub deposit_contract_tree_depth: u64,
|
||||
pub min_deposit: u64,
|
||||
pub max_deposit: u64,
|
||||
|
||||
/*
|
||||
* Gwei values
|
||||
*/
|
||||
pub min_deposit_amount: u64,
|
||||
pub max_deposit_amount: u64,
|
||||
pub fork_choice_balance_increment: u64,
|
||||
pub ejection_balance: u64,
|
||||
|
||||
/*
|
||||
* Initial Values
|
||||
*/
|
||||
pub genesis_fork_version: u64,
|
||||
pub genesis_slot: u64,
|
||||
pub genesis_slot: Slot,
|
||||
pub genesis_epoch: Epoch,
|
||||
pub genesis_start_shard: u64,
|
||||
pub far_future_slot: u64,
|
||||
pub far_future_epoch: Epoch,
|
||||
pub zero_hash: Hash256,
|
||||
pub empty_signature: Signature,
|
||||
pub bls_withdrawal_prefix_byte: u8,
|
||||
|
||||
/*
|
||||
* Time parameters
|
||||
*/
|
||||
pub slot_duration: u64,
|
||||
pub min_attestation_inclusion_delay: u64,
|
||||
pub epoch_length: u64,
|
||||
pub seed_lookahead: u64,
|
||||
pub seed_lookahead: Epoch,
|
||||
pub entry_exit_delay: u64,
|
||||
pub eth1_data_voting_period: u64,
|
||||
pub min_validator_withdrawal_time: u64,
|
||||
pub min_validator_withdrawal_epochs: Epoch,
|
||||
|
||||
/*
|
||||
* State list lengths
|
||||
*/
|
||||
pub latest_block_roots_length: usize,
|
||||
pub latest_randao_mixes_length: usize,
|
||||
pub latest_index_roots_length: usize,
|
||||
pub latest_penalized_exit_length: usize,
|
||||
|
||||
/*
|
||||
* Reward and penalty quotients
|
||||
*/
|
||||
@ -52,19 +71,22 @@ pub struct ChainSpec {
|
||||
pub whistleblower_reward_quotient: u64,
|
||||
pub includer_reward_quotient: u64,
|
||||
pub inactivity_penalty_quotient: u64,
|
||||
|
||||
/*
|
||||
* Max operations per block
|
||||
*/
|
||||
pub max_proposer_slashings: u64,
|
||||
pub max_casper_slashings: u64,
|
||||
pub max_attester_slashings: u64,
|
||||
pub max_attestations: u64,
|
||||
pub max_deposits: u64,
|
||||
pub max_exits: u64,
|
||||
|
||||
/*
|
||||
* Intialization parameters
|
||||
* Signature domains
|
||||
*/
|
||||
pub initial_validators: Vec<Validator>,
|
||||
pub initial_balances: Vec<u64>,
|
||||
pub genesis_time: u64,
|
||||
pub intial_eth1_data: Eth1Data,
|
||||
pub domain_deposit: u64,
|
||||
pub domain_attestation: u64,
|
||||
pub domain_proposal: u64,
|
||||
pub domain_exit: u64,
|
||||
pub domain_randao: u64,
|
||||
}
|
||||
|
@ -1,142 +0,0 @@
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
|
||||
/// The value of the "type" field of SpecialRecord.
|
||||
///
|
||||
/// Note: this value must serialize to a u8 and therefore must not be greater than 255.
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize)]
|
||||
pub enum SpecialRecordKind {
|
||||
Logout = 0,
|
||||
CasperSlashing = 1,
|
||||
RandaoChange = 2,
|
||||
}
|
||||
|
||||
/// The structure used in the `BeaconBlock.specials` field.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SpecialRecord {
|
||||
pub kind: u8,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SpecialRecord {
|
||||
pub fn logout(data: &[u8]) -> Self {
|
||||
Self {
|
||||
kind: SpecialRecordKind::Logout as u8,
|
||||
data: data.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn casper_slashing(data: &[u8]) -> Self {
|
||||
Self {
|
||||
kind: SpecialRecordKind::CasperSlashing as u8,
|
||||
data: data.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn randao_change(data: &[u8]) -> Self {
|
||||
Self {
|
||||
kind: SpecialRecordKind::RandaoChange as u8,
|
||||
data: data.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Match `self.kind` to a `SpecialRecordKind`.
|
||||
///
|
||||
/// Returns `None` if `self.kind` is an unknown value.
|
||||
pub fn resolve_kind(&self) -> Option<SpecialRecordKind> {
|
||||
match self.kind {
|
||||
x if x == SpecialRecordKind::Logout as u8 => Some(SpecialRecordKind::Logout),
|
||||
x if x == SpecialRecordKind::CasperSlashing as u8 => {
|
||||
Some(SpecialRecordKind::CasperSlashing)
|
||||
}
|
||||
x if x == SpecialRecordKind::RandaoChange as u8 => {
|
||||
Some(SpecialRecordKind::RandaoChange)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for SpecialRecord {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.kind);
|
||||
s.append_vec(&self.data);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for SpecialRecord {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (kind, i) = u8::ssz_decode(bytes, i)?;
|
||||
let (data, i) = Decodable::ssz_decode(bytes, i)?;
|
||||
Ok((SpecialRecord { kind, data }, i))
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeHash for SpecialRecord {
|
||||
fn hash_tree_root(&self) -> Vec<u8> {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.kind.hash_tree_root());
|
||||
result.append(&mut self.data.as_slice().hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn test_special_record_ssz_encode() {
|
||||
let s = SpecialRecord::logout(&vec![]);
|
||||
let mut ssz_stream = SszStream::new();
|
||||
ssz_stream.append(&s);
|
||||
let ssz = ssz_stream.drain();
|
||||
assert_eq!(ssz, vec![0, 0, 0, 0, 0]);
|
||||
|
||||
let s = SpecialRecord::casper_slashing(&vec![]);
|
||||
let mut ssz_stream = SszStream::new();
|
||||
ssz_stream.append(&s);
|
||||
let ssz = ssz_stream.drain();
|
||||
assert_eq!(ssz, vec![1, 0, 0, 0, 0]);
|
||||
|
||||
let s = SpecialRecord::randao_change(&vec![]);
|
||||
let mut ssz_stream = SszStream::new();
|
||||
ssz_stream.append(&s);
|
||||
let ssz = ssz_stream.drain();
|
||||
assert_eq!(ssz, vec![2, 0, 0, 0, 0]);
|
||||
|
||||
let s = SpecialRecord::randao_change(&vec![42, 43, 44]);
|
||||
let mut ssz_stream = SszStream::new();
|
||||
ssz_stream.append(&s);
|
||||
let ssz = ssz_stream.drain();
|
||||
assert_eq!(ssz, vec![2, 0, 0, 0, 3, 42, 43, 44]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_special_record_ssz_encode_decode() {
|
||||
let s = SpecialRecord::randao_change(&vec![13, 16, 14]);
|
||||
let mut ssz_stream = SszStream::new();
|
||||
ssz_stream.append(&s);
|
||||
let ssz = ssz_stream.drain();
|
||||
let (s_decoded, _) = SpecialRecord::ssz_decode(&ssz, 0).unwrap();
|
||||
assert_eq!(s, s_decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_special_record_resolve_kind() {
|
||||
let s = SpecialRecord::logout(&vec![]);
|
||||
assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::Logout));
|
||||
|
||||
let s = SpecialRecord::casper_slashing(&vec![]);
|
||||
assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::CasperSlashing));
|
||||
|
||||
let s = SpecialRecord::randao_change(&vec![]);
|
||||
assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::RandaoChange));
|
||||
|
||||
let s = SpecialRecord {
|
||||
kind: 88,
|
||||
data: vec![],
|
||||
};
|
||||
assert_eq!(s.resolve_kind(), None);
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
use super::Hash256;
|
||||
use crate::{test_utils::TestRandom, PublicKey};
|
||||
use crate::{test_utils::TestRandom, Epoch, Hash256, PublicKey};
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash};
|
||||
@ -47,21 +46,17 @@ fn status_flag_from_byte(flag: u8) -> Result<Option<StatusFlags>, StatusFlagsDec
|
||||
pub struct Validator {
|
||||
pub pubkey: PublicKey,
|
||||
pub withdrawal_credentials: Hash256,
|
||||
pub proposer_slots: u64,
|
||||
pub activation_slot: u64,
|
||||
pub exit_slot: u64,
|
||||
pub withdrawal_slot: u64,
|
||||
pub penalized_slot: u64,
|
||||
pub exit_count: u64,
|
||||
pub activation_epoch: Epoch,
|
||||
pub exit_epoch: Epoch,
|
||||
pub withdrawal_epoch: Epoch,
|
||||
pub penalized_epoch: Epoch,
|
||||
pub status_flags: Option<StatusFlags>,
|
||||
pub latest_custody_reseed_slot: u64,
|
||||
pub penultimate_custody_reseed_slot: u64,
|
||||
}
|
||||
|
||||
impl Validator {
|
||||
/// This predicate indicates if the validator represented by this record is considered "active" at `slot`.
|
||||
pub fn is_active_at(&self, slot: u64) -> bool {
|
||||
self.activation_slot <= slot && slot < self.exit_slot
|
||||
pub fn is_active_at(&self, slot: Epoch) -> bool {
|
||||
self.activation_epoch <= slot && slot < self.exit_epoch
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,15 +66,11 @@ impl Default for Validator {
|
||||
Self {
|
||||
pubkey: PublicKey::default(),
|
||||
withdrawal_credentials: Hash256::default(),
|
||||
proposer_slots: 0,
|
||||
activation_slot: std::u64::MAX,
|
||||
exit_slot: std::u64::MAX,
|
||||
withdrawal_slot: std::u64::MAX,
|
||||
penalized_slot: std::u64::MAX,
|
||||
exit_count: 0,
|
||||
activation_epoch: Epoch::from(std::u64::MAX),
|
||||
exit_epoch: Epoch::from(std::u64::MAX),
|
||||
withdrawal_epoch: Epoch::from(std::u64::MAX),
|
||||
penalized_epoch: Epoch::from(std::u64::MAX),
|
||||
status_flags: None,
|
||||
latest_custody_reseed_slot: 0, // NOTE: is `GENESIS_SLOT`
|
||||
penultimate_custody_reseed_slot: 0, // NOTE: is `GENESIS_SLOT`
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,7 +78,7 @@ impl Default for Validator {
|
||||
impl<T: RngCore> TestRandom<T> for StatusFlags {
|
||||
fn random_for_test(rng: &mut T) -> Self {
|
||||
let options = vec![StatusFlags::InitiatedExit, StatusFlags::Withdrawable];
|
||||
options[(rng.next_u32() as usize) % options.len()].clone()
|
||||
options[(rng.next_u32() as usize) % options.len()]
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,15 +86,11 @@ impl Encodable for Validator {
|
||||
fn ssz_append(&self, s: &mut SszStream) {
|
||||
s.append(&self.pubkey);
|
||||
s.append(&self.withdrawal_credentials);
|
||||
s.append(&self.proposer_slots);
|
||||
s.append(&self.activation_slot);
|
||||
s.append(&self.exit_slot);
|
||||
s.append(&self.withdrawal_slot);
|
||||
s.append(&self.penalized_slot);
|
||||
s.append(&self.exit_count);
|
||||
s.append(&self.activation_epoch);
|
||||
s.append(&self.exit_epoch);
|
||||
s.append(&self.withdrawal_epoch);
|
||||
s.append(&self.penalized_epoch);
|
||||
s.append(&status_flag_to_byte(self.status_flags));
|
||||
s.append(&self.latest_custody_reseed_slot);
|
||||
s.append(&self.penultimate_custody_reseed_slot);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,15 +98,11 @@ impl Decodable for Validator {
|
||||
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
|
||||
let (pubkey, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (withdrawal_credentials, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (proposer_slots, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (activation_slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (exit_slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (withdrawal_slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (penalized_slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (exit_count, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (activation_epoch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (exit_epoch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (withdrawal_epoch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (penalized_epoch, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (status_flags_byte, i): (u8, usize) = <_>::ssz_decode(bytes, i)?;
|
||||
let (latest_custody_reseed_slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (penultimate_custody_reseed_slot, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
let status_flags = status_flag_from_byte(status_flags_byte)?;
|
||||
|
||||
@ -127,15 +110,11 @@ impl Decodable for Validator {
|
||||
Self {
|
||||
pubkey,
|
||||
withdrawal_credentials,
|
||||
proposer_slots,
|
||||
activation_slot,
|
||||
exit_slot,
|
||||
withdrawal_slot,
|
||||
penalized_slot,
|
||||
exit_count,
|
||||
activation_epoch,
|
||||
exit_epoch,
|
||||
withdrawal_epoch,
|
||||
penalized_epoch,
|
||||
status_flags,
|
||||
latest_custody_reseed_slot,
|
||||
penultimate_custody_reseed_slot,
|
||||
},
|
||||
i,
|
||||
))
|
||||
@ -147,15 +126,11 @@ impl TreeHash for Validator {
|
||||
let mut result: Vec<u8> = vec![];
|
||||
result.append(&mut self.pubkey.hash_tree_root());
|
||||
result.append(&mut self.withdrawal_credentials.hash_tree_root());
|
||||
result.append(&mut self.proposer_slots.hash_tree_root());
|
||||
result.append(&mut self.activation_slot.hash_tree_root());
|
||||
result.append(&mut self.exit_slot.hash_tree_root());
|
||||
result.append(&mut self.withdrawal_slot.hash_tree_root());
|
||||
result.append(&mut self.penalized_slot.hash_tree_root());
|
||||
result.append(&mut self.exit_count.hash_tree_root());
|
||||
result.append(&mut (status_flag_to_byte(self.status_flags) as u64).hash_tree_root());
|
||||
result.append(&mut self.latest_custody_reseed_slot.hash_tree_root());
|
||||
result.append(&mut self.penultimate_custody_reseed_slot.hash_tree_root());
|
||||
result.append(&mut self.activation_epoch.hash_tree_root());
|
||||
result.append(&mut self.exit_epoch.hash_tree_root());
|
||||
result.append(&mut self.withdrawal_epoch.hash_tree_root());
|
||||
result.append(&mut self.penalized_epoch.hash_tree_root());
|
||||
result.append(&mut u64::from(status_flag_to_byte(self.status_flags)).hash_tree_root());
|
||||
hash(&result)
|
||||
}
|
||||
}
|
||||
@ -165,15 +140,11 @@ impl<T: RngCore> TestRandom<T> for Validator {
|
||||
Self {
|
||||
pubkey: <_>::random_for_test(rng),
|
||||
withdrawal_credentials: <_>::random_for_test(rng),
|
||||
proposer_slots: <_>::random_for_test(rng),
|
||||
activation_slot: <_>::random_for_test(rng),
|
||||
exit_slot: <_>::random_for_test(rng),
|
||||
withdrawal_slot: <_>::random_for_test(rng),
|
||||
penalized_slot: <_>::random_for_test(rng),
|
||||
exit_count: <_>::random_for_test(rng),
|
||||
activation_epoch: <_>::random_for_test(rng),
|
||||
exit_epoch: <_>::random_for_test(rng),
|
||||
withdrawal_epoch: <_>::random_for_test(rng),
|
||||
penalized_epoch: <_>::random_for_test(rng),
|
||||
status_flags: Some(<_>::random_for_test(rng)),
|
||||
latest_custody_reseed_slot: <_>::random_for_test(rng),
|
||||
penultimate_custody_reseed_slot: <_>::random_for_test(rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -200,16 +171,17 @@ mod tests {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let mut validator = Validator::random_for_test(&mut rng);
|
||||
|
||||
let activation_slot = u64::random_for_test(&mut rng);
|
||||
let exit_slot = activation_slot + 234;
|
||||
let activation_epoch = u64::random_for_test(&mut rng);
|
||||
let exit_epoch = activation_epoch + 234;
|
||||
|
||||
validator.activation_slot = activation_slot;
|
||||
validator.exit_slot = exit_slot;
|
||||
validator.activation_epoch = Epoch::from(activation_epoch);
|
||||
validator.exit_epoch = Epoch::from(exit_epoch);
|
||||
|
||||
for slot in (activation_slot - 100)..(exit_slot + 100) {
|
||||
if slot < activation_slot {
|
||||
for slot in (activation_epoch - 100)..(exit_epoch + 100) {
|
||||
let slot = Epoch::from(slot);
|
||||
if slot < activation_epoch {
|
||||
assert!(!validator.is_active_at(slot));
|
||||
} else if slot >= exit_slot {
|
||||
} else if slot >= exit_epoch {
|
||||
assert!(!validator.is_active_at(slot));
|
||||
} else {
|
||||
assert!(validator.is_active_at(slot));
|
||||
|
@ -1,14 +1,15 @@
|
||||
/// Contains logic to manipulate a `&[Validator]`.
|
||||
/// For now, we avoid defining a newtype and just have flat functions here.
|
||||
use super::validator::*;
|
||||
use crate::Epoch;
|
||||
|
||||
/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `slot`.
|
||||
pub fn get_active_validator_indices(validators: &[Validator], slot: u64) -> Vec<usize> {
|
||||
/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `epoch`.
|
||||
pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec<usize> {
|
||||
validators
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, validator)| {
|
||||
if validator.is_active_at(slot) {
|
||||
if validator.is_active_at(epoch) {
|
||||
Some(index)
|
||||
} else {
|
||||
None
|
||||
@ -27,8 +28,8 @@ mod tests {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let validators = vec![];
|
||||
let some_slot = u64::random_for_test(&mut rng);
|
||||
let indices = get_active_validator_indices(&validators, some_slot);
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![]);
|
||||
}
|
||||
|
||||
@ -41,8 +42,8 @@ mod tests {
|
||||
validators.push(Validator::default())
|
||||
}
|
||||
|
||||
let some_slot = u64::random_for_test(&mut rng);
|
||||
let indices = get_active_validator_indices(&validators, some_slot);
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![]);
|
||||
}
|
||||
|
||||
@ -50,7 +51,7 @@ mod tests {
|
||||
fn can_get_all_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let count_validators = 10;
|
||||
let some_slot = u64::random_for_test(&mut rng);
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
|
||||
let mut validators = (0..count_validators)
|
||||
.into_iter()
|
||||
@ -60,8 +61,8 @@ mod tests {
|
||||
let activation_offset = u64::random_for_test(&mut rng);
|
||||
let exit_offset = u64::random_for_test(&mut rng);
|
||||
|
||||
validator.activation_slot = some_slot.checked_sub(activation_offset).unwrap_or(0);
|
||||
validator.exit_slot = some_slot.checked_add(exit_offset).unwrap_or(std::u64::MAX);
|
||||
validator.activation_epoch = some_epoch - activation_offset;
|
||||
validator.exit_epoch = some_epoch + exit_offset;
|
||||
|
||||
validator
|
||||
})
|
||||
@ -69,10 +70,10 @@ mod tests {
|
||||
|
||||
// test boundary condition by ensuring that at least one validator in the list just activated
|
||||
if let Some(validator) = validators.get_mut(0) {
|
||||
validator.activation_slot = some_slot;
|
||||
validator.activation_epoch = some_epoch;
|
||||
}
|
||||
|
||||
let indices = get_active_validator_indices(&validators, some_slot);
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(
|
||||
indices,
|
||||
(0..count_validators).into_iter().collect::<Vec<_>>()
|
||||
@ -81,31 +82,35 @@ mod tests {
|
||||
|
||||
fn set_validators_to_default_entry_exit(validators: &mut [Validator]) {
|
||||
for validator in validators.iter_mut() {
|
||||
validator.activation_slot = std::u64::MAX;
|
||||
validator.exit_slot = std::u64::MAX;
|
||||
validator.activation_epoch = Epoch::max_value();
|
||||
validator.exit_epoch = Epoch::max_value();
|
||||
}
|
||||
}
|
||||
|
||||
// sets all `validators` to be active as of some slot prior to `slot`. returns the activation slot.
|
||||
fn set_validators_to_activated(validators: &mut [Validator], slot: u64) -> u64 {
|
||||
let activation_slot = slot - 10;
|
||||
// sets all `validators` to be active as of some epoch prior to `epoch`. returns the activation epoch.
|
||||
fn set_validators_to_activated(validators: &mut [Validator], epoch: Epoch) -> Epoch {
|
||||
let activation_epoch = epoch - 10;
|
||||
for validator in validators.iter_mut() {
|
||||
validator.activation_slot = activation_slot;
|
||||
validator.activation_epoch = activation_epoch;
|
||||
}
|
||||
activation_slot
|
||||
activation_epoch
|
||||
}
|
||||
|
||||
// sets all `validators` to be exited as of some slot before `slot`.
|
||||
fn set_validators_to_exited(validators: &mut [Validator], slot: u64, activation_slot: u64) {
|
||||
assert!(activation_slot < slot);
|
||||
let mut exit_slot = activation_slot + 10;
|
||||
while exit_slot >= slot {
|
||||
exit_slot -= 1;
|
||||
// sets all `validators` to be exited as of some epoch before `epoch`.
|
||||
fn set_validators_to_exited(
|
||||
validators: &mut [Validator],
|
||||
epoch: Epoch,
|
||||
activation_epoch: Epoch,
|
||||
) {
|
||||
assert!(activation_epoch < epoch);
|
||||
let mut exit_epoch = activation_epoch + 10;
|
||||
while exit_epoch >= epoch {
|
||||
exit_epoch -= 1;
|
||||
}
|
||||
assert!(activation_slot < exit_slot && exit_slot < slot);
|
||||
assert!(activation_epoch < exit_epoch && exit_epoch < epoch);
|
||||
|
||||
for validator in validators.iter_mut() {
|
||||
validator.exit_slot = exit_slot;
|
||||
validator.exit_epoch = exit_epoch;
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,18 +119,18 @@ mod tests {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
const COUNT_PARTITIONS: usize = 3;
|
||||
const COUNT_VALIDATORS: usize = 3 * COUNT_PARTITIONS;
|
||||
let some_slot: u64 = u64::random_for_test(&mut rng);
|
||||
let some_epoch: Epoch = Epoch::random_for_test(&mut rng);
|
||||
|
||||
let mut validators = (0..COUNT_VALIDATORS)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let mut validator = Validator::default();
|
||||
|
||||
let activation_offset = u64::random_for_test(&mut rng);
|
||||
let exit_offset = u64::random_for_test(&mut rng);
|
||||
let activation_offset = Epoch::random_for_test(&mut rng);
|
||||
let exit_offset = Epoch::random_for_test(&mut rng);
|
||||
|
||||
validator.activation_slot = some_slot.checked_sub(activation_offset).unwrap_or(0);
|
||||
validator.exit_slot = some_slot.checked_add(exit_offset).unwrap_or(std::u64::MAX);
|
||||
validator.activation_epoch = some_epoch - activation_offset;
|
||||
validator.exit_epoch = some_epoch + exit_offset;
|
||||
|
||||
validator
|
||||
})
|
||||
@ -140,19 +145,19 @@ mod tests {
|
||||
}
|
||||
1 => {
|
||||
// 2. activated, but not exited
|
||||
set_validators_to_activated(chunk, some_slot);
|
||||
set_validators_to_activated(chunk, some_epoch);
|
||||
// test boundary condition by ensuring that at least one validator in the list just activated
|
||||
if let Some(validator) = chunk.get_mut(0) {
|
||||
validator.activation_slot = some_slot;
|
||||
validator.activation_epoch = some_epoch;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
// 3. exited
|
||||
let activation_slot = set_validators_to_activated(chunk, some_slot);
|
||||
set_validators_to_exited(chunk, some_slot, activation_slot);
|
||||
let activation_epoch = set_validators_to_activated(chunk, some_epoch);
|
||||
set_validators_to_exited(chunk, some_epoch, activation_epoch);
|
||||
// test boundary condition by ensuring that at least one validator in the list just exited
|
||||
if let Some(validator) = chunk.get_mut(0) {
|
||||
validator.exit_slot = some_slot;
|
||||
validator.exit_epoch = some_epoch;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(
|
||||
@ -161,7 +166,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let indices = get_active_validator_indices(&validators, some_slot);
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![3, 4, 5]);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use super::Hash256;
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{test_utils::TestRandom, Hash256, Slot};
|
||||
use bls::PublicKey;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
@ -11,7 +10,7 @@ pub struct ValidatorRegistryDeltaBlock {
|
||||
pub latest_registry_delta_root: Hash256,
|
||||
pub validator_index: u32,
|
||||
pub pubkey: PublicKey,
|
||||
pub slot: u64,
|
||||
pub slot: Slot,
|
||||
pub flag: u64,
|
||||
}
|
||||
|
||||
@ -22,7 +21,7 @@ impl Default for ValidatorRegistryDeltaBlock {
|
||||
latest_registry_delta_root: Hash256::zero(),
|
||||
validator_index: std::u32::MAX,
|
||||
pubkey: PublicKey::default(),
|
||||
slot: std::u64::MAX,
|
||||
slot: Slot::from(std::u64::MAX),
|
||||
flag: std::u64::MAX,
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,11 @@ impl BooleanBitfield {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Returns true if `self.len() == 0`
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns the number of bytes required to represent this bitfield.
|
||||
pub fn num_bytes(&self) -> usize {
|
||||
self.to_bytes().len()
|
||||
|
@ -5,3 +5,4 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
types = { path = "../../types" }
|
||||
|
@ -3,9 +3,10 @@ mod testing_slot_clock;
|
||||
|
||||
pub use crate::system_time_slot_clock::{Error as SystemTimeSlotClockError, SystemTimeSlotClock};
|
||||
pub use crate::testing_slot_clock::{Error as TestingSlotClockError, TestingSlotClock};
|
||||
pub use types::Slot;
|
||||
|
||||
pub trait SlotClock: Send + Sync {
|
||||
type Error;
|
||||
|
||||
fn present_slot(&self) -> Result<Option<u64>, Self::Error>;
|
||||
fn present_slot(&self) -> Result<Option<Slot>, Self::Error>;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::SlotClock;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use types::Slot;
|
||||
|
||||
pub use std::time::SystemTimeError;
|
||||
|
||||
@ -38,7 +39,7 @@ impl SystemTimeSlotClock {
|
||||
impl SlotClock for SystemTimeSlotClock {
|
||||
type Error = Error;
|
||||
|
||||
fn present_slot(&self) -> Result<Option<u64>, Error> {
|
||||
fn present_slot(&self) -> Result<Option<Slot>, Error> {
|
||||
let syslot_time = SystemTime::now();
|
||||
let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH)?;
|
||||
let duration_since_genesis =
|
||||
@ -56,8 +57,10 @@ impl From<SystemTimeError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_from_duration(slot_duration_seconds: u64, duration: Duration) -> Option<u64> {
|
||||
duration.as_secs().checked_div(slot_duration_seconds)
|
||||
fn slot_from_duration(slot_duration_seconds: u64, duration: Duration) -> Option<Slot> {
|
||||
Some(Slot::new(
|
||||
duration.as_secs().checked_div(slot_duration_seconds)?,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -81,19 +84,19 @@ mod tests {
|
||||
genesis_seconds: genesis,
|
||||
slot_duration_seconds: slot_time,
|
||||
};
|
||||
assert_eq!(clock.present_slot().unwrap(), Some(89));
|
||||
assert_eq!(clock.present_slot().unwrap(), Some(Slot::new(89)));
|
||||
|
||||
let clock = SystemTimeSlotClock {
|
||||
genesis_seconds: since_epoch.as_secs(),
|
||||
slot_duration_seconds: slot_time,
|
||||
};
|
||||
assert_eq!(clock.present_slot().unwrap(), Some(0));
|
||||
assert_eq!(clock.present_slot().unwrap(), Some(Slot::new(0)));
|
||||
|
||||
let clock = SystemTimeSlotClock {
|
||||
genesis_seconds: since_epoch.as_secs() - slot_time * 42 - 5,
|
||||
slot_duration_seconds: slot_time,
|
||||
};
|
||||
assert_eq!(clock.present_slot().unwrap(), Some(42));
|
||||
assert_eq!(clock.present_slot().unwrap(), Some(Slot::new(42)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -102,23 +105,23 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
slot_from_duration(slot_time, Duration::from_secs(0)),
|
||||
Some(0)
|
||||
Some(Slot::new(0))
|
||||
);
|
||||
assert_eq!(
|
||||
slot_from_duration(slot_time, Duration::from_secs(10)),
|
||||
Some(0)
|
||||
Some(Slot::new(0))
|
||||
);
|
||||
assert_eq!(
|
||||
slot_from_duration(slot_time, Duration::from_secs(100)),
|
||||
Some(1)
|
||||
Some(Slot::new(1))
|
||||
);
|
||||
assert_eq!(
|
||||
slot_from_duration(slot_time, Duration::from_secs(101)),
|
||||
Some(1)
|
||||
Some(Slot::new(1))
|
||||
);
|
||||
assert_eq!(
|
||||
slot_from_duration(slot_time, Duration::from_secs(1000)),
|
||||
Some(10)
|
||||
Some(Slot::new(10))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::SlotClock;
|
||||
use std::sync::RwLock;
|
||||
use types::Slot;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {}
|
||||
@ -27,9 +28,9 @@ impl TestingSlotClock {
|
||||
impl SlotClock for TestingSlotClock {
|
||||
type Error = Error;
|
||||
|
||||
fn present_slot(&self) -> Result<Option<u64>, Error> {
|
||||
fn present_slot(&self) -> Result<Option<Slot>, Error> {
|
||||
let slot = *self.slot.read().expect("TestingSlotClock poisoned.");
|
||||
Ok(Some(slot))
|
||||
Ok(Some(Slot::new(slot)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,8 +41,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_slot_now() {
|
||||
let clock = TestingSlotClock::new(10);
|
||||
assert_eq!(clock.present_slot(), Ok(Some(10)));
|
||||
assert_eq!(clock.present_slot(), Ok(Some(Slot::new(10))));
|
||||
clock.set_slot(123);
|
||||
assert_eq!(clock.present_slot(), Ok(Some(123)));
|
||||
assert_eq!(clock.present_slot(), Ok(Some(Slot::new(123))));
|
||||
}
|
||||
}
|
||||
|
@ -1,180 +0,0 @@
|
||||
use bls::verify_proof_of_possession;
|
||||
use types::{BeaconState, ChainSpec, Deposit, Validator};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ValidatorInductionError {
|
||||
InvalidShard,
|
||||
InvaidProofOfPossession,
|
||||
InvalidWithdrawalCredentials,
|
||||
}
|
||||
|
||||
pub fn process_deposit(
|
||||
state: &mut BeaconState,
|
||||
deposit: &Deposit,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), ValidatorInductionError> {
|
||||
let deposit_input = &deposit.deposit_data.deposit_input;
|
||||
let deposit_data = &deposit.deposit_data;
|
||||
|
||||
// TODO: Update the signature validation as defined in the spec once issues #91 and #70 are completed
|
||||
if !verify_proof_of_possession(&deposit_input.proof_of_possession, &deposit_input.pubkey) {
|
||||
return Err(ValidatorInductionError::InvaidProofOfPossession);
|
||||
}
|
||||
|
||||
let validator_index = state
|
||||
.validator_registry
|
||||
.iter()
|
||||
.position(|validator| validator.pubkey == deposit_input.pubkey);
|
||||
|
||||
match validator_index {
|
||||
Some(i) => {
|
||||
if state.validator_registry[i].withdrawal_credentials
|
||||
== deposit_input.withdrawal_credentials
|
||||
{
|
||||
state.validator_balances[i] += deposit_data.amount;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(ValidatorInductionError::InvalidWithdrawalCredentials)
|
||||
}
|
||||
None => {
|
||||
let validator = Validator {
|
||||
pubkey: deposit_input.pubkey.clone(),
|
||||
withdrawal_credentials: deposit_input.withdrawal_credentials,
|
||||
proposer_slots: 0,
|
||||
activation_slot: spec.far_future_slot,
|
||||
exit_slot: spec.far_future_slot,
|
||||
withdrawal_slot: spec.far_future_slot,
|
||||
penalized_slot: spec.far_future_slot,
|
||||
exit_count: 0,
|
||||
status_flags: None,
|
||||
latest_custody_reseed_slot: 0,
|
||||
penultimate_custody_reseed_slot: 0,
|
||||
};
|
||||
|
||||
let _index = state.validator_registry.len();
|
||||
state.validator_registry.push(validator);
|
||||
state.validator_balances.push(deposit_data.amount);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
|
||||
use bls::{create_proof_of_possession, Keypair};
|
||||
|
||||
/// The size of a validators deposit in GWei.
|
||||
pub const DEPOSIT_GWEI: u64 = 32_000_000_000;
|
||||
|
||||
fn get_deposit() -> Deposit {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let mut deposit = Deposit::random_for_test(&mut rng);
|
||||
|
||||
let kp = Keypair::random();
|
||||
deposit.deposit_data.deposit_input.pubkey = kp.pk.clone();
|
||||
deposit.deposit_data.deposit_input.proof_of_possession = create_proof_of_possession(&kp);
|
||||
deposit
|
||||
}
|
||||
|
||||
fn get_validator() -> Validator {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
Validator::random_for_test(&mut rng)
|
||||
}
|
||||
|
||||
fn deposit_equals_record(dep: &Deposit, val: &Validator) -> bool {
|
||||
(dep.deposit_data.deposit_input.pubkey == val.pubkey)
|
||||
& (dep.deposit_data.deposit_input.withdrawal_credentials == val.withdrawal_credentials)
|
||||
& (verify_proof_of_possession(
|
||||
&dep.deposit_data.deposit_input.proof_of_possession,
|
||||
&val.pubkey,
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_deposit_valid_empty_validators() {
|
||||
let mut state = BeaconState::default();
|
||||
let mut deposit = get_deposit();
|
||||
let spec = ChainSpec::foundation();
|
||||
deposit.deposit_data.amount = DEPOSIT_GWEI;
|
||||
|
||||
let result = process_deposit(&mut state, &deposit, &spec);
|
||||
|
||||
assert_eq!(result.unwrap(), ());
|
||||
assert!(deposit_equals_record(
|
||||
&deposit,
|
||||
&state.validator_registry[0]
|
||||
));
|
||||
assert_eq!(state.validator_registry.len(), 1);
|
||||
assert_eq!(state.validator_balances.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_deposits_empty_validators() {
|
||||
let mut state = BeaconState::default();
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
for i in 0..5 {
|
||||
let mut deposit = get_deposit();
|
||||
let result = process_deposit(&mut state, &deposit, &spec);
|
||||
deposit.deposit_data.amount = DEPOSIT_GWEI;
|
||||
assert_eq!(result.unwrap(), ());
|
||||
assert!(deposit_equals_record(
|
||||
&deposit,
|
||||
&state.validator_registry[i]
|
||||
));
|
||||
assert_eq!(state.validator_registry.len(), i + 1);
|
||||
assert_eq!(state.validator_balances.len(), i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_deposit_top_out() {
|
||||
let mut state = BeaconState::default();
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let mut deposit = get_deposit();
|
||||
let mut validator = get_validator();
|
||||
|
||||
deposit.deposit_data.amount = DEPOSIT_GWEI;
|
||||
validator.pubkey = deposit.deposit_data.deposit_input.pubkey.clone();
|
||||
validator.withdrawal_credentials =
|
||||
deposit.deposit_data.deposit_input.withdrawal_credentials;
|
||||
|
||||
state.validator_registry.push(validator);
|
||||
state.validator_balances.push(DEPOSIT_GWEI);
|
||||
|
||||
let result = process_deposit(&mut state, &deposit, &spec);
|
||||
|
||||
assert_eq!(result.unwrap(), ());
|
||||
assert!(deposit_equals_record(
|
||||
&deposit,
|
||||
&state.validator_registry[0]
|
||||
));
|
||||
assert_eq!(state.validator_balances[0], DEPOSIT_GWEI * 2);
|
||||
assert_eq!(state.validator_registry.len(), 1);
|
||||
assert_eq!(state.validator_balances.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_deposit_invalid_proof_of_possession() {
|
||||
let mut state = BeaconState::default();
|
||||
let mut deposit = get_deposit();
|
||||
let spec = ChainSpec::foundation();
|
||||
deposit.deposit_data.amount = DEPOSIT_GWEI;
|
||||
deposit.deposit_data.deposit_input.proof_of_possession =
|
||||
create_proof_of_possession(&Keypair::random());
|
||||
|
||||
let result = process_deposit(&mut state, &deposit, &spec);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ValidatorInductionError::InvaidProofOfPossession)
|
||||
);
|
||||
assert_eq!(state.validator_registry.len(), 0);
|
||||
assert_eq!(state.validator_balances.len(), 0);
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
mod inductor;
|
||||
|
||||
pub use crate::inductor::{process_deposit, ValidatorInductionError};
|
@ -1,2 +1,5 @@
|
||||
// The protobuf code-generator is not up-to-date with clippy, therefore we silence some warnings.
|
||||
#[allow(renamed_and_removed_lints)]
|
||||
pub mod services;
|
||||
#[allow(renamed_and_removed_lints)]
|
||||
pub mod services_grpc;
|
||||
|
@ -5,7 +5,7 @@ use protos::services::{
|
||||
use protos::services_grpc::BeaconBlockServiceClient;
|
||||
use ssz::{ssz_encode, Decodable};
|
||||
use std::sync::Arc;
|
||||
use types::{BeaconBlock, BeaconBlockBody, Eth1Data, Hash256, PublicKey, Signature};
|
||||
use types::{BeaconBlock, BeaconBlockBody, Eth1Data, Hash256, Signature, Slot};
|
||||
|
||||
/// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be
|
||||
/// implemented upon it.
|
||||
@ -20,23 +20,18 @@ impl BeaconBlockGrpcClient {
|
||||
}
|
||||
|
||||
impl BeaconNode for BeaconBlockGrpcClient {
|
||||
fn proposer_nonce(&self, pubkey: &PublicKey) -> Result<u64, BeaconNodeError> {
|
||||
// TODO: this might not be required.
|
||||
//
|
||||
// See: https://github.com/ethereum/eth2.0-specs/pull/496
|
||||
panic!("Not implemented.")
|
||||
}
|
||||
/// Request a Beacon Node (BN) to produce a new block at the supplied slot.
|
||||
///
|
||||
/// Returns `None` if it is not possible to produce at the supplied slot. For example, if the
|
||||
/// BN is unable to find a parent block.
|
||||
fn produce_beacon_block(
|
||||
&self,
|
||||
slot: u64,
|
||||
randao_reveal: &Signature,
|
||||
slot: Slot,
|
||||
// TODO: use randao_reveal, when proto APIs have been updated.
|
||||
_randao_reveal: &Signature,
|
||||
) -> Result<Option<BeaconBlock>, BeaconNodeError> {
|
||||
let mut req = ProduceBeaconBlockRequest::new();
|
||||
req.set_slot(slot);
|
||||
req.set_slot(slot.as_u64());
|
||||
|
||||
let reply = self
|
||||
.client
|
||||
@ -54,7 +49,7 @@ impl BeaconNode for BeaconBlockGrpcClient {
|
||||
|
||||
// TODO: this conversion is incomplete; fix it.
|
||||
Ok(Some(BeaconBlock {
|
||||
slot: block.get_slot(),
|
||||
slot: Slot::new(block.get_slot()),
|
||||
parent_root: Hash256::zero(),
|
||||
state_root: Hash256::zero(),
|
||||
randao_reveal,
|
||||
@ -65,11 +60,8 @@ impl BeaconNode for BeaconBlockGrpcClient {
|
||||
signature,
|
||||
body: BeaconBlockBody {
|
||||
proposer_slashings: vec![],
|
||||
casper_slashings: vec![],
|
||||
attester_slashings: vec![],
|
||||
attestations: vec![],
|
||||
custody_reseeds: vec![],
|
||||
custody_challenges: vec![],
|
||||
custody_responses: vec![],
|
||||
deposits: vec![],
|
||||
exits: vec![],
|
||||
},
|
||||
@ -88,7 +80,7 @@ impl BeaconNode for BeaconBlockGrpcClient {
|
||||
|
||||
// TODO: this conversion is incomplete; fix it.
|
||||
let mut grpc_block = GrpcBeaconBlock::new();
|
||||
grpc_block.set_slot(block.slot);
|
||||
grpc_block.set_slot(block.slot.as_u64());
|
||||
grpc_block.set_block_root(vec![0]);
|
||||
grpc_block.set_randao_reveal(ssz_encode(&block.randao_reveal));
|
||||
grpc_block.set_signature(ssz_encode(&block.signature));
|
@ -1,5 +1,58 @@
|
||||
mod grpc;
|
||||
mod service;
|
||||
mod beacon_block_grpc_client;
|
||||
// mod block_producer_service;
|
||||
|
||||
pub use self::grpc::BeaconBlockGrpcClient;
|
||||
pub use self::service::BlockProducerService;
|
||||
use block_producer::{
|
||||
BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, Signer,
|
||||
};
|
||||
use slog::{error, info, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::time::Duration;
|
||||
|
||||
pub use self::beacon_block_grpc_client::BeaconBlockGrpcClient;
|
||||
|
||||
pub struct BlockProducerService<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> {
|
||||
pub block_producer: BlockProducer<T, U, V, W>,
|
||||
pub poll_interval_millis: u64,
|
||||
pub log: Logger,
|
||||
}
|
||||
|
||||
impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducerService<T, U, V, W> {
|
||||
/// Run a loop which polls the block producer each `poll_interval_millis` millseconds.
|
||||
///
|
||||
/// Logs the results of the polls.
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
match self.block_producer.poll() {
|
||||
Err(error) => {
|
||||
error!(self.log, "Block producer poll error"; "error" => format!("{:?}", error))
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::BlockProduced(slot)) => {
|
||||
info!(self.log, "Produced block"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::SlashableBlockNotProduced(slot)) => {
|
||||
warn!(self.log, "Slashable block was not signed"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::BlockProductionNotRequired(slot)) => {
|
||||
info!(self.log, "Block production not required"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::ProducerDutiesUnknown(slot)) => {
|
||||
error!(self.log, "Block production duties unknown"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::SlotAlreadyProcessed(slot)) => {
|
||||
warn!(self.log, "Attempted to re-process slot"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::BeaconNodeUnableToProduceBlock(slot)) => {
|
||||
error!(self.log, "Beacon node unable to produce block"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::SignerRejection(slot)) => {
|
||||
error!(self.log, "The cryptographic signer refused to sign the block"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::ValidatorIsUnknown(slot)) => {
|
||||
error!(self.log, "The Beacon Node does not recognise the validator"; "slot" => slot)
|
||||
}
|
||||
};
|
||||
|
||||
std::thread::sleep(Duration::from_millis(self.poll_interval_millis));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
use block_producer::{
|
||||
BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, Signer,
|
||||
};
|
||||
use slog::{error, info, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct BlockProducerService<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> {
|
||||
pub block_producer: BlockProducer<T, U, V, W>,
|
||||
pub poll_interval_millis: u64,
|
||||
pub log: Logger,
|
||||
}
|
||||
|
||||
impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducerService<T, U, V, W> {
|
||||
/// Run a loop which polls the block producer each `poll_interval_millis` millseconds.
|
||||
///
|
||||
/// Logs the results of the polls.
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
match self.block_producer.poll() {
|
||||
Err(error) => {
|
||||
error!(self.log, "Block producer poll error"; "error" => format!("{:?}", error))
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::BlockProduced(slot)) => {
|
||||
info!(self.log, "Produced block"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::SlashableBlockNotProduced(slot)) => {
|
||||
warn!(self.log, "Slashable block was not signed"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::BlockProductionNotRequired(slot)) => {
|
||||
info!(self.log, "Block production not required"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::ProducerDutiesUnknown(slot)) => {
|
||||
error!(self.log, "Block production duties unknown"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::SlotAlreadyProcessed(slot)) => {
|
||||
warn!(self.log, "Attempted to re-process slot"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::BeaconNodeUnableToProduceBlock(slot)) => {
|
||||
error!(self.log, "Beacon node unable to produce block"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::SignerRejection(slot)) => {
|
||||
error!(self.log, "The cryptographic signer refused to sign the block"; "slot" => slot)
|
||||
}
|
||||
Ok(BlockProducerPollOutcome::ValidatorIsUnknown(slot)) => {
|
||||
error!(self.log, "The Beacon Node does not recognise the validator"; "slot" => slot)
|
||||
}
|
||||
};
|
||||
|
||||
std::thread::sleep(Duration::from_millis(self.poll_interval_millis));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use block_producer::{DutiesReader, DutiesReaderError};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use types::{Epoch, Slot};
|
||||
|
||||
/// The information required for a validator to propose and attest during some epoch.
|
||||
///
|
||||
@ -10,14 +11,14 @@ use std::sync::RwLock;
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Default)]
|
||||
pub struct EpochDuties {
|
||||
pub validator_index: u64,
|
||||
pub block_production_slot: Option<u64>,
|
||||
pub block_production_slot: Option<Slot>,
|
||||
// Future shard info
|
||||
}
|
||||
|
||||
impl EpochDuties {
|
||||
/// Returns `true` if the supplied `slot` is a slot in which the validator should produce a
|
||||
/// block.
|
||||
pub fn is_block_production_slot(&self, slot: u64) -> bool {
|
||||
pub fn is_block_production_slot(&self, slot: Slot) -> bool {
|
||||
match self.block_production_slot {
|
||||
Some(s) if s == slot => true,
|
||||
_ => false,
|
||||
@ -32,7 +33,7 @@ pub enum EpochDutiesMapError {
|
||||
/// Maps an `epoch` to some `EpochDuties` for a single validator.
|
||||
pub struct EpochDutiesMap {
|
||||
pub epoch_length: u64,
|
||||
pub map: RwLock<HashMap<u64, EpochDuties>>,
|
||||
pub map: RwLock<HashMap<Epoch, EpochDuties>>,
|
||||
}
|
||||
|
||||
impl EpochDutiesMap {
|
||||
@ -43,17 +44,17 @@ impl EpochDutiesMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, epoch: u64) -> Result<Option<EpochDuties>, EpochDutiesMapError> {
|
||||
pub fn get(&self, epoch: Epoch) -> Result<Option<EpochDuties>, EpochDutiesMapError> {
|
||||
let map = self.map.read().map_err(|_| EpochDutiesMapError::Poisoned)?;
|
||||
match map.get(&epoch) {
|
||||
Some(duties) => Ok(Some(duties.clone())),
|
||||
Some(duties) => Ok(Some(*duties)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
&self,
|
||||
epoch: u64,
|
||||
epoch: Epoch,
|
||||
epoch_duties: EpochDuties,
|
||||
) -> Result<Option<EpochDuties>, EpochDutiesMapError> {
|
||||
let mut map = self
|
||||
@ -65,10 +66,8 @@ impl EpochDutiesMap {
|
||||
}
|
||||
|
||||
impl DutiesReader for EpochDutiesMap {
|
||||
fn is_block_production_slot(&self, slot: u64) -> Result<bool, DutiesReaderError> {
|
||||
let epoch = slot
|
||||
.checked_div(self.epoch_length)
|
||||
.ok_or_else(|| DutiesReaderError::EpochLengthIsZero)?;
|
||||
fn is_block_production_slot(&self, slot: Slot) -> Result<bool, DutiesReaderError> {
|
||||
let epoch = slot.epoch(self.epoch_length);
|
||||
|
||||
let map = self.map.read().map_err(|_| DutiesReaderError::Poisoned)?;
|
||||
let duties = map
|
@ -3,7 +3,7 @@ use super::EpochDuties;
|
||||
use protos::services::{ProposeBlockSlotRequest, PublicKey as IndexRequest};
|
||||
use protos::services_grpc::ValidatorServiceClient;
|
||||
use ssz::ssz_encode;
|
||||
use types::PublicKey;
|
||||
use types::{Epoch, PublicKey, Slot};
|
||||
|
||||
impl BeaconNode for ValidatorServiceClient {
|
||||
/// Request the shuffling from the Beacon Node (BN).
|
||||
@ -14,7 +14,7 @@ impl BeaconNode for ValidatorServiceClient {
|
||||
/// Note: presently only block production information is returned.
|
||||
fn request_shuffling(
|
||||
&self,
|
||||
epoch: u64,
|
||||
epoch: Epoch,
|
||||
public_key: &PublicKey,
|
||||
) -> Result<Option<EpochDuties>, BeaconNodeError> {
|
||||
// Lookup the validator index for the supplied public key.
|
||||
@ -29,7 +29,7 @@ impl BeaconNode for ValidatorServiceClient {
|
||||
|
||||
let mut req = ProposeBlockSlotRequest::new();
|
||||
req.set_validator_index(validator_index);
|
||||
req.set_epoch(epoch);
|
||||
req.set_epoch(epoch.as_u64());
|
||||
|
||||
let reply = self
|
||||
.propose_block_slot(&req)
|
||||
@ -41,6 +41,11 @@ impl BeaconNode for ValidatorServiceClient {
|
||||
None
|
||||
};
|
||||
|
||||
let block_production_slot = match block_production_slot {
|
||||
Some(slot) => Some(Slot::new(slot)),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Some(EpochDuties {
|
||||
validator_index,
|
||||
block_production_slot,
|
||||
|
@ -1,31 +1,31 @@
|
||||
mod duties_map;
|
||||
mod epoch_duties;
|
||||
mod grpc;
|
||||
mod service;
|
||||
#[cfg(test)]
|
||||
mod test_node;
|
||||
mod traits;
|
||||
|
||||
pub use self::duties_map::EpochDutiesMap;
|
||||
use self::duties_map::{EpochDuties, EpochDutiesMapError};
|
||||
pub use self::epoch_duties::EpochDutiesMap;
|
||||
use self::epoch_duties::{EpochDuties, EpochDutiesMapError};
|
||||
pub use self::service::DutiesManagerService;
|
||||
use self::traits::{BeaconNode, BeaconNodeError};
|
||||
use bls::PublicKey;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::ChainSpec;
|
||||
use types::{ChainSpec, Epoch};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum PollOutcome {
|
||||
/// The `EpochDuties` were not updated during this poll.
|
||||
NoChange(u64),
|
||||
NoChange(Epoch),
|
||||
/// The `EpochDuties` for the `epoch` were previously unknown, but obtained in the poll.
|
||||
NewDuties(u64, EpochDuties),
|
||||
NewDuties(Epoch, EpochDuties),
|
||||
/// New `EpochDuties` were obtained, different to those which were previously known. This is
|
||||
/// likely to be the result of chain re-organisation.
|
||||
DutiesChanged(u64, EpochDuties),
|
||||
DutiesChanged(Epoch, EpochDuties),
|
||||
/// The Beacon Node was unable to return the duties as the validator is unknown, or the
|
||||
/// shuffling for the epoch is unknown.
|
||||
UnknownValidatorOrEpoch(u64),
|
||||
UnknownValidatorOrEpoch(Epoch),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -33,7 +33,6 @@ pub enum Error {
|
||||
SlotClockError,
|
||||
SlotUnknowable,
|
||||
EpochMapPoisoned,
|
||||
EpochLengthIsZero,
|
||||
BeaconNodeError(BeaconNodeError),
|
||||
}
|
||||
|
||||
@ -62,9 +61,7 @@ impl<T: SlotClock, U: BeaconNode> DutiesManager<T, U> {
|
||||
.map_err(|_| Error::SlotClockError)?
|
||||
.ok_or(Error::SlotUnknowable)?;
|
||||
|
||||
let epoch = slot
|
||||
.checked_div(self.spec.epoch_length)
|
||||
.ok_or(Error::EpochLengthIsZero)?;
|
||||
let epoch = slot.epoch(self.spec.epoch_length);
|
||||
|
||||
if let Some(duties) = self.beacon_node.request_shuffling(epoch, &self.pubkey)? {
|
||||
// If these duties were known, check to see if they're updates or identical.
|
||||
@ -105,6 +102,7 @@ mod tests {
|
||||
use super::*;
|
||||
use bls::Keypair;
|
||||
use slot_clock::TestingSlotClock;
|
||||
use types::Slot;
|
||||
|
||||
// TODO: implement more thorough testing.
|
||||
// https://github.com/sigp/lighthouse/issues/160
|
||||
@ -130,25 +128,34 @@ mod tests {
|
||||
// Configure response from the BeaconNode.
|
||||
let duties = EpochDuties {
|
||||
validator_index: 0,
|
||||
block_production_slot: Some(10),
|
||||
block_production_slot: Some(Slot::new(10)),
|
||||
};
|
||||
beacon_node.set_next_shuffling_result(Ok(Some(duties)));
|
||||
|
||||
// Get the duties for the first time...
|
||||
assert_eq!(manager.poll(), Ok(PollOutcome::NewDuties(0, duties)));
|
||||
assert_eq!(
|
||||
manager.poll(),
|
||||
Ok(PollOutcome::NewDuties(Epoch::new(0), duties))
|
||||
);
|
||||
// Get the same duties again...
|
||||
assert_eq!(manager.poll(), Ok(PollOutcome::NoChange(0)));
|
||||
assert_eq!(manager.poll(), Ok(PollOutcome::NoChange(Epoch::new(0))));
|
||||
|
||||
// Return new duties.
|
||||
let duties = EpochDuties {
|
||||
validator_index: 0,
|
||||
block_production_slot: Some(11),
|
||||
block_production_slot: Some(Slot::new(11)),
|
||||
};
|
||||
beacon_node.set_next_shuffling_result(Ok(Some(duties)));
|
||||
assert_eq!(manager.poll(), Ok(PollOutcome::DutiesChanged(0, duties)));
|
||||
assert_eq!(
|
||||
manager.poll(),
|
||||
Ok(PollOutcome::DutiesChanged(Epoch::new(0), duties))
|
||||
);
|
||||
|
||||
// Return no duties.
|
||||
beacon_node.set_next_shuffling_result(Ok(None));
|
||||
assert_eq!(manager.poll(), Ok(PollOutcome::UnknownValidatorOrEpoch(0)));
|
||||
assert_eq!(
|
||||
manager.poll(),
|
||||
Ok(PollOutcome::UnknownValidatorOrEpoch(Epoch::new(0)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ use super::traits::{BeaconNode, BeaconNodeError};
|
||||
use super::EpochDuties;
|
||||
use bls::PublicKey;
|
||||
use std::sync::RwLock;
|
||||
use types::Epoch;
|
||||
|
||||
type ShufflingResult = Result<Option<EpochDuties>, BeaconNodeError>;
|
||||
|
||||
/// A test-only struct used to simulate a Beacon Node.
|
||||
#[derive(Default)]
|
||||
pub struct TestBeaconNode {
|
||||
pub request_shuffling_input: RwLock<Option<(u64, PublicKey)>>,
|
||||
pub request_shuffling_input: RwLock<Option<(Epoch, PublicKey)>>,
|
||||
pub request_shuffling_result: RwLock<Option<ShufflingResult>>,
|
||||
}
|
||||
|
||||
@ -21,7 +22,7 @@ impl TestBeaconNode {
|
||||
|
||||
impl BeaconNode for TestBeaconNode {
|
||||
/// Returns the value specified by the `set_next_shuffling_result`.
|
||||
fn request_shuffling(&self, epoch: u64, public_key: &PublicKey) -> ShufflingResult {
|
||||
fn request_shuffling(&self, epoch: Epoch, public_key: &PublicKey) -> ShufflingResult {
|
||||
*self.request_shuffling_input.write().unwrap() = Some((epoch, public_key.clone()));
|
||||
match *self.request_shuffling_result.read().unwrap() {
|
||||
Some(ref r) => r.clone(),
|
||||
|
@ -1,5 +1,6 @@
|
||||
use super::EpochDuties;
|
||||
use bls::PublicKey;
|
||||
use types::Epoch;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BeaconNodeError {
|
||||
@ -13,7 +14,7 @@ pub trait BeaconNode: Send + Sync {
|
||||
/// Returns Ok(None) if the public key is unknown, or the shuffling for that epoch is unknown.
|
||||
fn request_shuffling(
|
||||
&self,
|
||||
epoch: u64,
|
||||
epoch: Epoch,
|
||||
public_key: &PublicKey,
|
||||
) -> Result<Option<EpochDuties>, BeaconNodeError>;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService};
|
||||
use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap};
|
||||
use crate::config::ClientConfig;
|
||||
use block_producer::{test_utils::TestSigner, BlockProducer};
|
||||
use block_producer::{test_utils::LocalSigner, BlockProducer};
|
||||
use bls::Keypair;
|
||||
use clap::{App, Arg};
|
||||
use grpcio::{ChannelBuilder, EnvBuilder};
|
||||
@ -88,9 +88,12 @@ fn main() {
|
||||
let spec = Arc::new(ChainSpec::foundation());
|
||||
|
||||
// Clock for determining the present slot.
|
||||
// TODO: this shouldn't be a static time, instead it should be pulled from the beacon node.
|
||||
// https://github.com/sigp/lighthouse/issues/160
|
||||
let genesis_time = 1_549_935_547;
|
||||
let slot_clock = {
|
||||
info!(log, "Genesis time"; "unix_epoch_seconds" => spec.genesis_time);
|
||||
let clock = SystemTimeSlotClock::new(spec.genesis_time, spec.slot_duration)
|
||||
info!(log, "Genesis time"; "unix_epoch_seconds" => genesis_time);
|
||||
let clock = SystemTimeSlotClock::new(genesis_time, spec.slot_duration)
|
||||
.expect("Unable to instantiate SystemTimeSlotClock.");
|
||||
Arc::new(clock)
|
||||
};
|
||||
@ -139,15 +142,14 @@ fn main() {
|
||||
// Spawn a new thread to perform block production for the validator.
|
||||
let producer_thread = {
|
||||
let spec = spec.clone();
|
||||
let pubkey = keypair.pk.clone();
|
||||
let signer = Arc::new(TestSigner::new(keypair.clone()));
|
||||
let signer = Arc::new(LocalSigner::new(keypair.clone()));
|
||||
let duties_map = duties_map.clone();
|
||||
let slot_clock = slot_clock.clone();
|
||||
let log = log.clone();
|
||||
let client = Arc::new(BeaconBlockGrpcClient::new(beacon_block_grpc_client.clone()));
|
||||
thread::spawn(move || {
|
||||
let block_producer =
|
||||
BlockProducer::new(spec, pubkey, duties_map, slot_clock, client, signer);
|
||||
BlockProducer::new(spec, duties_map, slot_clock, client, signer);
|
||||
let mut block_producer_service = BlockProducerService {
|
||||
block_producer,
|
||||
poll_interval_millis,
|
||||
|
Loading…
Reference in New Issue
Block a user