mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-20 13:24:44 +00:00
Strip out old code
All of these files have been moved to either: - https://github.com/sigp/lighthouse-beacon - https://github.com/sigp/lighthouse-validator - https://github.com/sigp/lighthouse-common For rationale, see: https://github.com/sigp/lighthouse/issues/197
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "beacon_node"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../eth2/utils/bls" }
|
||||
beacon_chain = { path = "beacon_chain" }
|
||||
grpcio = { version = "0.4", default-features = false, features = ["protobuf-codec"] }
|
||||
protobuf = "2.0.2"
|
||||
protos = { path = "../protos" }
|
||||
clap = "2.32.0"
|
||||
db = { path = "db" }
|
||||
dirs = "1.0.3"
|
||||
futures = "0.1.23"
|
||||
slog = "^2.2.3"
|
||||
slot_clock = { path = "../eth2/utils/slot_clock" }
|
||||
slog-term = "^2.4.0"
|
||||
slog-async = "^2.3.0"
|
||||
types = { path = "../eth2/types" }
|
||||
ssz = { path = "../eth2/utils/ssz" }
|
||||
tokio = "0.1"
|
||||
@@ -1,24 +0,0 @@
|
||||
[package]
|
||||
name = "beacon_chain"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
block_producer = { path = "../../eth2/block_producer" }
|
||||
bls = { path = "../../eth2/utils/bls" }
|
||||
boolean-bitfield = { path = "../../eth2/utils/boolean-bitfield" }
|
||||
db = { path = "../db" }
|
||||
failure = "0.1"
|
||||
failure_derive = "0.1"
|
||||
hashing = { path = "../../eth2/utils/hashing" }
|
||||
parking_lot = "0.7"
|
||||
log = "0.4"
|
||||
env_logger = "0.6"
|
||||
serde = "1.0"
|
||||
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,217 +0,0 @@
|
||||
use state_processing::validate_attestation_without_signature;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use types::{
|
||||
beacon_state::CommitteesError, AggregateSignature, Attestation, AttestationData, BeaconState,
|
||||
Bitfield, ChainSpec, FreeAttestation, Signature,
|
||||
};
|
||||
|
||||
const PHASE_0_CUSTODY_BIT: bool = false;
|
||||
|
||||
/// Provides the functionality to:
|
||||
///
|
||||
/// - Recieve a `FreeAttestation` and aggregate it into an `Attestation` (or create a new if it
|
||||
/// doesn't exist).
|
||||
/// - Store all aggregated or created `Attestation`s.
|
||||
/// - Produce a list of attestations that would be valid for inclusion in some `BeaconState` (and
|
||||
/// therefore valid for inclusion in a `BeaconBlock`.
|
||||
///
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
pub struct Outcome {
|
||||
pub valid: bool,
|
||||
pub message: Message,
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
/// The free attestation was added to an existing attestation.
|
||||
Aggregated,
|
||||
/// The free attestation has already been aggregated to an existing attestation.
|
||||
AggregationNotRequired,
|
||||
/// The free attestation was transformed into a new attestation.
|
||||
NewAttestationCreated,
|
||||
/// The supplied `validator_index` is not in the committee for the given `shard` and `slot`.
|
||||
BadValidatorIndex,
|
||||
/// The given `signature` did not match the `pubkey` in the given
|
||||
/// `state.validator_registry`.
|
||||
BadSignature,
|
||||
/// The given `slot` does not match the validators committee assignment.
|
||||
BadSlot,
|
||||
/// The given `shard` does not match the validators committee assignment.
|
||||
BadShard,
|
||||
}
|
||||
|
||||
macro_rules! some_or_invalid {
|
||||
($expression: expr, $error: expr) => {
|
||||
match $expression {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Ok(Outcome {
|
||||
valid: false,
|
||||
message: $error,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl AttestationAggregator {
|
||||
/// Instantiates a new AttestationAggregator with an empty database.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
store: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Accepts some `FreeAttestation`, validates it and either aggregates it upon some existing
|
||||
/// `Attestation` or produces a new `Attestation`.
|
||||
///
|
||||
/// The "validation" provided is not complete, instead the following points are checked:
|
||||
/// - The given `validator_index` is in the committee for the given `shard` for the given
|
||||
/// `slot`.
|
||||
/// - The signature is verified against that of the validator at `validator_index`.
|
||||
pub fn process_free_attestation(
|
||||
&mut self,
|
||||
state: &BeaconState,
|
||||
free_attestation: &FreeAttestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Outcome, CommitteesError> {
|
||||
let (slot, shard, committee_index) = some_or_invalid!(
|
||||
state.attestation_slot_and_shard_for_validator(
|
||||
free_attestation.validator_index as usize,
|
||||
spec,
|
||||
)?,
|
||||
Message::BadValidatorIndex
|
||||
);
|
||||
|
||||
if free_attestation.data.slot != slot {
|
||||
return Ok(Outcome {
|
||||
valid: false,
|
||||
message: Message::BadSlot,
|
||||
});
|
||||
}
|
||||
if free_attestation.data.shard != shard {
|
||||
return Ok(Outcome {
|
||||
valid: false,
|
||||
message: Message::BadShard,
|
||||
});
|
||||
}
|
||||
|
||||
let signable_message = free_attestation.data.signable_message(PHASE_0_CUSTODY_BIT);
|
||||
|
||||
let validator_record = some_or_invalid!(
|
||||
state
|
||||
.validator_registry
|
||||
.get(free_attestation.validator_index as usize),
|
||||
Message::BadValidatorIndex
|
||||
);
|
||||
|
||||
if !free_attestation
|
||||
.signature
|
||||
.verify(&signable_message, &validator_record.pubkey)
|
||||
{
|
||||
return Ok(Outcome {
|
||||
valid: false,
|
||||
message: Message::BadSignature,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(existing_attestation) = self.store.get(&signable_message) {
|
||||
if let Some(updated_attestation) = aggregate_attestation(
|
||||
existing_attestation,
|
||||
&free_attestation.signature,
|
||||
committee_index as usize,
|
||||
) {
|
||||
self.store.insert(signable_message, updated_attestation);
|
||||
Ok(Outcome {
|
||||
valid: true,
|
||||
message: Message::Aggregated,
|
||||
})
|
||||
} else {
|
||||
Ok(Outcome {
|
||||
valid: true,
|
||||
message: Message::AggregationNotRequired,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let mut aggregate_signature = AggregateSignature::new();
|
||||
aggregate_signature.add(&free_attestation.signature);
|
||||
let mut aggregation_bitfield = Bitfield::new();
|
||||
aggregation_bitfield.set(committee_index as usize, true);
|
||||
let new_attestation = Attestation {
|
||||
data: free_attestation.data.clone(),
|
||||
aggregation_bitfield,
|
||||
custody_bitfield: Bitfield::new(),
|
||||
aggregate_signature,
|
||||
};
|
||||
self.store.insert(signable_message, new_attestation);
|
||||
Ok(Outcome {
|
||||
valid: true,
|
||||
message: Message::NewAttestationCreated,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all known attestations which are:
|
||||
///
|
||||
/// - Valid for the given state
|
||||
/// - Not already in `state.latest_attestations`.
|
||||
pub fn get_attestations_for_state(
|
||||
&self,
|
||||
state: &BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Vec<Attestation> {
|
||||
let mut known_attestation_data: HashSet<AttestationData> = HashSet::new();
|
||||
|
||||
state.latest_attestations.iter().for_each(|attestation| {
|
||||
known_attestation_data.insert(attestation.data.clone());
|
||||
});
|
||||
|
||||
self.store
|
||||
.values()
|
||||
.filter_map(|attestation| {
|
||||
if validate_attestation_without_signature(&state, attestation, spec).is_ok()
|
||||
&& !known_attestation_data.contains(&attestation.data)
|
||||
{
|
||||
Some(attestation.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a new `Attestation` where:
|
||||
///
|
||||
/// - `signature` is added to `Attestation.aggregate_signature`
|
||||
/// - Attestation.aggregation_bitfield[committee_index]` is set to true.
|
||||
fn aggregate_attestation(
|
||||
existing_attestation: &Attestation,
|
||||
signature: &Signature,
|
||||
committee_index: usize,
|
||||
) -> Option<Attestation> {
|
||||
let already_signed = existing_attestation
|
||||
.aggregation_bitfield
|
||||
.get(committee_index)
|
||||
.unwrap_or(false);
|
||||
|
||||
if already_signed {
|
||||
None
|
||||
} else {
|
||||
let mut aggregation_bitfield = existing_attestation.aggregation_bitfield.clone();
|
||||
aggregation_bitfield.set(committee_index, true);
|
||||
let mut aggregate_signature = existing_attestation.aggregate_signature.clone();
|
||||
aggregate_signature.add(&signature);
|
||||
|
||||
Some(Attestation {
|
||||
aggregation_bitfield,
|
||||
aggregate_signature,
|
||||
..existing_attestation.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use types::Hash256;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AttestationTargets {
|
||||
map: HashMap<u64, Hash256>,
|
||||
}
|
||||
|
||||
impl AttestationTargets {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, validator_index: u64) -> Option<&Hash256> {
|
||||
self.map.get(&validator_index)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, validator_index: u64, block_hash: Hash256) -> Option<Hash256> {
|
||||
self.map.insert(validator_index, block_hash)
|
||||
}
|
||||
}
|
||||
@@ -1,564 +0,0 @@
|
||||
use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome};
|
||||
use crate::attestation_targets::AttestationTargets;
|
||||
use crate::block_graph::BlockGraph;
|
||||
use crate::checkpoint::CheckPoint;
|
||||
use db::{
|
||||
stores::{BeaconBlockStore, BeaconStateStore},
|
||||
ClientDB, DBError,
|
||||
};
|
||||
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::CommitteesError,
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Crosslink, Deposit,
|
||||
Epoch, Eth1Data, FreeAttestation, Hash256, PublicKey, Signature, Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
InsufficientValidators,
|
||||
BadRecentBlockRoots,
|
||||
CommitteesError(CommitteesError),
|
||||
DBInconsistent(String),
|
||||
DBError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ValidBlock {
|
||||
/// The block was sucessfully processed.
|
||||
Processed,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum InvalidBlock {
|
||||
/// The block slot is greater than the present slot.
|
||||
FutureSlot,
|
||||
/// The block state_root does not match the generated state.
|
||||
StateRootMismatch,
|
||||
/// The blocks parent_root is unknown.
|
||||
ParentUnknown,
|
||||
/// There was an error whilst advancing the parent state to the present slot. This condition
|
||||
/// should not occur, it likely represents an internal error.
|
||||
SlotProcessingError(SlotProcessingError),
|
||||
/// The block could not be applied to the state, it is invalid.
|
||||
PerBlockProcessingError(BlockProcessingError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockProcessingOutcome {
|
||||
/// The block was sucessfully validated.
|
||||
ValidBlock(ValidBlock),
|
||||
/// The block was not sucessfully validated.
|
||||
InvalidBlock(InvalidBlock),
|
||||
}
|
||||
|
||||
pub struct BeaconChain<T: ClientDB + Sized, U: SlotClock> {
|
||||
pub block_store: Arc<BeaconBlockStore<T>>,
|
||||
pub state_store: Arc<BeaconStateStore<T>>,
|
||||
pub slot_clock: U,
|
||||
pub block_graph: BlockGraph,
|
||||
pub attestation_aggregator: RwLock<AttestationAggregator>,
|
||||
canonical_head: RwLock<CheckPoint>,
|
||||
finalized_head: RwLock<CheckPoint>,
|
||||
pub state: RwLock<BeaconState>,
|
||||
pub latest_attestation_targets: RwLock<AttestationTargets>,
|
||||
pub spec: ChainSpec,
|
||||
}
|
||||
|
||||
impl<T, U> BeaconChain<T, U>
|
||||
where
|
||||
T: ClientDB,
|
||||
U: SlotClock,
|
||||
{
|
||||
/// Instantiate a new Beacon Chain, from genesis.
|
||||
pub fn genesis(
|
||||
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,
|
||||
) -> Result<Self, Error> {
|
||||
if initial_validator_deposits.is_empty() {
|
||||
return Err(Error::InsufficientValidators);
|
||||
}
|
||||
|
||||
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 = BeaconBlock::genesis(state_root, &spec);
|
||||
let block_root = genesis_block.canonical_root();
|
||||
block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?;
|
||||
|
||||
let block_graph = BlockGraph::new();
|
||||
block_graph.add_leaf(&Hash256::zero(), block_root);
|
||||
|
||||
let finalized_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,
|
||||
genesis_state.clone(),
|
||||
state_root,
|
||||
));
|
||||
let attestation_aggregator = RwLock::new(AttestationAggregator::new());
|
||||
|
||||
let latest_attestation_targets = RwLock::new(AttestationTargets::new());
|
||||
|
||||
Ok(Self {
|
||||
block_store,
|
||||
state_store,
|
||||
slot_clock,
|
||||
block_graph,
|
||||
attestation_aggregator,
|
||||
state: RwLock::new(genesis_state.clone()),
|
||||
finalized_head,
|
||||
canonical_head,
|
||||
latest_attestation_targets,
|
||||
spec,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update the canonical head to some new values.
|
||||
pub fn update_canonical_head(
|
||||
&self,
|
||||
new_beacon_block: BeaconBlock,
|
||||
new_beacon_block_root: Hash256,
|
||||
new_beacon_state: BeaconState,
|
||||
new_beacon_state_root: Hash256,
|
||||
) {
|
||||
let mut head = self.canonical_head.write();
|
||||
head.update(
|
||||
new_beacon_block,
|
||||
new_beacon_block_root,
|
||||
new_beacon_state,
|
||||
new_beacon_state_root,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a read-lock guarded `CheckPoint` struct for reading the head (as chosen by the
|
||||
/// fork-choice rule).
|
||||
///
|
||||
/// It is important to note that the `beacon_state` returned may not match the present slot. It
|
||||
/// is the state as it was when the head block was recieved, which could be some slots prior to
|
||||
/// now.
|
||||
pub fn head(&self) -> RwLockReadGuard<CheckPoint> {
|
||||
self.canonical_head.read()
|
||||
}
|
||||
|
||||
/// Update the justified head to some new values.
|
||||
pub fn update_finalized_head(
|
||||
&self,
|
||||
new_beacon_block: BeaconBlock,
|
||||
new_beacon_block_root: Hash256,
|
||||
new_beacon_state: BeaconState,
|
||||
new_beacon_state_root: Hash256,
|
||||
) {
|
||||
let mut finalized_head = self.finalized_head.write();
|
||||
finalized_head.update(
|
||||
new_beacon_block,
|
||||
new_beacon_block_root,
|
||||
new_beacon_state,
|
||||
new_beacon_state_root,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a read-lock guarded `CheckPoint` struct for reading the justified head (as chosen,
|
||||
/// indirectly, by the fork-choice rule).
|
||||
pub fn finalized_head(&self) -> RwLockReadGuard<CheckPoint> {
|
||||
self.finalized_head.read()
|
||||
}
|
||||
|
||||
/// Advance the `self.state` `BeaconState` to the supplied slot.
|
||||
///
|
||||
/// This will perform per_slot and per_epoch processing as required.
|
||||
///
|
||||
/// The `previous_block_root` will be set to the root of the current head block (as determined
|
||||
/// by the fork-choice rule).
|
||||
///
|
||||
/// 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: Slot) -> Result<(), SlotProcessingError> {
|
||||
let state_slot = self.state.read().slot;
|
||||
let head_block_root = self.head().beacon_block_root;
|
||||
for _ in state_slot.as_u64()..slot.as_u64() {
|
||||
self.state
|
||||
.write()
|
||||
.per_slot_processing(head_block_root, &self.spec)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the the validator index (if any) for the given public key.
|
||||
///
|
||||
/// Information is retrieved from the present `beacon_state.validator_registry`.
|
||||
pub fn validator_index(&self, pubkey: &PublicKey) -> Option<usize> {
|
||||
for (i, validator) in self
|
||||
.head()
|
||||
.beacon_state
|
||||
.validator_registry
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if validator.pubkey == *pubkey {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
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
|
||||
/// is before genesis (i.e., a negative slot).
|
||||
///
|
||||
/// 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<Slot> {
|
||||
match self.slot_clock.present_slot() {
|
||||
Ok(Some(some_slot)) => Some(some_slot),
|
||||
Ok(None) => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns slot of the present state.
|
||||
///
|
||||
/// 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) -> Slot {
|
||||
self.state.read().slot
|
||||
}
|
||||
|
||||
/// Returns the block proposer for a given slot.
|
||||
///
|
||||
/// 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: Slot) -> Result<usize, CommitteesError> {
|
||||
let index = self
|
||||
.state
|
||||
.read()
|
||||
.get_beacon_proposer_index(slot, &self.spec)?;
|
||||
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
/// Returns the justified slot for the present state.
|
||||
pub fn justified_epoch(&self) -> Epoch {
|
||||
self.state.read().justified_epoch
|
||||
}
|
||||
|
||||
/// Returns the attestation slot and shard for a given validator index.
|
||||
///
|
||||
/// Information is read from the current state, so only information from the present and prior
|
||||
/// epoch is available.
|
||||
pub fn validator_attestion_slot_and_shard(
|
||||
&self,
|
||||
validator_index: usize,
|
||||
) -> Result<Option<(Slot, u64)>, CommitteesError> {
|
||||
if let Some((slot, shard, _committee)) = self
|
||||
.state
|
||||
.read()
|
||||
.attestation_slot_and_shard_for_validator(validator_index, &self.spec)?
|
||||
{
|
||||
Ok(Some((slot, shard)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_epoch = self.justified_epoch();
|
||||
let justified_block_root = *self
|
||||
.state
|
||||
.read()
|
||||
.get_block_root(
|
||||
justified_epoch.start_slot(self.spec.epoch_length),
|
||||
&self.spec,
|
||||
)
|
||||
.ok_or_else(|| Error::BadRecentBlockRoots)?;
|
||||
|
||||
let epoch_boundary_root = *self
|
||||
.state
|
||||
.read()
|
||||
.get_block_root(
|
||||
self.state.read().current_epoch_start_slot(&self.spec),
|
||||
&self.spec,
|
||||
)
|
||||
.ok_or_else(|| Error::BadRecentBlockRoots)?;
|
||||
|
||||
Ok(AttestationData {
|
||||
slot: self.state.read().slot,
|
||||
shard,
|
||||
beacon_block_root: self.head().beacon_block_root,
|
||||
epoch_boundary_root,
|
||||
shard_block_root: Hash256::zero(),
|
||||
latest_crosslink: Crosslink {
|
||||
epoch: self.state.read().slot.epoch(self.spec.epoch_length),
|
||||
shard_block_root: Hash256::zero(),
|
||||
},
|
||||
justified_epoch,
|
||||
justified_block_root,
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate a `FreeAttestation` and either:
|
||||
///
|
||||
/// - Create a new `Attestation`.
|
||||
/// - Aggregate it to an existing `Attestation`.
|
||||
pub fn process_free_attestation(
|
||||
&self,
|
||||
free_attestation: FreeAttestation,
|
||||
) -> Result<AggregationOutcome, Error> {
|
||||
self.attestation_aggregator
|
||||
.write()
|
||||
.process_free_attestation(&self.state.read(), &free_attestation, &self.spec)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Set the latest attestation target for some validator.
|
||||
pub fn insert_latest_attestation_target(&self, validator_index: u64, block_root: Hash256) {
|
||||
let mut targets = self.latest_attestation_targets.write();
|
||||
targets.insert(validator_index, block_root);
|
||||
}
|
||||
|
||||
/// Get the latest attestation target for some validator.
|
||||
pub fn get_latest_attestation_target(&self, validator_index: u64) -> Option<Hash256> {
|
||||
let targets = self.latest_attestation_targets.read();
|
||||
|
||||
match targets.get(validator_index) {
|
||||
Some(hash) => Some(*hash),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Dumps the entire canonical chain, from the head to genesis to a vector for analysis.
|
||||
///
|
||||
/// This could be a very expensive operation and should only be done in testing/analysis
|
||||
/// activities.
|
||||
pub fn chain_dump(&self) -> Result<Vec<CheckPoint>, Error> {
|
||||
let mut dump = vec![];
|
||||
|
||||
let mut last_slot = CheckPoint {
|
||||
beacon_block: self.head().beacon_block.clone(),
|
||||
beacon_block_root: self.head().beacon_block_root,
|
||||
beacon_state: self.head().beacon_state.clone(),
|
||||
beacon_state_root: self.head().beacon_state_root,
|
||||
};
|
||||
|
||||
dump.push(last_slot.clone());
|
||||
|
||||
loop {
|
||||
let beacon_block_root = last_slot.beacon_block.parent_root;
|
||||
|
||||
if beacon_block_root == self.spec.zero_hash {
|
||||
break; // Genesis has been reached.
|
||||
}
|
||||
|
||||
let beacon_block = self
|
||||
.block_store
|
||||
.get_deserialized(&beacon_block_root)?
|
||||
.ok_or_else(|| {
|
||||
Error::DBInconsistent(format!("Missing block {}", beacon_block_root))
|
||||
})?;
|
||||
let beacon_state_root = beacon_block.state_root;
|
||||
let beacon_state = self
|
||||
.state_store
|
||||
.get_deserialized(&beacon_state_root)?
|
||||
.ok_or_else(|| {
|
||||
Error::DBInconsistent(format!("Missing state {}", beacon_state_root))
|
||||
})?;
|
||||
|
||||
let slot = CheckPoint {
|
||||
beacon_block,
|
||||
beacon_block_root,
|
||||
beacon_state,
|
||||
beacon_state_root,
|
||||
};
|
||||
|
||||
dump.push(slot.clone());
|
||||
last_slot = slot;
|
||||
}
|
||||
|
||||
Ok(dump)
|
||||
}
|
||||
|
||||
/// Accept some block and attempt to add it to block DAG.
|
||||
///
|
||||
/// Will accept blocks from prior slots, however it will reject any block from a future slot.
|
||||
pub fn process_block(&self, block: BeaconBlock) -> Result<BlockProcessingOutcome, Error> {
|
||||
debug!("Processing block with slot {}...", block.slot());
|
||||
|
||||
let block_root = block.canonical_root();
|
||||
|
||||
let present_slot = self.present_slot();
|
||||
|
||||
if block.slot > present_slot {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::FutureSlot,
|
||||
));
|
||||
}
|
||||
|
||||
// Load the blocks parent block from the database, returning invalid if that block is not
|
||||
// found.
|
||||
let parent_block_root = block.parent_root;
|
||||
let parent_block = match self.block_store.get_reader(&parent_block_root)? {
|
||||
Some(parent_root) => parent_root,
|
||||
None => {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::ParentUnknown,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Load the parent blocks state from the database, returning an error if it is not found.
|
||||
// It is an error because if know the parent block we should also know the parent state.
|
||||
let parent_state_root = parent_block.state_root();
|
||||
let parent_state = self
|
||||
.state_store
|
||||
.get_reader(&parent_state_root)?
|
||||
.ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))?
|
||||
.into_beacon_state()
|
||||
.ok_or_else(|| {
|
||||
Error::DBInconsistent(format!("State SSZ invalid {}", parent_state_root))
|
||||
})?;
|
||||
|
||||
// TODO: check the block proposer signature BEFORE doing a state transition. This will
|
||||
// significantly lower exposure surface to DoS attacks.
|
||||
|
||||
// Transition the parent state to the present slot.
|
||||
let mut state = parent_state;
|
||||
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),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the recieved block to its parent state (which has been transitioned into this
|
||||
// slot).
|
||||
if let Err(e) = state.per_block_processing(&block, &self.spec) {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::PerBlockProcessingError(e),
|
||||
));
|
||||
}
|
||||
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
if block.state_root != state_root {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::StateRootMismatch,
|
||||
));
|
||||
}
|
||||
|
||||
// Store the block and state.
|
||||
self.block_store.put(&block_root, &ssz_encode(&block)[..])?;
|
||||
self.state_store.put(&state_root, &ssz_encode(&state)[..])?;
|
||||
|
||||
// Update the block DAG.
|
||||
self.block_graph.add_leaf(&parent_block_root, block_root);
|
||||
|
||||
// If the parent block was the parent_block, automatically update the canonical head.
|
||||
//
|
||||
// TODO: this is a first-in-best-dressed scenario that is not ideal; fork_choice should be
|
||||
// run instead.
|
||||
if self.head().beacon_block_root == parent_block_root {
|
||||
self.update_canonical_head(block.clone(), block_root, state.clone(), state_root);
|
||||
// Update the local state variable.
|
||||
*self.state.write() = state.clone();
|
||||
}
|
||||
|
||||
Ok(BlockProcessingOutcome::ValidBlock(ValidBlock::Processed))
|
||||
}
|
||||
|
||||
/// Produce a new block at the present slot.
|
||||
///
|
||||
/// The produced block will not be inheriently valid, it must be signed by a block producer.
|
||||
/// Block signing is out of the scope of this function and should be done by a separate program.
|
||||
pub fn produce_block(&self, randao_reveal: Signature) -> Option<(BeaconBlock, BeaconState)> {
|
||||
debug!("Producing block at slot {}...", self.state.read().slot);
|
||||
|
||||
let mut state = self.state.read().clone();
|
||||
|
||||
trace!("Finding attestations for new block...");
|
||||
|
||||
let attestations = self
|
||||
.attestation_aggregator
|
||||
.read()
|
||||
.get_attestations_for_state(&state, &self.spec);
|
||||
|
||||
trace!(
|
||||
"Inserting {} attestation(s) into new block.",
|
||||
attestations.len()
|
||||
);
|
||||
|
||||
let parent_root = *state.get_block_root(state.slot.saturating_sub(1_u64), &self.spec)?;
|
||||
|
||||
let mut block = BeaconBlock {
|
||||
slot: state.slot,
|
||||
parent_root,
|
||||
state_root: Hash256::zero(), // Updated after the state is calculated.
|
||||
randao_reveal,
|
||||
eth1_data: Eth1Data {
|
||||
// TODO: replace with real data
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
},
|
||||
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
|
||||
body: BeaconBlockBody {
|
||||
proposer_slashings: vec![],
|
||||
attester_slashings: vec![],
|
||||
attestations,
|
||||
deposits: vec![],
|
||||
exits: vec![],
|
||||
},
|
||||
};
|
||||
|
||||
state
|
||||
.per_block_processing_without_verifying_block_signature(&block, &self.spec)
|
||||
.ok()?;
|
||||
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
block.state_root = state_root;
|
||||
|
||||
trace!("Block produced.");
|
||||
|
||||
Some((block, state))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DBError> for Error {
|
||||
fn from(e: DBError) -> Error {
|
||||
Error::DBError(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommitteesError> for Error {
|
||||
fn from(e: CommitteesError) -> Error {
|
||||
Error::CommitteesError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use std::collections::HashSet;
|
||||
use types::Hash256;
|
||||
|
||||
/// Maintains a view of the block DAG, also known as the "blockchain" (except, it tracks multiple
|
||||
/// chains eminating from a single root instead of just the head of some canonical chain).
|
||||
///
|
||||
/// The BlockGraph does not store the blocks, instead it tracks the block hashes of blocks at the
|
||||
/// tip of the DAG. It is out of the scope of the object to retrieve blocks.
|
||||
///
|
||||
/// Presently, the DAG root (genesis block) is not tracked.
|
||||
///
|
||||
/// The BlogGraph is thread-safe due to internal RwLocks.
|
||||
#[derive(Default)]
|
||||
pub struct BlockGraph {
|
||||
pub leaves: RwLock<HashSet<Hash256>>,
|
||||
}
|
||||
|
||||
impl BlockGraph {
|
||||
/// Create a new block graph without any leaves.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
leaves: RwLock::new(HashSet::new()),
|
||||
}
|
||||
}
|
||||
/// Add a new leaf to the block hash graph. Returns `true` if the leaf was built upon another
|
||||
/// leaf.
|
||||
pub fn add_leaf(&self, parent: &Hash256, leaf: Hash256) -> bool {
|
||||
let mut leaves = self.leaves.write();
|
||||
|
||||
if leaves.contains(parent) {
|
||||
leaves.remove(parent);
|
||||
leaves.insert(leaf);
|
||||
true
|
||||
} else {
|
||||
leaves.insert(leaf);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a read-guarded HashSet of all leaf blocks.
|
||||
pub fn leaves(&self) -> RwLockReadGuard<HashSet<Hash256>> {
|
||||
self.leaves.read()
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
use serde_derive::Serialize;
|
||||
use types::{BeaconBlock, BeaconState, Hash256};
|
||||
|
||||
/// Represents some block and it's associated state. Generally, this will be used for tracking the
|
||||
/// head, justified head and finalized head.
|
||||
#[derive(PartialEq, Clone, Serialize)]
|
||||
pub struct CheckPoint {
|
||||
pub beacon_block: BeaconBlock,
|
||||
pub beacon_block_root: Hash256,
|
||||
pub beacon_state: BeaconState,
|
||||
pub beacon_state_root: Hash256,
|
||||
}
|
||||
|
||||
impl CheckPoint {
|
||||
/// Create a new checkpoint.
|
||||
pub fn new(
|
||||
beacon_block: BeaconBlock,
|
||||
beacon_block_root: Hash256,
|
||||
beacon_state: BeaconState,
|
||||
beacon_state_root: Hash256,
|
||||
) -> Self {
|
||||
Self {
|
||||
beacon_block,
|
||||
beacon_block_root,
|
||||
beacon_state,
|
||||
beacon_state_root,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update all fields of the checkpoint.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
beacon_block: BeaconBlock,
|
||||
beacon_block_root: Hash256,
|
||||
beacon_state: BeaconState,
|
||||
beacon_state_root: Hash256,
|
||||
) {
|
||||
self.beacon_block = beacon_block;
|
||||
self.beacon_block_root = beacon_block_root;
|
||||
self.beacon_state = beacon_state;
|
||||
self.beacon_state_root = beacon_state_root;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
mod attestation_aggregator;
|
||||
mod attestation_targets;
|
||||
mod beacon_chain;
|
||||
mod block_graph;
|
||||
mod checkpoint;
|
||||
mod lmd_ghost;
|
||||
|
||||
pub use self::beacon_chain::{BeaconChain, Error};
|
||||
pub use self::checkpoint::CheckPoint;
|
||||
@@ -1,198 +0,0 @@
|
||||
use crate::BeaconChain;
|
||||
use db::{
|
||||
stores::{BeaconBlockAtSlotError, BeaconBlockStore},
|
||||
ClientDB, DBError,
|
||||
};
|
||||
use slot_clock::{SlotClock, TestingSlotClockError};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
validator_registry::get_active_validator_indices,
|
||||
Hash256, Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
DBError(String),
|
||||
MissingBeaconState(Hash256),
|
||||
InvalidBeaconState(Hash256),
|
||||
MissingBeaconBlock(Hash256),
|
||||
InvalidBeaconBlock(Hash256),
|
||||
}
|
||||
|
||||
impl<T, U> BeaconChain<T, U>
|
||||
where
|
||||
T: ClientDB,
|
||||
U: SlotClock,
|
||||
Error: From<<U as SlotClock>::Error>,
|
||||
{
|
||||
/// Run the fork-choice rule on the current chain, updating the canonical head, if required.
|
||||
pub fn fork_choice(&self) -> Result<(), Error> {
|
||||
let present_head = &self.finalized_head().beacon_block_root;
|
||||
|
||||
let new_head = self.slow_lmd_ghost(&self.finalized_head().beacon_block_root)?;
|
||||
|
||||
if new_head != *present_head {
|
||||
let block = self
|
||||
.block_store
|
||||
.get_deserialized(&new_head)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(new_head))?;
|
||||
let block_root = block.canonical_root();
|
||||
|
||||
let state = self
|
||||
.state_store
|
||||
.get_deserialized(&block.state_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
self.update_canonical_head(block, block_root, state, state_root);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A very inefficient implementation of LMD ghost.
|
||||
pub fn slow_lmd_ghost(&self, start_hash: &Hash256) -> Result<Hash256, Error> {
|
||||
let start = self
|
||||
.block_store
|
||||
.get_reader(&start_hash)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(*start_hash))?;
|
||||
|
||||
let start_state_root = start.state_root();
|
||||
|
||||
let state = self
|
||||
.state_store
|
||||
.get_reader(&start_state_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconState(start_state_root))?
|
||||
.into_beacon_state()
|
||||
.ok_or_else(|| Error::InvalidBeaconState(start_state_root))?;
|
||||
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
&state.validator_registry,
|
||||
start.slot().epoch(self.spec.epoch_length),
|
||||
);
|
||||
|
||||
let mut attestation_targets = Vec::with_capacity(active_validator_indices.len());
|
||||
for i in active_validator_indices {
|
||||
if let Some(target) = self.get_latest_attestation_target(i as u64) {
|
||||
attestation_targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
let mut head_hash = Hash256::zero();
|
||||
let mut head_vote_count = 0;
|
||||
|
||||
loop {
|
||||
let child_hashes_and_slots = get_child_hashes_and_slots(
|
||||
&self.block_store,
|
||||
&head_hash,
|
||||
&self.block_graph.leaves(),
|
||||
)?;
|
||||
|
||||
if child_hashes_and_slots.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for (child_hash, child_slot) in child_hashes_and_slots {
|
||||
let vote_count = get_vote_count(
|
||||
&self.block_store,
|
||||
&attestation_targets[..],
|
||||
&child_hash,
|
||||
child_slot,
|
||||
)?;
|
||||
|
||||
if vote_count > head_vote_count {
|
||||
head_hash = child_hash;
|
||||
head_vote_count = vote_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(head_hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the total number of votes for some given block root.
|
||||
///
|
||||
/// The vote count is incrememented each time an attestation target votes for a block root.
|
||||
fn get_vote_count<T: ClientDB>(
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
attestation_targets: &[Hash256],
|
||||
block_root: &Hash256,
|
||||
slot: Slot,
|
||||
) -> Result<u64, Error> {
|
||||
let mut count = 0;
|
||||
for target in attestation_targets {
|
||||
let (root_at_slot, _) = block_store
|
||||
.block_at_slot(&block_root, slot)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(*block_root))?;
|
||||
if root_at_slot == *target {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Starting from some `leaf_hashes`, recurse back down each branch until the `root_hash`, adding
|
||||
/// each `block_root` and `slot` to a HashSet.
|
||||
fn get_child_hashes_and_slots<T: ClientDB>(
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
root_hash: &Hash256,
|
||||
leaf_hashes: &HashSet<Hash256>,
|
||||
) -> Result<HashSet<(Hash256, Slot)>, Error> {
|
||||
let mut hash_set = HashSet::new();
|
||||
|
||||
for leaf_hash in leaf_hashes {
|
||||
let mut current_hash = *leaf_hash;
|
||||
|
||||
loop {
|
||||
if let Some(block_reader) = block_store.get_reader(¤t_hash)? {
|
||||
let parent_root = block_reader.parent_root();
|
||||
|
||||
let new_hash = hash_set.insert((current_hash, block_reader.slot()));
|
||||
|
||||
// If the hash just added was already in the set, break the loop.
|
||||
//
|
||||
// In such a case, the present branch has merged with a branch that is already in
|
||||
// the set.
|
||||
if !new_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
// The branch is exhausted if the parent of this block is the root_hash.
|
||||
if parent_root == *root_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
current_hash = parent_root;
|
||||
} else {
|
||||
return Err(Error::MissingBeaconBlock(current_hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hash_set)
|
||||
}
|
||||
|
||||
impl From<DBError> for Error {
|
||||
fn from(e: DBError) -> Error {
|
||||
Error::DBError(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconBlockAtSlotError> for Error {
|
||||
fn from(e: BeaconBlockAtSlotError) -> Error {
|
||||
match e {
|
||||
BeaconBlockAtSlotError::UnknownBeaconBlock(h) => Error::MissingBeaconBlock(h),
|
||||
BeaconBlockAtSlotError::InvalidBeaconBlock(h) => Error::InvalidBeaconBlock(h),
|
||||
BeaconBlockAtSlotError::DBError(msg) => Error::DBError(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestingSlotClockError> for Error {
|
||||
fn from(_: TestingSlotClockError) -> Error {
|
||||
unreachable!(); // Testing clock never throws an error.
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
[package]
|
||||
name = "test_harness"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[[bench]]
|
||||
name = "state_transition"
|
||||
harness = false
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.2"
|
||||
|
||||
[dependencies]
|
||||
attester = { path = "../../../eth2/attester" }
|
||||
beacon_chain = { path = "../../beacon_chain" }
|
||||
block_producer = { path = "../../../eth2/block_producer" }
|
||||
bls = { path = "../../../eth2/utils/bls" }
|
||||
boolean-bitfield = { path = "../../../eth2/utils/boolean-bitfield" }
|
||||
db = { path = "../../db" }
|
||||
parking_lot = "0.7"
|
||||
failure = "0.1"
|
||||
failure_derive = "0.1"
|
||||
hashing = { path = "../../../eth2/utils/hashing" }
|
||||
log = "0.4"
|
||||
env_logger = "0.6.0"
|
||||
rayon = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
slot_clock = { path = "../../../eth2/utils/slot_clock" }
|
||||
ssz = { path = "../../../eth2/utils/ssz" }
|
||||
types = { path = "../../../eth2/types" }
|
||||
@@ -1,68 +0,0 @@
|
||||
use criterion::Criterion;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Benchmark};
|
||||
// use env_logger::{Builder, Env};
|
||||
use test_harness::BeaconChainHarness;
|
||||
use types::{ChainSpec, Hash256};
|
||||
|
||||
fn mid_epoch_state_transition(c: &mut Criterion) {
|
||||
// Builder::from_env(Env::default().default_filter_or("debug")).init();
|
||||
|
||||
let validator_count = 1000;
|
||||
let mut rig = BeaconChainHarness::new(ChainSpec::foundation(), validator_count);
|
||||
|
||||
let epoch_depth = (rig.spec.epoch_length * 2) + (rig.spec.epoch_length / 2);
|
||||
|
||||
for _ in 0..epoch_depth {
|
||||
rig.advance_chain_with_block();
|
||||
}
|
||||
|
||||
let state = rig.beacon_chain.state.read().clone();
|
||||
|
||||
assert!((state.slot + 1) % rig.spec.epoch_length != 0);
|
||||
|
||||
c.bench_function("mid-epoch state transition 10k validators", move |b| {
|
||||
let state = state.clone();
|
||||
b.iter(|| {
|
||||
let mut state = state.clone();
|
||||
black_box(state.per_slot_processing(Hash256::zero(), &rig.spec))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn epoch_boundary_state_transition(c: &mut Criterion) {
|
||||
// Builder::from_env(Env::default().default_filter_or("debug")).init();
|
||||
|
||||
let validator_count = 10000;
|
||||
let mut rig = BeaconChainHarness::new(ChainSpec::foundation(), validator_count);
|
||||
|
||||
let epoch_depth = rig.spec.epoch_length * 2;
|
||||
|
||||
for _ in 0..(epoch_depth - 1) {
|
||||
rig.advance_chain_with_block();
|
||||
}
|
||||
|
||||
let state = rig.beacon_chain.state.read().clone();
|
||||
|
||||
assert_eq!((state.slot + 1) % rig.spec.epoch_length, 0);
|
||||
|
||||
c.bench(
|
||||
"routines",
|
||||
Benchmark::new("routine_1", move |b| {
|
||||
let state = state.clone();
|
||||
b.iter(|| {
|
||||
let mut state = state.clone();
|
||||
black_box(black_box(
|
||||
state.per_slot_processing(Hash256::zero(), &rig.spec),
|
||||
))
|
||||
})
|
||||
})
|
||||
.sample_size(5), // sample size is low because function is sloooow.
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
mid_epoch_state_transition,
|
||||
epoch_boundary_state_transition
|
||||
);
|
||||
criterion_main!(benches);
|
||||
@@ -1,243 +0,0 @@
|
||||
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,
|
||||
};
|
||||
use log::debug;
|
||||
use rayon::prelude::*;
|
||||
use slot_clock::TestingSlotClock;
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::iter::FromIterator;
|
||||
use std::sync::Arc;
|
||||
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
|
||||
/// information and submit blocks/attesations for processing.
|
||||
///
|
||||
/// This test harness is useful for testing validator and internal state transition logic. It
|
||||
/// is not useful for testing that multiple beacon nodes can reach consensus.
|
||||
pub struct BeaconChainHarness {
|
||||
pub db: Arc<MemoryDB>,
|
||||
pub beacon_chain: Arc<BeaconChain<MemoryDB, TestingSlotClock>>,
|
||||
pub block_store: Arc<BeaconBlockStore<MemoryDB>>,
|
||||
pub state_store: Arc<BeaconStateStore<MemoryDB>>,
|
||||
pub validators: Vec<ValidatorHarness>,
|
||||
pub spec: Arc<ChainSpec>,
|
||||
}
|
||||
|
||||
impl BeaconChainHarness {
|
||||
/// Create a new harness with:
|
||||
///
|
||||
/// - A keypair, `BlockProducer` and `Attester` for each validator.
|
||||
/// - A new BeaconChain struct where the given validators are in the genesis.
|
||||
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 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 latest_eth1_data = Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
};
|
||||
|
||||
debug!("Generating validator keypairs...");
|
||||
|
||||
let keypairs: Vec<Keypair> = (0..validator_count)
|
||||
.collect::<Vec<usize>>()
|
||||
.par_iter()
|
||||
.map(|_| Keypair::random())
|
||||
.collect();
|
||||
|
||||
debug!("Creating validator deposits...");
|
||||
|
||||
let initial_validator_deposits = keypairs
|
||||
.par_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();
|
||||
|
||||
debug!("Creating the BeaconChain...");
|
||||
|
||||
// Create the Beacon Chain
|
||||
let beacon_chain = Arc::new(
|
||||
BeaconChain::genesis(
|
||||
state_store.clone(),
|
||||
block_store.clone(),
|
||||
slot_clock,
|
||||
genesis_time,
|
||||
latest_eth1_data,
|
||||
initial_validator_deposits,
|
||||
spec.clone(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let spec = Arc::new(spec);
|
||||
|
||||
debug!("Creating validator producer and attester instances...");
|
||||
|
||||
// Spawn the test validator instances.
|
||||
let validators: Vec<ValidatorHarness> = keypairs
|
||||
.iter()
|
||||
.map(|keypair| {
|
||||
ValidatorHarness::new(keypair.clone(), beacon_chain.clone(), spec.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug!("Created {} ValidatorHarnesss", validators.len());
|
||||
|
||||
Self {
|
||||
db,
|
||||
beacon_chain,
|
||||
block_store,
|
||||
state_store,
|
||||
validators,
|
||||
spec,
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the `slot_clock` for the `BeaconChain` forward one slot.
|
||||
///
|
||||
/// 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) -> Slot {
|
||||
let slot = self.beacon_chain.present_slot() + 1;
|
||||
|
||||
debug!("Incrementing BeaconChain slot to {}.", slot);
|
||||
|
||||
self.beacon_chain.slot_clock.set_slot(slot.as_u64());
|
||||
self.beacon_chain.advance_state(slot).unwrap();
|
||||
slot
|
||||
}
|
||||
|
||||
/// Gather the `FreeAttestation`s from the valiators.
|
||||
///
|
||||
/// Note: validators will only produce attestations _once per slot_. So, if you call this twice
|
||||
/// you'll only get attestations on the first run.
|
||||
pub fn gather_free_attesations(&mut self) -> Vec<FreeAttestation> {
|
||||
let present_slot = self.beacon_chain.present_slot();
|
||||
|
||||
let attesting_validators = self
|
||||
.beacon_chain
|
||||
.state
|
||||
.read()
|
||||
.get_crosslink_committees_at_slot(present_slot, false, &self.spec)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.fold(vec![], |mut acc, (committee, _slot)| {
|
||||
acc.append(&mut committee.clone());
|
||||
acc
|
||||
});
|
||||
let attesting_validators: HashSet<usize> =
|
||||
HashSet::from_iter(attesting_validators.iter().cloned());
|
||||
|
||||
let free_attestations: Vec<FreeAttestation> = self
|
||||
.validators
|
||||
.par_iter_mut()
|
||||
.enumerate()
|
||||
.filter_map(|(i, validator)| {
|
||||
if attesting_validators.contains(&i) {
|
||||
// Advance the validator slot.
|
||||
validator.set_slot(present_slot);
|
||||
|
||||
// Prompt the validator to produce an attestation (if required).
|
||||
validator.produce_free_attestation().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug!(
|
||||
"Gathered {} FreeAttestations for slot {}.",
|
||||
free_attestations.len(),
|
||||
present_slot
|
||||
);
|
||||
|
||||
free_attestations
|
||||
}
|
||||
|
||||
/// Get the block from the proposer for the slot.
|
||||
///
|
||||
/// Note: the validator will only produce it _once per slot_. So, if you call this twice you'll
|
||||
/// only get a block once.
|
||||
pub fn produce_block(&mut self) -> BeaconBlock {
|
||||
let present_slot = self.beacon_chain.present_slot();
|
||||
|
||||
let proposer = self.beacon_chain.block_proposer(present_slot).unwrap();
|
||||
|
||||
debug!(
|
||||
"Producing block from validator #{} for slot {}.",
|
||||
proposer, present_slot
|
||||
);
|
||||
|
||||
// Ensure the validators slot clock is accurate.
|
||||
self.validators[proposer].set_slot(present_slot);
|
||||
self.validators[proposer].produce_block().unwrap()
|
||||
}
|
||||
|
||||
/// Advances the chain with a BeaconBlock and attestations from all validators.
|
||||
///
|
||||
/// This is the ideal scenario for the Beacon Chain, 100% honest participation from
|
||||
/// validators.
|
||||
pub fn advance_chain_with_block(&mut self) {
|
||||
self.increment_beacon_chain_slot();
|
||||
|
||||
// Produce a new block.
|
||||
let block = self.produce_block();
|
||||
debug!("Submitting block for processing...");
|
||||
self.beacon_chain.process_block(block).unwrap();
|
||||
debug!("...block processed by BeaconChain.");
|
||||
|
||||
debug!("Producing free attestations...");
|
||||
|
||||
// Produce new attestations.
|
||||
let free_attestations = self.gather_free_attesations();
|
||||
|
||||
debug!("Processing free attestations...");
|
||||
|
||||
free_attestations.par_iter().for_each(|free_attestation| {
|
||||
self.beacon_chain
|
||||
.process_free_attestation(free_attestation.clone())
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
debug!("Free attestations processed.");
|
||||
}
|
||||
|
||||
/// Dump all blocks and states from the canonical beacon chain.
|
||||
pub fn chain_dump(&self) -> Result<Vec<CheckPoint>, BeaconChainError> {
|
||||
self.beacon_chain.chain_dump()
|
||||
}
|
||||
|
||||
/// Write the output of `chain_dump` to a JSON file.
|
||||
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())
|
||||
.expect("Failed writing dump to file.");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
mod beacon_chain_harness;
|
||||
mod validator_harness;
|
||||
|
||||
pub use self::beacon_chain_harness::BeaconChainHarness;
|
||||
pub use self::validator_harness::ValidatorHarness;
|
||||
@@ -1,107 +0,0 @@
|
||||
use attester::{
|
||||
BeaconNode as AttesterBeaconNode, BeaconNodeError as NodeError,
|
||||
PublishOutcome as AttestationPublishOutcome,
|
||||
};
|
||||
use beacon_chain::BeaconChain;
|
||||
use block_producer::{
|
||||
BeaconNode as BeaconBlockNode, BeaconNodeError as BeaconBlockNodeError,
|
||||
PublishOutcome as BlockPublishOutcome,
|
||||
};
|
||||
use db::ClientDB;
|
||||
use parking_lot::RwLock;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::{AttestationData, BeaconBlock, FreeAttestation, Signature, Slot};
|
||||
|
||||
// mod attester;
|
||||
// mod producer;
|
||||
|
||||
/// Connect directly to a borrowed `BeaconChain` instance so an attester/producer can request/submit
|
||||
/// blocks/attestations.
|
||||
///
|
||||
/// `BeaconBlock`s and `FreeAttestation`s are not actually published to the `BeaconChain`, instead
|
||||
/// they are stored inside this struct. This is to allow one to benchmark the submission of the
|
||||
/// block/attestation directly, or modify it before submission.
|
||||
pub struct DirectBeaconNode<T: ClientDB, U: SlotClock> {
|
||||
beacon_chain: Arc<BeaconChain<T, U>>,
|
||||
published_blocks: RwLock<Vec<BeaconBlock>>,
|
||||
published_attestations: RwLock<Vec<FreeAttestation>>,
|
||||
}
|
||||
|
||||
impl<T: ClientDB, U: SlotClock> DirectBeaconNode<T, U> {
|
||||
pub fn new(beacon_chain: Arc<BeaconChain<T, U>>) -> Self {
|
||||
Self {
|
||||
beacon_chain,
|
||||
published_blocks: RwLock::new(vec![]),
|
||||
published_attestations: RwLock::new(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the last published block (if any).
|
||||
pub fn last_published_block(&self) -> Option<BeaconBlock> {
|
||||
Some(self.published_blocks.read().last()?.clone())
|
||||
}
|
||||
|
||||
/// Get the last published attestation (if any).
|
||||
pub fn last_published_free_attestation(&self) -> Option<FreeAttestation> {
|
||||
Some(self.published_attestations.read().last()?.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ClientDB, U: SlotClock> AttesterBeaconNode for DirectBeaconNode<T, U> {
|
||||
fn produce_attestation_data(
|
||||
&self,
|
||||
_slot: Slot,
|
||||
shard: u64,
|
||||
) -> Result<Option<AttestationData>, NodeError> {
|
||||
match self.beacon_chain.produce_attestation_data(shard) {
|
||||
Ok(attestation_data) => Ok(Some(attestation_data)),
|
||||
Err(e) => Err(NodeError::RemoteFailure(format!("{:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
fn publish_attestation_data(
|
||||
&self,
|
||||
free_attestation: FreeAttestation,
|
||||
) -> Result<AttestationPublishOutcome, NodeError> {
|
||||
self.published_attestations.write().push(free_attestation);
|
||||
Ok(AttestationPublishOutcome::ValidAttestation)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ClientDB, U: SlotClock> BeaconBlockNode for DirectBeaconNode<T, U> {
|
||||
/// Requests a new `BeaconBlock from the `BeaconChain`.
|
||||
fn produce_beacon_block(
|
||||
&self,
|
||||
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("Did not produce block.".to_string())
|
||||
})?;
|
||||
|
||||
if block.slot == slot {
|
||||
Ok(Some(block))
|
||||
} else {
|
||||
Err(BeaconBlockNodeError::RemoteFailure(
|
||||
"Unable to produce at non-current slot.".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A block is not _actually_ published to the `BeaconChain`, instead it is stored in the
|
||||
/// `published_block_vec` and a successful `ValidBlock` is returned to the caller.
|
||||
///
|
||||
/// The block may be retrieved and then applied to the `BeaconChain` manually, potentially in a
|
||||
/// benchmarking scenario.
|
||||
fn publish_beacon_block(
|
||||
&self,
|
||||
block: BeaconBlock,
|
||||
) -> Result<BlockPublishOutcome, BeaconBlockNodeError> {
|
||||
self.published_blocks.write().push(block);
|
||||
Ok(BlockPublishOutcome::ValidBlock)
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
use attester::{
|
||||
DutiesReader as AttesterDutiesReader, DutiesReaderError as AttesterDutiesReaderError,
|
||||
};
|
||||
use beacon_chain::BeaconChain;
|
||||
use block_producer::{
|
||||
DutiesReader as ProducerDutiesReader, DutiesReaderError as ProducerDutiesReaderError,
|
||||
};
|
||||
use db::ClientDB;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::{PublicKey, Slot};
|
||||
|
||||
/// Connects directly to a borrowed `BeaconChain` and reads attester/proposer duties directly from
|
||||
/// it.
|
||||
pub struct DirectDuties<T: ClientDB, U: SlotClock> {
|
||||
beacon_chain: Arc<BeaconChain<T, U>>,
|
||||
pubkey: PublicKey,
|
||||
}
|
||||
|
||||
impl<T: ClientDB, U: SlotClock> DirectDuties<T, U> {
|
||||
pub fn new(pubkey: PublicKey, beacon_chain: Arc<BeaconChain<T, U>>) -> Self {
|
||||
Self {
|
||||
beacon_chain,
|
||||
pubkey,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ClientDB, U: SlotClock> ProducerDutiesReader for DirectDuties<T, U> {
|
||||
fn is_block_production_slot(&self, slot: Slot) -> Result<bool, ProducerDutiesReaderError> {
|
||||
let validator_index = self
|
||||
.beacon_chain
|
||||
.validator_index(&self.pubkey)
|
||||
.ok_or_else(|| ProducerDutiesReaderError::UnknownValidator)?;
|
||||
|
||||
match self.beacon_chain.block_proposer(slot) {
|
||||
Ok(proposer) if proposer == validator_index => Ok(true),
|
||||
Ok(_) => Ok(false),
|
||||
Err(_) => Err(ProducerDutiesReaderError::UnknownEpoch),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ClientDB, U: SlotClock> AttesterDutiesReader for DirectDuties<T, U> {
|
||||
fn validator_index(&self) -> Option<u64> {
|
||||
match self.beacon_chain.validator_index(&self.pubkey) {
|
||||
Some(index) => Some(index as u64),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn attestation_shard(&self, slot: Slot) -> Result<Option<u64>, AttesterDutiesReaderError> {
|
||||
if let Some(validator_index) = self.validator_index() {
|
||||
match self
|
||||
.beacon_chain
|
||||
.validator_attestion_slot_and_shard(validator_index as usize)
|
||||
{
|
||||
Ok(Some((attest_slot, attest_shard))) if attest_slot == slot => {
|
||||
Ok(Some(attest_shard))
|
||||
}
|
||||
Ok(Some(_)) => Ok(None),
|
||||
Ok(None) => Err(AttesterDutiesReaderError::UnknownEpoch),
|
||||
Err(_) => unreachable!("Error when getting validator attestation shard."),
|
||||
}
|
||||
} else {
|
||||
Err(AttesterDutiesReaderError::UnknownValidator)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use attester::Signer as AttesterSigner;
|
||||
use block_producer::Signer as BlockProposerSigner;
|
||||
use std::sync::RwLock;
|
||||
use types::{Keypair, Signature};
|
||||
|
||||
/// A test-only struct used to perform signing for a proposer or attester.
|
||||
pub struct LocalSigner {
|
||||
keypair: Keypair,
|
||||
should_sign: RwLock<bool>,
|
||||
}
|
||||
|
||||
impl LocalSigner {
|
||||
/// Produce a new TestSigner with signing enabled by default.
|
||||
pub fn new(keypair: Keypair) -> Self {
|
||||
Self {
|
||||
keypair,
|
||||
should_sign: RwLock::new(true),
|
||||
}
|
||||
}
|
||||
|
||||
/// If set to `false`, the service will refuse to sign all messages. Otherwise, all messages
|
||||
/// will be signed.
|
||||
pub fn enable_signing(&self, enabled: bool) {
|
||||
*self.should_sign.write().unwrap() = enabled;
|
||||
}
|
||||
|
||||
/// Sign some message.
|
||||
fn bls_sign(&self, message: &[u8]) -> Option<Signature> {
|
||||
Some(Signature::new(message, &self.keypair.sk))
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockProposerSigner for LocalSigner {
|
||||
fn sign_block_proposal(&self, message: &[u8]) -> Option<Signature> {
|
||||
self.bls_sign(message)
|
||||
}
|
||||
|
||||
fn sign_randao_reveal(&self, message: &[u8]) -> Option<Signature> {
|
||||
self.bls_sign(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl AttesterSigner for LocalSigner {
|
||||
fn sign_attestation_message(&self, message: &[u8]) -> Option<Signature> {
|
||||
self.bls_sign(message)
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
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 local_signer::LocalSigner;
|
||||
use slot_clock::TestingSlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair, Slot};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockProduceError {
|
||||
DidNotProduce(BlockPollOutcome),
|
||||
PollError(BlockPollError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AttestationProduceError {
|
||||
DidNotProduce(AttestationPollOutcome),
|
||||
PollError(AttestationPollError),
|
||||
}
|
||||
|
||||
/// A `BlockProducer` and `Attester` which sign using a common keypair.
|
||||
///
|
||||
/// 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 ValidatorHarness {
|
||||
pub block_producer: BlockProducer<
|
||||
TestingSlotClock,
|
||||
DirectBeaconNode<MemoryDB, TestingSlotClock>,
|
||||
DirectDuties<MemoryDB, TestingSlotClock>,
|
||||
LocalSigner,
|
||||
>,
|
||||
pub attester: Attester<
|
||||
TestingSlotClock,
|
||||
DirectBeaconNode<MemoryDB, TestingSlotClock>,
|
||||
DirectDuties<MemoryDB, TestingSlotClock>,
|
||||
LocalSigner,
|
||||
>,
|
||||
pub spec: Arc<ChainSpec>,
|
||||
pub epoch_map: Arc<DirectDuties<MemoryDB, TestingSlotClock>>,
|
||||
pub keypair: Keypair,
|
||||
pub beacon_node: Arc<DirectBeaconNode<MemoryDB, TestingSlotClock>>,
|
||||
pub slot_clock: Arc<TestingSlotClock>,
|
||||
pub signer: Arc<LocalSigner>,
|
||||
}
|
||||
|
||||
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..
|
||||
pub fn new(
|
||||
keypair: Keypair,
|
||||
beacon_chain: Arc<BeaconChain<MemoryDB, TestingSlotClock>>,
|
||||
spec: Arc<ChainSpec>,
|
||||
) -> Self {
|
||||
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(),
|
||||
epoch_map.clone(),
|
||||
slot_clock.clone(),
|
||||
beacon_node.clone(),
|
||||
signer.clone(),
|
||||
);
|
||||
|
||||
let attester = Attester::new(
|
||||
epoch_map.clone(),
|
||||
slot_clock.clone(),
|
||||
beacon_node.clone(),
|
||||
signer.clone(),
|
||||
);
|
||||
|
||||
Self {
|
||||
block_producer,
|
||||
attester,
|
||||
spec,
|
||||
epoch_map,
|
||||
keypair,
|
||||
beacon_node,
|
||||
slot_clock,
|
||||
signer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the `poll` function on the `BlockProducer` and produce a block.
|
||||
///
|
||||
/// An error is returned if the producer refuses to produce.
|
||||
pub fn produce_block(&mut self) -> Result<BeaconBlock, BlockProduceError> {
|
||||
// Using `DirectBeaconNode`, the validator will always return sucessufully if it tries to
|
||||
// publish a block.
|
||||
match self.block_producer.poll() {
|
||||
Ok(BlockPollOutcome::BlockProduced(_)) => {}
|
||||
Ok(outcome) => return Err(BlockProduceError::DidNotProduce(outcome)),
|
||||
Err(error) => return Err(BlockProduceError::PollError(error)),
|
||||
};
|
||||
Ok(self
|
||||
.beacon_node
|
||||
.last_published_block()
|
||||
.expect("Unable to obtain produced block."))
|
||||
}
|
||||
|
||||
/// Run the `poll` function on the `Attester` and produce a `FreeAttestation`.
|
||||
///
|
||||
/// An error is returned if the attester refuses to attest.
|
||||
pub fn produce_free_attestation(&mut self) -> Result<FreeAttestation, AttestationProduceError> {
|
||||
match self.attester.poll() {
|
||||
Ok(AttestationPollOutcome::AttestationProduced(_)) => {}
|
||||
Ok(outcome) => return Err(AttestationProduceError::DidNotProduce(outcome)),
|
||||
Err(error) => return Err(AttestationProduceError::PollError(error)),
|
||||
};
|
||||
Ok(self
|
||||
.beacon_node
|
||||
.last_published_free_attestation()
|
||||
.expect("Unable to obtain produced attestation."))
|
||||
}
|
||||
|
||||
/// 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: Slot) {
|
||||
self.slot_clock.set_slot(slot.as_u64())
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use env_logger::{Builder, Env};
|
||||
use log::debug;
|
||||
use test_harness::BeaconChainHarness;
|
||||
use types::{ChainSpec, Slot};
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn it_can_build_on_genesis_block() {
|
||||
let mut spec = ChainSpec::foundation();
|
||||
spec.genesis_slot = Slot::new(spec.epoch_length * 8);
|
||||
|
||||
/*
|
||||
spec.shard_count = spec.shard_count / 8;
|
||||
spec.target_committee_size = spec.target_committee_size / 8;
|
||||
*/
|
||||
let validator_count = 1000;
|
||||
|
||||
let mut harness = BeaconChainHarness::new(spec, validator_count as usize);
|
||||
|
||||
harness.advance_chain_with_block();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn it_can_produce_past_first_epoch_boundary() {
|
||||
Builder::from_env(Env::default().default_filter_or("debug")).init();
|
||||
|
||||
let validator_count = 100;
|
||||
|
||||
debug!("Starting harness build...");
|
||||
|
||||
let mut harness = BeaconChainHarness::new(ChainSpec::foundation(), validator_count);
|
||||
|
||||
debug!("Harness built, tests starting..");
|
||||
|
||||
let blocks = harness.spec.epoch_length * 3 + 1;
|
||||
|
||||
for i in 0..blocks {
|
||||
harness.advance_chain_with_block();
|
||||
debug!("Produced block {}/{}.", i, blocks);
|
||||
}
|
||||
let dump = harness.chain_dump().expect("Chain dump failed.");
|
||||
|
||||
assert_eq!(dump.len() as u64, blocks + 1); // + 1 for genesis block.
|
||||
|
||||
harness.dump_to_file("/tmp/chaindump.json".to_string(), &dump);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "db"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
blake2-rfc = "0.2.18"
|
||||
bls = { path = "../../eth2/utils/bls" }
|
||||
bytes = "0.4.10"
|
||||
rocksdb = "0.10.1"
|
||||
ssz = { path = "../../eth2/utils/ssz" }
|
||||
types = { path = "../../eth2/types" }
|
||||
@@ -1,197 +0,0 @@
|
||||
extern crate rocksdb;
|
||||
|
||||
use super::rocksdb::Error as RocksError;
|
||||
use super::rocksdb::{Options, DB};
|
||||
use super::{ClientDB, DBError, DBValue};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// A on-disk database which implements the ClientDB trait.
|
||||
///
|
||||
/// This implementation uses RocksDB with default options.
|
||||
pub struct DiskDB {
|
||||
db: DB,
|
||||
}
|
||||
|
||||
impl DiskDB {
|
||||
/// Open the RocksDB database, optionally supplying columns if required.
|
||||
///
|
||||
/// The RocksDB database will be contained in a directory titled
|
||||
/// "database" in the supplied path.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the database is unable to be created.
|
||||
pub fn open(path: &Path, columns: Option<&[&str]>) -> Self {
|
||||
/*
|
||||
* Initialise the options
|
||||
*/
|
||||
let mut options = Options::default();
|
||||
options.create_if_missing(true);
|
||||
|
||||
// TODO: ensure that columns are created (and remove
|
||||
// the dead_code allow)
|
||||
|
||||
/*
|
||||
* Initialise the path
|
||||
*/
|
||||
fs::create_dir_all(&path).unwrap_or_else(|_| panic!("Unable to create {:?}", &path));
|
||||
let db_path = path.join("database");
|
||||
|
||||
/*
|
||||
* Open the database
|
||||
*/
|
||||
let db = match columns {
|
||||
None => DB::open(&options, db_path),
|
||||
Some(columns) => DB::open_cf(&options, db_path, columns),
|
||||
}
|
||||
.expect("Unable to open local database");;
|
||||
|
||||
Self { db }
|
||||
}
|
||||
|
||||
/// Create a RocksDB column family. Corresponds to the
|
||||
/// `create_cf()` function on the RocksDB API.
|
||||
#[allow(dead_code)]
|
||||
fn create_col(&mut self, col: &str) -> Result<(), DBError> {
|
||||
match self.db.create_cf(col, &Options::default()) {
|
||||
Err(e) => Err(e.into()),
|
||||
Ok(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RocksError> for DBError {
|
||||
fn from(e: RocksError) -> Self {
|
||||
Self {
|
||||
message: e.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientDB for DiskDB {
|
||||
/// Get the value for some key on some column.
|
||||
///
|
||||
/// Corresponds to the `get_cf()` method on the RocksDB API.
|
||||
/// Will attempt to get the `ColumnFamily` and return an Err
|
||||
/// if it fails.
|
||||
fn get(&self, col: &str, key: &[u8]) -> Result<Option<DBValue>, DBError> {
|
||||
match self.db.cf_handle(col) {
|
||||
None => Err(DBError {
|
||||
message: "Unknown column".to_string(),
|
||||
}),
|
||||
Some(handle) => match self.db.get_cf(handle, key)? {
|
||||
None => Ok(None),
|
||||
Some(db_vec) => Ok(Some(DBValue::from(&*db_vec))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Set some value for some key on some column.
|
||||
///
|
||||
/// Corresponds to the `cf_handle()` method on the RocksDB API.
|
||||
/// Will attempt to get the `ColumnFamily` and return an Err
|
||||
/// if it fails.
|
||||
fn put(&self, col: &str, key: &[u8], val: &[u8]) -> Result<(), DBError> {
|
||||
match self.db.cf_handle(col) {
|
||||
None => Err(DBError {
|
||||
message: "Unknown column".to_string(),
|
||||
}),
|
||||
Some(handle) => self.db.put_cf(handle, key, val).map_err(|e| e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if some key exists in some column.
|
||||
fn exists(&self, col: &str, key: &[u8]) -> Result<bool, DBError> {
|
||||
/*
|
||||
* I'm not sure if this is the correct way to read if some
|
||||
* block exists. Naively I would expect this to unncessarily
|
||||
* copy some data, but I could be wrong.
|
||||
*/
|
||||
match self.db.cf_handle(col) {
|
||||
None => Err(DBError {
|
||||
message: "Unknown column".to_string(),
|
||||
}),
|
||||
Some(handle) => Ok(self.db.get_cf(handle, key)?.is_some()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete the value for some key on some column.
|
||||
///
|
||||
/// Corresponds to the `delete_cf()` method on the RocksDB API.
|
||||
/// Will attempt to get the `ColumnFamily` and return an Err
|
||||
/// if it fails.
|
||||
fn delete(&self, col: &str, key: &[u8]) -> Result<(), DBError> {
|
||||
match self.db.cf_handle(col) {
|
||||
None => Err(DBError {
|
||||
message: "Unknown column".to_string(),
|
||||
}),
|
||||
Some(handle) => {
|
||||
self.db.delete_cf(handle, key)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::ClientDB;
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use std::{env, fs, thread};
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_rocksdb_can_use_db() {
|
||||
let pwd = env::current_dir().unwrap();
|
||||
let path = pwd.join("testdb_please_remove");
|
||||
let _ = fs::remove_dir_all(&path);
|
||||
fs::create_dir_all(&path).unwrap();
|
||||
|
||||
let col_name: &str = "TestColumn";
|
||||
let column_families = vec![col_name];
|
||||
|
||||
let mut db = DiskDB::open(&path, None);
|
||||
|
||||
for cf in column_families {
|
||||
db.create_col(&cf).unwrap();
|
||||
}
|
||||
|
||||
let db = Arc::new(db);
|
||||
|
||||
let thread_count = 10;
|
||||
let write_count = 10;
|
||||
|
||||
// We're execting the product of these numbers to fit in one byte.
|
||||
assert!(thread_count * write_count <= 255);
|
||||
|
||||
let mut handles = vec![];
|
||||
for t in 0..thread_count {
|
||||
let wc = write_count;
|
||||
let db = db.clone();
|
||||
let col = col_name.clone();
|
||||
let handle = thread::spawn(move || {
|
||||
for w in 0..wc {
|
||||
let key = (t * w) as u8;
|
||||
let val = 42;
|
||||
db.put(&col, &vec![key], &vec![val]).unwrap();
|
||||
}
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
for t in 0..thread_count {
|
||||
for w in 0..write_count {
|
||||
let key = (t * w) as u8;
|
||||
let val = db.get(&col_name, &vec![key]).unwrap().unwrap();
|
||||
assert_eq!(vec![42], val);
|
||||
}
|
||||
}
|
||||
fs::remove_dir_all(&path).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
extern crate blake2_rfc as blake2;
|
||||
extern crate bls;
|
||||
extern crate rocksdb;
|
||||
|
||||
mod disk_db;
|
||||
mod memory_db;
|
||||
pub mod stores;
|
||||
mod traits;
|
||||
|
||||
use self::stores::COLUMNS;
|
||||
|
||||
pub use self::disk_db::DiskDB;
|
||||
pub use self::memory_db::MemoryDB;
|
||||
pub use self::traits::{ClientDB, DBError, DBValue};
|
||||
@@ -1,236 +0,0 @@
|
||||
use super::blake2::blake2b::blake2b;
|
||||
use super::COLUMNS;
|
||||
use super::{ClientDB, DBError, DBValue};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::RwLock;
|
||||
|
||||
type DBHashMap = HashMap<Vec<u8>, Vec<u8>>;
|
||||
type ColumnHashSet = HashSet<String>;
|
||||
|
||||
/// An in-memory database implementing the ClientDB trait.
|
||||
///
|
||||
/// It is not particularily optimized, it exists for ease and speed of testing. It's not expected
|
||||
/// this DB would be used outside of tests.
|
||||
pub struct MemoryDB {
|
||||
db: RwLock<DBHashMap>,
|
||||
known_columns: RwLock<ColumnHashSet>,
|
||||
}
|
||||
|
||||
impl MemoryDB {
|
||||
/// Open the in-memory database.
|
||||
///
|
||||
/// All columns must be supplied initially, you will get an error if you try to access a column
|
||||
/// that was not declared here. This condition is enforced artificially to simulate RocksDB.
|
||||
pub fn open() -> Self {
|
||||
let db: DBHashMap = HashMap::new();
|
||||
let mut known_columns: ColumnHashSet = HashSet::new();
|
||||
for col in &COLUMNS {
|
||||
known_columns.insert(col.to_string());
|
||||
}
|
||||
Self {
|
||||
db: RwLock::new(db),
|
||||
known_columns: RwLock::new(known_columns),
|
||||
}
|
||||
}
|
||||
|
||||
/// Hashes a key and a column name in order to get a unique key for the supplied column.
|
||||
fn get_key_for_col(col: &str, key: &[u8]) -> Vec<u8> {
|
||||
blake2b(32, col.as_bytes(), key).as_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientDB for MemoryDB {
|
||||
/// Get the value of some key from the database. Returns `None` if the key does not exist.
|
||||
fn get(&self, col: &str, key: &[u8]) -> Result<Option<DBValue>, DBError> {
|
||||
// Panic if the DB locks are poisoned.
|
||||
let db = self.db.read().unwrap();
|
||||
let known_columns = self.known_columns.read().unwrap();
|
||||
|
||||
if known_columns.contains(&col.to_string()) {
|
||||
let column_key = MemoryDB::get_key_for_col(col, key);
|
||||
Ok(db.get(&column_key).and_then(|val| Some(val.clone())))
|
||||
} else {
|
||||
Err(DBError {
|
||||
message: "Unknown column".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Puts a key in the database.
|
||||
fn put(&self, col: &str, key: &[u8], val: &[u8]) -> Result<(), DBError> {
|
||||
// Panic if the DB locks are poisoned.
|
||||
let mut db = self.db.write().unwrap();
|
||||
let known_columns = self.known_columns.read().unwrap();
|
||||
|
||||
if known_columns.contains(&col.to_string()) {
|
||||
let column_key = MemoryDB::get_key_for_col(col, key);
|
||||
db.insert(column_key, val.to_vec());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DBError {
|
||||
message: "Unknown column".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if some key exists in some column.
|
||||
fn exists(&self, col: &str, key: &[u8]) -> Result<bool, DBError> {
|
||||
// Panic if the DB locks are poisoned.
|
||||
let db = self.db.read().unwrap();
|
||||
let known_columns = self.known_columns.read().unwrap();
|
||||
|
||||
if known_columns.contains(&col.to_string()) {
|
||||
let column_key = MemoryDB::get_key_for_col(col, key);
|
||||
Ok(db.contains_key(&column_key))
|
||||
} else {
|
||||
Err(DBError {
|
||||
message: "Unknown column".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete some key from the database.
|
||||
fn delete(&self, col: &str, key: &[u8]) -> Result<(), DBError> {
|
||||
// Panic if the DB locks are poisoned.
|
||||
let mut db = self.db.write().unwrap();
|
||||
let known_columns = self.known_columns.read().unwrap();
|
||||
|
||||
if known_columns.contains(&col.to_string()) {
|
||||
let column_key = MemoryDB::get_key_for_col(col, key);
|
||||
db.remove(&column_key);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(DBError {
|
||||
message: "Unknown column".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::stores::{BLOCKS_DB_COLUMN, VALIDATOR_DB_COLUMN};
|
||||
use super::super::ClientDB;
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn test_memorydb_can_delete() {
|
||||
let col_a: &str = BLOCKS_DB_COLUMN;
|
||||
|
||||
let db = MemoryDB::open();
|
||||
|
||||
db.put(col_a, "dogs".as_bytes(), "lol".as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
db.get(col_a, "dogs".as_bytes()).unwrap().unwrap(),
|
||||
"lol".as_bytes()
|
||||
);
|
||||
|
||||
db.delete(col_a, "dogs".as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(db.get(col_a, "dogs".as_bytes()).unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memorydb_column_access() {
|
||||
let col_a: &str = BLOCKS_DB_COLUMN;
|
||||
let col_b: &str = VALIDATOR_DB_COLUMN;
|
||||
|
||||
let db = MemoryDB::open();
|
||||
|
||||
/*
|
||||
* Testing that if we write to the same key in different columns that
|
||||
* there is not an overlap.
|
||||
*/
|
||||
db.put(col_a, "same".as_bytes(), "cat".as_bytes()).unwrap();
|
||||
db.put(col_b, "same".as_bytes(), "dog".as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
db.get(col_a, "same".as_bytes()).unwrap().unwrap(),
|
||||
"cat".as_bytes()
|
||||
);
|
||||
assert_eq!(
|
||||
db.get(col_b, "same".as_bytes()).unwrap().unwrap(),
|
||||
"dog".as_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memorydb_unknown_column_access() {
|
||||
let col_a: &str = BLOCKS_DB_COLUMN;
|
||||
let col_x: &str = "ColumnX";
|
||||
|
||||
let db = MemoryDB::open();
|
||||
|
||||
/*
|
||||
* Test that we get errors when using undeclared columns
|
||||
*/
|
||||
assert!(db.put(col_a, "cats".as_bytes(), "lol".as_bytes()).is_ok());
|
||||
assert!(db.put(col_x, "cats".as_bytes(), "lol".as_bytes()).is_err());
|
||||
|
||||
assert!(db.get(col_a, "cats".as_bytes()).is_ok());
|
||||
assert!(db.get(col_x, "cats".as_bytes()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memorydb_exists() {
|
||||
let col_a: &str = BLOCKS_DB_COLUMN;
|
||||
let col_b: &str = VALIDATOR_DB_COLUMN;
|
||||
|
||||
let db = MemoryDB::open();
|
||||
|
||||
/*
|
||||
* Testing that if we write to the same key in different columns that
|
||||
* there is not an overlap.
|
||||
*/
|
||||
db.put(col_a, "cats".as_bytes(), "lol".as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(true, db.exists(col_a, "cats".as_bytes()).unwrap());
|
||||
assert_eq!(false, db.exists(col_b, "cats".as_bytes()).unwrap());
|
||||
|
||||
assert_eq!(false, db.exists(col_a, "dogs".as_bytes()).unwrap());
|
||||
assert_eq!(false, db.exists(col_b, "dogs".as_bytes()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memorydb_threading() {
|
||||
let col_name: &str = BLOCKS_DB_COLUMN;
|
||||
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
|
||||
let thread_count = 10;
|
||||
let write_count = 10;
|
||||
|
||||
// We're execting the product of these numbers to fit in one byte.
|
||||
assert!(thread_count * write_count <= 255);
|
||||
|
||||
let mut handles = vec![];
|
||||
for t in 0..thread_count {
|
||||
let wc = write_count;
|
||||
let db = db.clone();
|
||||
let col = col_name.clone();
|
||||
let handle = thread::spawn(move || {
|
||||
for w in 0..wc {
|
||||
let key = (t * w) as u8;
|
||||
let val = 42;
|
||||
db.put(&col, &vec![key], &vec![val]).unwrap();
|
||||
}
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
for t in 0..thread_count {
|
||||
for w in 0..write_count {
|
||||
let key = (t * w) as u8;
|
||||
let val = db.get(&col_name, &vec![key]).unwrap().unwrap();
|
||||
assert_eq!(vec![42], val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
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, Slot};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum BeaconBlockAtSlotError {
|
||||
UnknownBeaconBlock(Hash256),
|
||||
InvalidBeaconBlock(Hash256),
|
||||
DBError(String),
|
||||
}
|
||||
|
||||
pub struct BeaconBlockStore<T>
|
||||
where
|
||||
T: ClientDB,
|
||||
{
|
||||
db: Arc<T>,
|
||||
}
|
||||
|
||||
// Implements `put`, `get`, `exists` and `delete` for the store.
|
||||
impl_crud_for_store!(BeaconBlockStore, DB_COLUMN);
|
||||
|
||||
impl<T: ClientDB> BeaconBlockStore<T> {
|
||||
pub fn new(db: Arc<T>) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub fn get_deserialized(&self, hash: &Hash256) -> Result<Option<BeaconBlock>, DBError> {
|
||||
match self.get(&hash)? {
|
||||
None => Ok(None),
|
||||
Some(ssz) => {
|
||||
let (block, _) = BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| DBError {
|
||||
message: "Bad BeaconBlock SSZ.".to_string(),
|
||||
})?;
|
||||
Ok(Some(block))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retuns an object implementing `BeaconBlockReader`, or `None` (if hash not known).
|
||||
///
|
||||
/// Note: Presently, this function fully deserializes a `BeaconBlock` and returns that. In the
|
||||
/// future, it would be ideal to return an object capable of reading directly from serialized
|
||||
/// SSZ bytes.
|
||||
pub fn get_reader(&self, hash: &Hash256) -> Result<Option<impl BeaconBlockReader>, DBError> {
|
||||
match self.get(&hash)? {
|
||||
None => Ok(None),
|
||||
Some(ssz) => {
|
||||
let (block, _) = BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| DBError {
|
||||
message: "Bad BeaconBlock SSZ.".to_string(),
|
||||
})?;
|
||||
Ok(Some(block))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the block at a slot given a "head_hash" and a slot.
|
||||
///
|
||||
/// A "head_hash" must be a block hash with a slot number greater than or equal to the desired
|
||||
/// slot.
|
||||
///
|
||||
/// This function will read each block down the chain until it finds a block with the given
|
||||
/// slot number. If the slot is skipped, the function will return None.
|
||||
///
|
||||
/// If a block is found, a tuple of (block_hash, serialized_block) is returned.
|
||||
///
|
||||
/// Note: this function uses a loop instead of recursion as the compiler is over-strict when it
|
||||
/// comes to recursion and the `impl Trait` pattern. See:
|
||||
/// https://stackoverflow.com/questions/54032940/using-impl-trait-in-a-recursive-function
|
||||
pub fn block_at_slot(
|
||||
&self,
|
||||
head_hash: &Hash256,
|
||||
slot: Slot,
|
||||
) -> Result<Option<(Hash256, impl BeaconBlockReader)>, BeaconBlockAtSlotError> {
|
||||
let mut current_hash = *head_hash;
|
||||
|
||||
loop {
|
||||
if let Some(block_reader) = self.get_reader(¤t_hash)? {
|
||||
if block_reader.slot() == slot {
|
||||
break Ok(Some((current_hash, block_reader)));
|
||||
} else if block_reader.slot() < slot {
|
||||
break Ok(None);
|
||||
} else {
|
||||
current_hash = block_reader.parent_root();
|
||||
}
|
||||
} else {
|
||||
break Err(BeaconBlockAtSlotError::UnknownBeaconBlock(current_hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DBError> for BeaconBlockAtSlotError {
|
||||
fn from(e: DBError) -> Self {
|
||||
BeaconBlockAtSlotError::DBError(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::super::MemoryDB;
|
||||
use super::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use ssz::ssz_encode;
|
||||
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use types::BeaconBlock;
|
||||
use types::Hash256;
|
||||
|
||||
test_crud_for_store!(BeaconBlockStore, DB_COLUMN);
|
||||
|
||||
#[test]
|
||||
fn head_hash_slot_too_low() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let bs = Arc::new(BeaconBlockStore::new(db.clone()));
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let mut block = BeaconBlock::random_for_test(&mut rng);
|
||||
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, Slot::from(11_u64)).unwrap();
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_block_at_slot() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = BeaconBlockStore::new(db.clone());
|
||||
|
||||
let ssz = "definitly not a valid block".as_bytes();
|
||||
let hash = &Hash256::from("some hash".as_bytes());
|
||||
|
||||
db.put(DB_COLUMN, hash, ssz).unwrap();
|
||||
assert_eq!(
|
||||
store.block_at_slot(hash, Slot::from(42_u64)),
|
||||
Err(BeaconBlockAtSlotError::DBError(
|
||||
"Bad BeaconBlock SSZ.".into()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unknown_block_at_slot() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = BeaconBlockStore::new(db.clone());
|
||||
|
||||
let ssz = "some bytes".as_bytes();
|
||||
let hash = &Hash256::from("some hash".as_bytes());
|
||||
let other_hash = &Hash256::from("another hash".as_bytes());
|
||||
|
||||
db.put(DB_COLUMN, hash, ssz).unwrap();
|
||||
assert_eq!(
|
||||
store.block_at_slot(other_hash, Slot::from(42_u64)),
|
||||
Err(BeaconBlockAtSlotError::UnknownBeaconBlock(*other_hash))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_store_on_memory_db() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let bs = Arc::new(BeaconBlockStore::new(db.clone()));
|
||||
|
||||
let thread_count = 10;
|
||||
let write_count = 10;
|
||||
|
||||
// We're expecting the product of these numbers to fit in one byte.
|
||||
assert!(thread_count * write_count <= 255);
|
||||
|
||||
let mut handles = vec![];
|
||||
for t in 0..thread_count {
|
||||
let wc = write_count;
|
||||
let bs = bs.clone();
|
||||
let handle = thread::spawn(move || {
|
||||
for w in 0..wc {
|
||||
let key = (t * w) as u8;
|
||||
let val = 42;
|
||||
bs.put(&[key][..].into(), &vec![val]).unwrap();
|
||||
}
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
for t in 0..thread_count {
|
||||
for w in 0..write_count {
|
||||
let key = (t * w) as u8;
|
||||
assert!(bs.exists(&[key][..].into()).unwrap());
|
||||
let val = bs.get(&[key][..].into()).unwrap().unwrap();
|
||||
assert_eq!(vec![42], val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_at_slot() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let bs = Arc::new(BeaconBlockStore::new(db.clone()));
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
// Specify test block parameters.
|
||||
let hashes = [
|
||||
Hash256::from(&[0; 32][..]),
|
||||
Hash256::from(&[1; 32][..]),
|
||||
Hash256::from(&[2; 32][..]),
|
||||
Hash256::from(&[3; 32][..]),
|
||||
Hash256::from(&[4; 32][..]),
|
||||
];
|
||||
let parent_hashes = [
|
||||
Hash256::from(&[255; 32][..]), // Genesis block.
|
||||
Hash256::from(&[0; 32][..]),
|
||||
Hash256::from(&[1; 32][..]),
|
||||
Hash256::from(&[2; 32][..]),
|
||||
Hash256::from(&[3; 32][..]),
|
||||
];
|
||||
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;
|
||||
let mut blocks: Vec<BeaconBlock> = Vec::with_capacity(5);
|
||||
for i in 0..block_count {
|
||||
let mut block = BeaconBlock::random_for_test(&mut rng);
|
||||
|
||||
block.parent_root = parent_hashes[i];
|
||||
block.slot = slots[i];
|
||||
|
||||
let ssz = ssz_encode(&block);
|
||||
db.put(DB_COLUMN, &hashes[i], &ssz).unwrap();
|
||||
|
||||
blocks.push(block);
|
||||
}
|
||||
|
||||
// Test that certain slots can be reached from certain hashes.
|
||||
let test_cases = vec![(4, 4), (4, 3), (4, 2), (4, 1), (4, 0)];
|
||||
for (hashes_index, slot_index) in test_cases {
|
||||
let (matched_block_hash, reader) = bs
|
||||
.block_at_slot(&hashes[hashes_index], slots[slot_index])
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(matched_block_hash, hashes[slot_index]);
|
||||
assert_eq!(reader.slot(), slots[slot_index]);
|
||||
}
|
||||
|
||||
let ssz = bs.block_at_slot(&hashes[4], Slot::new(2)).unwrap();
|
||||
assert_eq!(ssz, None);
|
||||
|
||||
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, Slot::new(2));
|
||||
assert_eq!(
|
||||
ssz,
|
||||
Err(BeaconBlockAtSlotError::UnknownBeaconBlock(*bad_hash))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
use super::STATES_DB_COLUMN as DB_COLUMN;
|
||||
use super::{ClientDB, DBError};
|
||||
use ssz::Decodable;
|
||||
use std::sync::Arc;
|
||||
use types::{readers::BeaconStateReader, BeaconState, Hash256};
|
||||
|
||||
pub struct BeaconStateStore<T>
|
||||
where
|
||||
T: ClientDB,
|
||||
{
|
||||
db: Arc<T>,
|
||||
}
|
||||
|
||||
// Implements `put`, `get`, `exists` and `delete` for the store.
|
||||
impl_crud_for_store!(BeaconStateStore, DB_COLUMN);
|
||||
|
||||
impl<T: ClientDB> BeaconStateStore<T> {
|
||||
pub fn new(db: Arc<T>) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub fn get_deserialized(&self, hash: &Hash256) -> Result<Option<BeaconState>, DBError> {
|
||||
match self.get(&hash)? {
|
||||
None => Ok(None),
|
||||
Some(ssz) => {
|
||||
let (state, _) = BeaconState::ssz_decode(&ssz, 0).map_err(|_| DBError {
|
||||
message: "Bad State SSZ.".to_string(),
|
||||
})?;
|
||||
Ok(Some(state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retuns an object implementing `BeaconStateReader`, or `None` (if hash not known).
|
||||
///
|
||||
/// Note: Presently, this function fully deserializes a `BeaconState` and returns that. In the
|
||||
/// future, it would be ideal to return an object capable of reading directly from serialized
|
||||
/// SSZ bytes.
|
||||
pub fn get_reader(&self, hash: &Hash256) -> Result<Option<impl BeaconStateReader>, DBError> {
|
||||
match self.get(&hash)? {
|
||||
None => Ok(None),
|
||||
Some(ssz) => {
|
||||
let (state, _) = BeaconState::ssz_decode(&ssz, 0).map_err(|_| DBError {
|
||||
message: "Bad State SSZ.".to_string(),
|
||||
})?;
|
||||
Ok(Some(state))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::super::MemoryDB;
|
||||
use super::*;
|
||||
|
||||
use ssz::ssz_encode;
|
||||
use std::sync::Arc;
|
||||
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use types::Hash256;
|
||||
|
||||
test_crud_for_store!(BeaconStateStore, DB_COLUMN);
|
||||
|
||||
#[test]
|
||||
fn test_reader() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = BeaconStateStore::new(db.clone());
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let state = BeaconState::random_for_test(&mut rng);
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
store.put(&state_root, &ssz_encode(&state)).unwrap();
|
||||
|
||||
let reader = store.get_reader(&state_root).unwrap().unwrap();
|
||||
let decoded = reader.into_beacon_state().unwrap();
|
||||
|
||||
assert_eq!(state, decoded);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
macro_rules! impl_crud_for_store {
|
||||
($store: ident, $db_column: expr) => {
|
||||
impl<T: ClientDB> $store<T> {
|
||||
pub fn put(&self, hash: &Hash256, ssz: &[u8]) -> Result<(), DBError> {
|
||||
self.db.put($db_column, hash, ssz)
|
||||
}
|
||||
|
||||
pub fn get(&self, hash: &Hash256) -> Result<Option<Vec<u8>>, DBError> {
|
||||
self.db.get($db_column, hash)
|
||||
}
|
||||
|
||||
pub fn exists(&self, hash: &Hash256) -> Result<bool, DBError> {
|
||||
self.db.exists($db_column, hash)
|
||||
}
|
||||
|
||||
pub fn delete(&self, hash: &Hash256) -> Result<(), DBError> {
|
||||
self.db.delete($db_column, hash)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! test_crud_for_store {
|
||||
($store: ident, $db_column: expr) => {
|
||||
#[test]
|
||||
fn test_put() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = $store::new(db.clone());
|
||||
|
||||
let ssz = "some bytes".as_bytes();
|
||||
let hash = &Hash256::from("some hash".as_bytes());
|
||||
|
||||
store.put(hash, ssz).unwrap();
|
||||
assert_eq!(db.get(DB_COLUMN, hash).unwrap().unwrap(), ssz);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = $store::new(db.clone());
|
||||
|
||||
let ssz = "some bytes".as_bytes();
|
||||
let hash = &Hash256::from("some hash".as_bytes());
|
||||
|
||||
db.put(DB_COLUMN, hash, ssz).unwrap();
|
||||
assert_eq!(store.get(hash).unwrap().unwrap(), ssz);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_unknown() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = $store::new(db.clone());
|
||||
|
||||
let ssz = "some bytes".as_bytes();
|
||||
let hash = &Hash256::from("some hash".as_bytes());
|
||||
let other_hash = &Hash256::from("another hash".as_bytes());
|
||||
|
||||
db.put(DB_COLUMN, other_hash, ssz).unwrap();
|
||||
assert_eq!(store.get(hash).unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exists() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = $store::new(db.clone());
|
||||
|
||||
let ssz = "some bytes".as_bytes();
|
||||
let hash = &Hash256::from("some hash".as_bytes());
|
||||
|
||||
db.put(DB_COLUMN, hash, ssz).unwrap();
|
||||
assert!(store.exists(hash).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_does_not_exist() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = $store::new(db.clone());
|
||||
|
||||
let ssz = "some bytes".as_bytes();
|
||||
let hash = &Hash256::from("some hash".as_bytes());
|
||||
let other_hash = &Hash256::from("another hash".as_bytes());
|
||||
|
||||
db.put(DB_COLUMN, hash, ssz).unwrap();
|
||||
assert!(!store.exists(other_hash).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = $store::new(db.clone());
|
||||
|
||||
let ssz = "some bytes".as_bytes();
|
||||
let hash = &Hash256::from("some hash".as_bytes());
|
||||
|
||||
db.put(DB_COLUMN, hash, ssz).unwrap();
|
||||
assert!(db.exists(DB_COLUMN, hash).unwrap());
|
||||
|
||||
store.delete(hash).unwrap();
|
||||
assert!(!db.exists(DB_COLUMN, hash).unwrap());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
use super::{ClientDB, DBError};
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod beacon_block_store;
|
||||
mod beacon_state_store;
|
||||
mod pow_chain_store;
|
||||
mod validator_store;
|
||||
|
||||
pub use self::beacon_block_store::{BeaconBlockAtSlotError, BeaconBlockStore};
|
||||
pub use self::beacon_state_store::BeaconStateStore;
|
||||
pub use self::pow_chain_store::PoWChainStore;
|
||||
pub use self::validator_store::{ValidatorStore, ValidatorStoreError};
|
||||
|
||||
pub const BLOCKS_DB_COLUMN: &str = "blocks";
|
||||
pub const STATES_DB_COLUMN: &str = "states";
|
||||
pub const POW_CHAIN_DB_COLUMN: &str = "powchain";
|
||||
pub const VALIDATOR_DB_COLUMN: &str = "validator";
|
||||
|
||||
pub const COLUMNS: [&str; 4] = [
|
||||
BLOCKS_DB_COLUMN,
|
||||
STATES_DB_COLUMN,
|
||||
POW_CHAIN_DB_COLUMN,
|
||||
VALIDATOR_DB_COLUMN,
|
||||
];
|
||||
@@ -1,68 +0,0 @@
|
||||
use super::POW_CHAIN_DB_COLUMN as DB_COLUMN;
|
||||
use super::{ClientDB, DBError};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct PoWChainStore<T>
|
||||
where
|
||||
T: ClientDB,
|
||||
{
|
||||
db: Arc<T>,
|
||||
}
|
||||
|
||||
impl<T: ClientDB> PoWChainStore<T> {
|
||||
pub fn new(db: Arc<T>) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub fn put_block_hash(&self, hash: &[u8]) -> Result<(), DBError> {
|
||||
self.db.put(DB_COLUMN, hash, &[0])
|
||||
}
|
||||
|
||||
pub fn block_hash_exists(&self, hash: &[u8]) -> Result<bool, DBError> {
|
||||
self.db.exists(DB_COLUMN, hash)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate types;
|
||||
|
||||
use super::super::super::MemoryDB;
|
||||
use super::*;
|
||||
|
||||
use self::types::Hash256;
|
||||
|
||||
#[test]
|
||||
fn test_put_block_hash() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = PoWChainStore::new(db.clone());
|
||||
|
||||
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
|
||||
store.put_block_hash(hash).unwrap();
|
||||
|
||||
assert!(db.exists(DB_COLUMN, hash).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_hash_exists() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = PoWChainStore::new(db.clone());
|
||||
|
||||
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
|
||||
db.put(DB_COLUMN, hash, &[0]).unwrap();
|
||||
|
||||
assert!(store.block_hash_exists(hash).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_hash_does_not_exist() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = PoWChainStore::new(db.clone());
|
||||
|
||||
let hash = &Hash256::from("some hash".as_bytes()).to_vec();
|
||||
let other_hash = &Hash256::from("another hash".as_bytes()).to_vec();
|
||||
db.put(DB_COLUMN, hash, &[0]).unwrap();
|
||||
|
||||
assert!(!store.block_hash_exists(other_hash).unwrap());
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
extern crate bytes;
|
||||
|
||||
use self::bytes::{BufMut, BytesMut};
|
||||
use super::VALIDATOR_DB_COLUMN as DB_COLUMN;
|
||||
use super::{ClientDB, DBError};
|
||||
use bls::PublicKey;
|
||||
use ssz::{ssz_encode, Decodable};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ValidatorStoreError {
|
||||
DBError(String),
|
||||
DecodeError,
|
||||
}
|
||||
|
||||
impl From<DBError> for ValidatorStoreError {
|
||||
fn from(error: DBError) -> Self {
|
||||
ValidatorStoreError::DBError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum KeyPrefixes {
|
||||
PublicKey,
|
||||
}
|
||||
|
||||
pub struct ValidatorStore<T>
|
||||
where
|
||||
T: ClientDB,
|
||||
{
|
||||
db: Arc<T>,
|
||||
}
|
||||
|
||||
impl<T: ClientDB> ValidatorStore<T> {
|
||||
pub fn new(db: Arc<T>) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
fn prefix_bytes(&self, key_prefix: &KeyPrefixes) -> Vec<u8> {
|
||||
match key_prefix {
|
||||
KeyPrefixes::PublicKey => b"pubkey".to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_db_key_for_index(&self, key_prefix: &KeyPrefixes, index: usize) -> Vec<u8> {
|
||||
let mut buf = BytesMut::with_capacity(6 + 8);
|
||||
buf.put(self.prefix_bytes(key_prefix));
|
||||
buf.put_u64_be(index as u64);
|
||||
buf.take().to_vec()
|
||||
}
|
||||
|
||||
pub fn put_public_key_by_index(
|
||||
&self,
|
||||
index: usize,
|
||||
public_key: &PublicKey,
|
||||
) -> Result<(), ValidatorStoreError> {
|
||||
let key = self.get_db_key_for_index(&KeyPrefixes::PublicKey, index);
|
||||
let val = ssz_encode(public_key);
|
||||
self.db
|
||||
.put(DB_COLUMN, &key[..], &val[..])
|
||||
.map_err(ValidatorStoreError::from)
|
||||
}
|
||||
|
||||
pub fn get_public_key_by_index(
|
||||
&self,
|
||||
index: usize,
|
||||
) -> Result<Option<PublicKey>, ValidatorStoreError> {
|
||||
let key = self.get_db_key_for_index(&KeyPrefixes::PublicKey, index);
|
||||
let val = self.db.get(DB_COLUMN, &key[..])?;
|
||||
match val {
|
||||
None => Ok(None),
|
||||
Some(val) => match PublicKey::ssz_decode(&val, 0) {
|
||||
Ok((key, _)) => Ok(Some(key)),
|
||||
Err(_) => Err(ValidatorStoreError::DecodeError),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::super::MemoryDB;
|
||||
use super::*;
|
||||
use bls::Keypair;
|
||||
|
||||
#[test]
|
||||
fn test_prefix_bytes() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = ValidatorStore::new(db.clone());
|
||||
|
||||
assert_eq!(
|
||||
store.prefix_bytes(&KeyPrefixes::PublicKey),
|
||||
b"pubkey".to_vec()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_db_key_for_index() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = ValidatorStore::new(db.clone());
|
||||
|
||||
let mut buf = BytesMut::with_capacity(6 + 8);
|
||||
buf.put(b"pubkey".to_vec());
|
||||
buf.put_u64_be(42);
|
||||
assert_eq!(
|
||||
store.get_db_key_for_index(&KeyPrefixes::PublicKey, 42),
|
||||
buf.take().to_vec()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_put_public_key_by_index() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = ValidatorStore::new(db.clone());
|
||||
|
||||
let index = 3;
|
||||
let public_key = Keypair::random().pk;
|
||||
|
||||
store.put_public_key_by_index(index, &public_key).unwrap();
|
||||
let public_key_at_index = db
|
||||
.get(
|
||||
DB_COLUMN,
|
||||
&store.get_db_key_for_index(&KeyPrefixes::PublicKey, index)[..],
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(public_key_at_index, ssz_encode(&public_key));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_public_key_by_index() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = ValidatorStore::new(db.clone());
|
||||
|
||||
let index = 4;
|
||||
let public_key = Keypair::random().pk;
|
||||
|
||||
db.put(
|
||||
DB_COLUMN,
|
||||
&store.get_db_key_for_index(&KeyPrefixes::PublicKey, index)[..],
|
||||
&ssz_encode(&public_key)[..],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let public_key_at_index = store.get_public_key_by_index(index).unwrap().unwrap();
|
||||
assert_eq!(public_key_at_index, public_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_public_key_by_unknown_index() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = ValidatorStore::new(db.clone());
|
||||
|
||||
let public_key = Keypair::random().pk;
|
||||
|
||||
db.put(
|
||||
DB_COLUMN,
|
||||
&store.get_db_key_for_index(&KeyPrefixes::PublicKey, 3)[..],
|
||||
&ssz_encode(&public_key)[..],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let public_key_at_index = store.get_public_key_by_index(4).unwrap();
|
||||
assert_eq!(public_key_at_index, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_invalid_public_key() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = ValidatorStore::new(db.clone());
|
||||
|
||||
let key = store.get_db_key_for_index(&KeyPrefixes::PublicKey, 42);
|
||||
db.put(DB_COLUMN, &key[..], "cats".as_bytes()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
store.get_public_key_by_index(42),
|
||||
Err(ValidatorStoreError::DecodeError)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validator_store_put_get() {
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let store = ValidatorStore::new(db);
|
||||
|
||||
let keys = vec![
|
||||
Keypair::random(),
|
||||
Keypair::random(),
|
||||
Keypair::random(),
|
||||
Keypair::random(),
|
||||
Keypair::random(),
|
||||
];
|
||||
|
||||
for i in 0..keys.len() {
|
||||
store.put_public_key_by_index(i, &keys[i].pk).unwrap();
|
||||
}
|
||||
|
||||
/*
|
||||
* Check all keys are retrieved correctly.
|
||||
*/
|
||||
for i in 0..keys.len() {
|
||||
let retrieved = store.get_public_key_by_index(i).unwrap().unwrap();
|
||||
assert_eq!(retrieved, keys[i].pk);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that an index that wasn't stored returns None.
|
||||
*/
|
||||
assert!(store
|
||||
.get_public_key_by_index(keys.len() + 1)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
pub type DBValue = Vec<u8>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DBError {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl DBError {
|
||||
pub fn new(message: String) -> Self {
|
||||
Self { message }
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic database to be used by the "client' (i.e.,
|
||||
/// the lighthouse blockchain client).
|
||||
///
|
||||
/// The purpose of having this generic trait is to allow the
|
||||
/// program to use a persistent on-disk database during production,
|
||||
/// but use a transient database during tests.
|
||||
pub trait ClientDB: Sync + Send {
|
||||
fn get(&self, col: &str, key: &[u8]) -> Result<Option<DBValue>, DBError>;
|
||||
|
||||
fn put(&self, col: &str, key: &[u8], val: &[u8]) -> Result<(), DBError>;
|
||||
|
||||
fn exists(&self, col: &str, key: &[u8]) -> Result<bool, DBError>;
|
||||
|
||||
fn delete(&self, col: &str, key: &[u8]) -> Result<(), DBError>;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Stores the core configuration for this Lighthouse instance.
|
||||
/// This struct is general, other components may implement more
|
||||
/// specialized config structs.
|
||||
#[derive(Clone)]
|
||||
pub struct LighthouseConfig {
|
||||
pub data_dir: PathBuf,
|
||||
pub p2p_listen_port: u16,
|
||||
}
|
||||
|
||||
const DEFAULT_LIGHTHOUSE_DIR: &str = ".lighthouse";
|
||||
|
||||
impl LighthouseConfig {
|
||||
/// Build a new lighthouse configuration from defaults.
|
||||
pub fn default() -> Self {
|
||||
let data_dir = {
|
||||
let home = dirs::home_dir().expect("Unable to determine home dir.");
|
||||
home.join(DEFAULT_LIGHTHOUSE_DIR)
|
||||
};
|
||||
fs::create_dir_all(&data_dir)
|
||||
.unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir));
|
||||
let p2p_listen_port = 0;
|
||||
Self {
|
||||
data_dir,
|
||||
p2p_listen_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
extern crate slog;
|
||||
|
||||
mod config;
|
||||
mod rpc;
|
||||
|
||||
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},
|
||||
MemoryDB,
|
||||
};
|
||||
use slog::{error, info, o, Drain};
|
||||
use slot_clock::SystemTimeSlotClock;
|
||||
use std::sync::Arc;
|
||||
use types::{ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, Hash256, Keypair};
|
||||
|
||||
fn main() {
|
||||
let decorator = slog_term::TermDecorator::new().build();
|
||||
let drain = slog_term::CompactFormat::new(decorator).build().fuse();
|
||||
let drain = slog_async::Async::new(drain).build().fuse();
|
||||
let log = slog::Logger::root(drain, o!());
|
||||
|
||||
let matches = App::new("Lighthouse")
|
||||
.version("0.0.1")
|
||||
.author("Sigma Prime <paul@sigmaprime.io>")
|
||||
.about("Eth 2.0 Client")
|
||||
.arg(
|
||||
Arg::with_name("datadir")
|
||||
.long("datadir")
|
||||
.value_name("DIR")
|
||||
.help("Data directory for keys and databases.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("port")
|
||||
.long("port")
|
||||
.value_name("PORT")
|
||||
.help("Network listen port for p2p connections.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let mut config = LighthouseConfig::default();
|
||||
|
||||
// Custom datadir
|
||||
if let Some(dir) = matches.value_of("datadir") {
|
||||
config.data_dir = PathBuf::from(dir.to_string());
|
||||
}
|
||||
|
||||
// Custom p2p listen port
|
||||
if let Some(port_str) = matches.value_of("port") {
|
||||
if let Ok(port) = port_str.parse::<u16>() {
|
||||
config.p2p_listen_port = port;
|
||||
} else {
|
||||
error!(log, "Invalid port"; "port" => port_str);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Log configuration
|
||||
info!(log, "";
|
||||
"data_dir" => &config.data_dir.to_str(),
|
||||
"port" => &config.p2p_listen_port);
|
||||
|
||||
// Specification (presently fixed to foundation).
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
// Database (presently in-memory)
|
||||
let db = Arc::new(MemoryDB::open());
|
||||
let block_store = Arc::new(BeaconBlockStore::new(db.clone()));
|
||||
let state_store = Arc::new(BeaconStateStore::new(db.clone()));
|
||||
|
||||
// Slot clock
|
||||
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");
|
||||
|
||||
/*
|
||||
* 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
|
||||
let _chain_result = BeaconChain::genesis(
|
||||
state_store.clone(),
|
||||
block_store.clone(),
|
||||
slot_clock,
|
||||
genesis_time,
|
||||
latest_eth1_data,
|
||||
initial_validator_deposits,
|
||||
spec,
|
||||
);
|
||||
|
||||
let _server = start_server(log.clone());
|
||||
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use futures::Future;
|
||||
use grpcio::{RpcContext, UnarySink};
|
||||
use protos::services::{
|
||||
BeaconBlock as BeaconBlockProto, ProduceBeaconBlockRequest, ProduceBeaconBlockResponse,
|
||||
PublishBeaconBlockRequest, PublishBeaconBlockResponse,
|
||||
};
|
||||
use protos::services_grpc::BeaconBlockService;
|
||||
use slog::Logger;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BeaconBlockServiceInstance {
|
||||
pub log: Logger,
|
||||
}
|
||||
|
||||
impl BeaconBlockService for BeaconBlockServiceInstance {
|
||||
/// Produce a `BeaconBlock` for signing by a validator.
|
||||
fn produce_beacon_block(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: ProduceBeaconBlockRequest,
|
||||
sink: UnarySink<ProduceBeaconBlockResponse>,
|
||||
) {
|
||||
println!("producing at slot {}", req.get_slot());
|
||||
|
||||
// TODO: build a legit block.
|
||||
let mut block = BeaconBlockProto::new();
|
||||
block.set_slot(req.get_slot());
|
||||
block.set_block_root(b"cats".to_vec());
|
||||
|
||||
let mut resp = ProduceBeaconBlockResponse::new();
|
||||
resp.set_block(block);
|
||||
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| println!("failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
|
||||
/// Accept some fully-formed `BeaconBlock`, process and publish it.
|
||||
fn publish_beacon_block(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: PublishBeaconBlockRequest,
|
||||
sink: UnarySink<PublishBeaconBlockResponse>,
|
||||
) {
|
||||
println!("publishing {:?}", req.get_block());
|
||||
|
||||
// TODO: actually process the block.
|
||||
let mut resp = PublishBeaconBlockResponse::new();
|
||||
resp.set_success(true);
|
||||
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| println!("failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
mod beacon_block;
|
||||
mod validator;
|
||||
|
||||
use self::beacon_block::BeaconBlockServiceInstance;
|
||||
use self::validator::ValidatorServiceInstance;
|
||||
use grpcio::{Environment, Server, ServerBuilder};
|
||||
use protos::services_grpc::{create_beacon_block_service, create_validator_service};
|
||||
use std::sync::Arc;
|
||||
|
||||
use slog::{info, Logger};
|
||||
|
||||
pub fn start_server(log: Logger) -> Server {
|
||||
let log_clone = log.clone();
|
||||
let env = Arc::new(Environment::new(1));
|
||||
|
||||
let beacon_block_service = {
|
||||
let instance = BeaconBlockServiceInstance { log: log.clone() };
|
||||
create_beacon_block_service(instance)
|
||||
};
|
||||
let validator_service = {
|
||||
let instance = ValidatorServiceInstance { log: log.clone() };
|
||||
create_validator_service(instance)
|
||||
};
|
||||
|
||||
let mut server = ServerBuilder::new(env)
|
||||
.register_service(beacon_block_service)
|
||||
.register_service(validator_service)
|
||||
.bind("127.0.0.1", 50_051)
|
||||
.build()
|
||||
.unwrap();
|
||||
server.start();
|
||||
for &(ref host, port) in server.bind_addrs() {
|
||||
info!(log_clone, "gRPC listening on {}:{}", host, port);
|
||||
}
|
||||
server
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use bls::PublicKey;
|
||||
use futures::Future;
|
||||
use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink};
|
||||
use protos::services::{
|
||||
IndexResponse, ProposeBlockSlotRequest, ProposeBlockSlotResponse, PublicKey as PublicKeyRequest,
|
||||
};
|
||||
use protos::services_grpc::ValidatorService;
|
||||
use slog::{debug, Logger};
|
||||
use ssz::Decodable;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatorServiceInstance {
|
||||
pub log: Logger,
|
||||
}
|
||||
|
||||
impl ValidatorService for ValidatorServiceInstance {
|
||||
fn validator_index(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: PublicKeyRequest,
|
||||
sink: UnarySink<IndexResponse>,
|
||||
) {
|
||||
if let Ok((public_key, _)) = PublicKey::ssz_decode(req.get_public_key(), 0) {
|
||||
debug!(self.log, "RPC request"; "endpoint" => "ValidatorIndex", "public_key" => public_key.concatenated_hex_id());
|
||||
|
||||
let mut resp = IndexResponse::new();
|
||||
|
||||
// TODO: return a legit value.
|
||||
resp.set_index(1);
|
||||
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| println!("failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
} else {
|
||||
let f = sink
|
||||
.fail(RpcStatus::new(
|
||||
RpcStatusCode::InvalidArgument,
|
||||
Some("Invalid public_key".to_string()),
|
||||
))
|
||||
.map_err(move |e| println!("failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn propose_block_slot(
|
||||
&mut self,
|
||||
ctx: RpcContext,
|
||||
req: ProposeBlockSlotRequest,
|
||||
sink: UnarySink<ProposeBlockSlotResponse>,
|
||||
) {
|
||||
debug!(self.log, "RPC request"; "endpoint" => "ProposeBlockSlot", "epoch" => req.get_epoch(), "validator_index" => req.get_validator_index());
|
||||
|
||||
let mut resp = ProposeBlockSlotResponse::new();
|
||||
|
||||
// TODO: return a legit value.
|
||||
resp.set_slot(1);
|
||||
|
||||
let f = sink
|
||||
.success(resp)
|
||||
.map_err(move |e| println!("failed to reply {:?}: {:?}", req, e));
|
||||
ctx.spawn(f)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user