Implement very raw state transition logic

This commit is contained in:
Paul Hauner
2019-01-23 19:25:05 +11:00
parent b555916808
commit 1256ba0d01
24 changed files with 748 additions and 159 deletions

View File

@@ -6,6 +6,7 @@ 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" }

View File

@@ -0,0 +1,18 @@
[package]
name = "beacon_chain"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls = { path = "../../eth2/utils/bls" }
boolean-bitfield = { path = "../../eth2/utils/boolean-bitfield" }
db = { path = "../db" }
failure = "0.1"
failure_derive = "0.1"
genesis = { path = "../../eth2/genesis" }
serde_derive = "1.0"
slot_clock = { path = "../../eth2/utils/slot_clock" }
spec = { path = "../../eth2/spec" }
ssz = { path = "../../eth2/utils/ssz" }
types = { path = "../../eth2/types" }

View File

@@ -0,0 +1,136 @@
use super::state_transition::Error as TransitionError;
use super::{BeaconChain, ClientDB, DBError, SlotClock};
use slot_clock::{SystemTimeSlotClockError, TestingSlotClockError};
use ssz::{ssz_encode, Encodable};
use types::{
readers::{BeaconBlockReader, BeaconStateReader},
Hash256,
};
#[derive(Debug, PartialEq)]
pub enum Outcome {
FutureSlot,
Processed,
NewCanonicalBlock,
NewReorgBlock,
NewForkBlock,
StateTransitionFailed(TransitionError),
StateRootMismatch,
}
#[derive(Debug, PartialEq)]
pub enum Error {
DBError(String),
SlotClockError(SystemTimeSlotClockError),
NotImplemented,
PresentSlotIsNone,
UnableToDecodeBlock,
MissingParentState(Hash256),
InvalidParentState(Hash256),
MissingBeaconBlock(Hash256),
InvalidBeaconBlock(Hash256),
MissingParentBlock(Hash256),
NoBlockProducer,
StateSlotMismatch,
BadBlockSignature,
BadRandaoSignature,
MaxProposerSlashingsExceeded,
BadProposerSlashing,
MaxAttestationsExceeded,
BadAttestation,
NoBlockRoot,
MaxDepositsExceeded,
MaxExitsExceeded,
BadExit,
BadCustodyReseeds,
BadCustodyChallenges,
BadCustodyResponses,
}
impl<T, U> BeaconChain<T, U>
where
T: ClientDB,
U: SlotClock,
Error: From<<U as SlotClock>::Error>,
{
pub fn process_block<V>(&mut self, block: V) -> Result<Outcome, Error>
where
V: BeaconBlockReader + Encodable + Sized,
{
let block = block
.into_beacon_block()
.ok_or(Error::UnableToDecodeBlock)?;
let block_root = block.canonical_root();
let present_slot = self
.slot_clock
.present_slot()?
.ok_or(Error::PresentSlotIsNone)?;
// Block from future slots (i.e., greater than the present slot) should not be processed.
if block.slot() > present_slot {
return Ok(Outcome::FutureSlot);
}
let parent_block_root = block.parent_root();
let parent_block = self
.block_store
.get_reader(&parent_block_root)?
.ok_or(Error::MissingParentBlock(parent_block_root))?;
let parent_state_root = parent_block.parent_root();
let parent_state = self
.state_store
.get_reader(&parent_state_root)?
.ok_or(Error::MissingParentState(parent_state_root))?
.into_beacon_state()
.ok_or(Error::InvalidParentState(parent_state_root))?;
let state = match self.state_transition(parent_state, &block) {
Ok(state) => state,
Err(error) => return Ok(Outcome::StateTransitionFailed(error)),
};
let state_root = state.canonical_root();
if block.state_root != state_root {
return Ok(Outcome::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 leaf blocks so the implementation can track the chain heads.
if self.leaf_blocks.contains(&block.parent_root()) {
self.leaf_blocks.remove(&block.parent_root());
}
if self.canonical_leaf_block == block.parent_root() {
self.canonical_leaf_block = block_root;
}
self.leaf_blocks.insert(block_root);
// The block was sucessfully processed.
Ok(Outcome::Processed)
}
}
impl From<DBError> for Error {
fn from(e: DBError) -> Error {
Error::DBError(e.message)
}
}
impl From<TestingSlotClockError> for Error {
fn from(_: TestingSlotClockError) -> Error {
unreachable!(); // Testing clock never throws an error.
}
}
impl From<SystemTimeSlotClockError> for Error {
fn from(e: SystemTimeSlotClockError) -> Error {
Error::SlotClockError(e)
}
}

View File

@@ -1,13 +1,16 @@
use super::state_transition::Error as TransitionError;
use super::{BeaconChain, ClientDB, DBError, SlotClock};
use bls::Signature;
use slot_clock::TestingSlotClockError;
use types::{
readers::{BeaconBlockReader, BeaconStateReader},
BeaconBlock, BeaconState, Hash256,
BeaconBlock, BeaconBlockBody, BeaconState, Hash256,
};
#[derive(Debug, PartialEq)]
pub enum Error {
DBError(String),
StateTransitionError(TransitionError),
PresentSlotIsNone,
}
@@ -17,43 +20,48 @@ where
U: SlotClock,
Error: From<<U as SlotClock>::Error>,
{
pub fn produce_block(&mut self) -> Result<(BeaconBlock, BeaconState), Error> {
/*
* Important: this code is a big stub and only exists to ensure that tests pass.
*
* https://github.com/sigp/lighthouse/issues/107
*/
pub fn produce_block(
&mut self,
randao_reveal: Signature,
) -> Result<(BeaconBlock, BeaconState), Error> {
let present_slot = self
.slot_clock
.present_slot()?
.ok_or(Error::PresentSlotIsNone)?;
let parent_root = self.canonical_leaf_block;
let parent_block_reader = self
.block_store
.get_reader(&parent_root)?
.ok_or_else(|| Error::DBError("Block not found.".to_string()))?;
let parent_state_reader = self
let parent_state = self
.state_store
.get_reader(&parent_block_reader.state_root())?
.ok_or_else(|| Error::DBError("State not found.".to_string()))?;
.ok_or_else(|| Error::DBError("State not found.".to_string()))?
.into_beacon_state()
.ok_or_else(|| Error::DBError("State invalid.".to_string()))?;
let parent_block = parent_block_reader
.into_beacon_block()
.ok_or_else(|| Error::DBError("Bad parent block SSZ.".to_string()))?;
let mut block = BeaconBlock {
slot: present_slot,
parent_root,
state_root: Hash256::zero(), // Updated after the state is calculated.
..parent_block
randao_reveal: randao_reveal,
candidate_pow_receipt_root: Hash256::zero(), // TODO: replace w/ eth1 data.
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
body: BeaconBlockBody {
proposer_slashings: vec![],
casper_slashings: vec![],
attestations: vec![],
custody_reseeds: vec![],
custody_challenges: vec![],
custody_responses: vec![],
deposits: vec![],
exits: vec![],
},
};
let parent_state = parent_state_reader
.into_beacon_state()
.ok_or_else(|| Error::DBError("Bad parent block SSZ.".to_string()))?;
let state = BeaconState {
slot: present_slot,
..parent_state
};
let state = self.state_transition(parent_state, &block)?;
let state_root = state.canonical_root();
block.state_root = state_root;
@@ -68,6 +76,12 @@ impl From<DBError> for Error {
}
}
impl From<TransitionError> for Error {
fn from(e: TransitionError) -> Error {
Error::StateTransitionError(e)
}
}
impl From<TestingSlotClockError> for Error {
fn from(_: TestingSlotClockError) -> Error {
unreachable!(); // Testing clock never throws an error.

View File

@@ -1,8 +1,7 @@
mod block_processing;
mod block_production;
#[cfg(test)]
mod chain_test;
mod lmd_ghost;
mod state_transition;
use db::{
stores::{BeaconBlockStore, BeaconStateStore},

View File

@@ -0,0 +1,484 @@
use super::{BeaconChain, ClientDB, DBError, SlotClock};
use bls::{AggregatePublicKey, AggregateSignature, PublicKey, Signature};
use boolean_bitfield::BooleanBitfield;
use slot_clock::{SystemTimeSlotClockError, TestingSlotClockError};
use ssz::ssz_encode;
use types::{
readers::{BeaconBlockReader, BeaconStateReader},
AttestationData, AttestationDataAndCustodyBit, BeaconBlock, BeaconState, Exit, ForkData,
Hash256, PendingAttestationRecord, ProposalSignedData,
};
// TODO: define elsehwere.
const DOMAIN_ATTESTATION: u64 = 1;
const DOMAIN_PROPOSAL: u64 = 2;
const DOMAIN_EXIT: u64 = 3;
const DOMAIN_RANDAO: u64 = 4;
macro_rules! ensure {
($condition: expr, $result: expr) => {
if !$condition {
return Err($result);
}
};
}
#[derive(Debug, PartialEq)]
pub enum Error {
DBError(String),
StateAlreadyTransitioned,
NotImplemented,
PresentSlotIsNone,
UnableToDecodeBlock,
MissingParentState(Hash256),
InvalidParentState(Hash256),
MissingBeaconBlock(Hash256),
InvalidBeaconBlock(Hash256),
MissingParentBlock(Hash256),
NoBlockProducer,
StateSlotMismatch,
BadBlockSignature,
BadRandaoSignature,
MaxProposerSlashingsExceeded,
BadProposerSlashing,
MaxAttestationsExceeded,
BadAttestation,
NoBlockRoot,
MaxDepositsExceeded,
MaxExitsExceeded,
BadExit,
BadCustodyReseeds,
BadCustodyChallenges,
BadCustodyResponses,
SlotClockError(SystemTimeSlotClockError),
}
impl<T, U> BeaconChain<T, U>
where
T: ClientDB,
U: SlotClock,
{
pub fn state_transition(
&self,
mut state: BeaconState,
block: &BeaconBlock,
) -> Result<BeaconState, Error> {
ensure!(state.slot < block.slot, Error::StateAlreadyTransitioned);
for _ in state.slot..block.slot {
self.per_slot_processing(&mut state, &block.parent_root)?;
}
/*
* Slot
*/
ensure!(block.slot() == state.slot, Error::StateSlotMismatch);
/*
* Proposer Signature
*/
let block_without_signature_root = {
let mut block_without_signature = block.clone();
block_without_signature.signature = self.spec.empty_signature.clone();
block_without_signature.canonical_root()
};
let proposal_root = {
let proposal = ProposalSignedData {
slot: state.slot,
shard: self.spec.beacon_chain_shard_number,
block_root: block_without_signature_root,
};
hash_tree_root(&proposal)
};
let block_proposer_index =
get_beacon_proposer_index(&state, block.slot, self.spec.epoch_length)
.ok_or(Error::NoBlockProducer)?;
let block_proposer = &state.validator_registry[block_proposer_index];
ensure!(
bls_verify(
&block_proposer.pubkey,
&proposal_root,
&block.signature,
get_domain(&state.fork_data, state.slot, DOMAIN_PROPOSAL)
),
Error::BadBlockSignature
);
/*
* RANDAO
*/
ensure!(
bls_verify(
&block_proposer.pubkey,
&ssz_encode(&block_proposer.proposer_slots),
&block.randao_reveal,
get_domain(&state.fork_data, state.slot, DOMAIN_RANDAO)
),
Error::BadRandaoSignature
);
let new_mix = {
let mut mix = state.latest_randao_mixes
[(state.slot % self.spec.latest_randao_mixes_length) as usize]
.to_vec();
mix.append(&mut ssz_encode(&block.randao_reveal));
hash(&mix)
};
state.latest_randao_mixes[(state.slot % self.spec.latest_randao_mixes_length) as usize] =
new_mix;
/*
* Eth1 data
*/
// TODO: Eth1 data stuff.
/*
* OPERATIONS
*/
/*
* Proposer slashings
*/
ensure!(
block.body.proposer_slashings.len() as u64 <= self.spec.max_proposer_slashings,
Error::MaxProposerSlashingsExceeded
);
for proposer_slashing in &block.body.proposer_slashings {
let proposer = state
.validator_registry
.get(proposer_slashing.proposer_index as usize)
.ok_or(Error::BadProposerSlashing)?;
ensure!(
proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot,
Error::BadProposerSlashing
);
ensure!(
proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard,
Error::BadProposerSlashing
);
ensure!(
proposer_slashing.proposal_data_1.block_root
!= proposer_slashing.proposal_data_2.block_root,
Error::BadProposerSlashing
);
ensure!(
proposer.penalized_slot > state.slot,
Error::BadProposerSlashing
);
ensure!(
bls_verify(
&proposer.pubkey,
&hash_tree_root(&proposer_slashing.proposal_data_1),
&proposer_slashing.proposal_signature_1,
get_domain(
&state.fork_data,
proposer_slashing.proposal_data_1.slot,
DOMAIN_PROPOSAL
)
),
Error::BadProposerSlashing
);
ensure!(
bls_verify(
&proposer.pubkey,
&hash_tree_root(&proposer_slashing.proposal_data_2),
&proposer_slashing.proposal_signature_2,
get_domain(
&state.fork_data,
proposer_slashing.proposal_data_2.slot,
DOMAIN_PROPOSAL
)
),
Error::BadProposerSlashing
);
penalize_validator(&state, proposer_slashing.proposer_index as usize);
}
/*
* Attestations
*/
ensure!(
block.body.attestations.len() as u64 <= self.spec.max_attestations,
Error::MaxAttestationsExceeded
);
for attestation in &block.body.attestations {
ensure!(
attestation.data.slot + self.spec.min_attestation_inclusion_delay <= state.slot,
Error::BadAttestation
);
ensure!(
attestation.data.slot + self.spec.epoch_length >= state.slot,
Error::BadAttestation
);
if state.justified_slot >= state.slot - (state.slot % self.spec.epoch_length) {
ensure!(
attestation.data.justified_slot == state.justified_slot,
Error::BadAttestation
);
} else {
ensure!(
attestation.data.justified_slot == state.previous_justified_slot,
Error::BadAttestation
);
}
ensure!(
attestation.data.justified_block_root
== *get_block_root(
&state,
attestation.data.justified_slot,
self.spec.latest_block_roots_length
)
.ok_or(Error::NoBlockRoot)?,
Error::BadAttestation
);
ensure!(
(attestation.data.latest_crosslink_root
== state.latest_crosslinks[attestation.data.shard as usize].shard_block_root)
|| (attestation.data.shard_block_root
== state.latest_crosslinks[attestation.data.shard as usize]
.shard_block_root),
Error::BadAttestation
);
let participants = get_attestation_participants(
&state,
&attestation.data,
&attestation.aggregation_bitfield,
);
let mut group_public_key = AggregatePublicKey::new();
for participant in participants {
group_public_key.add(
state.validator_registry[participant as usize]
.pubkey
.as_raw(),
)
}
let attestation_message = {
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
data: attestation.data.clone(),
custody_bit: false,
};
hash_tree_root(&attestation_data_and_custody_bit).to_vec()
};
// Signature verification.
ensure!(
bls_verify_aggregate(
&group_public_key,
&attestation_message[..],
&attestation.aggregate_signature,
get_domain(&state.fork_data, attestation.data.slot, DOMAIN_ATTESTATION)
),
Error::BadProposerSlashing
);
ensure!(
attestation.data.shard_block_root == self.spec.zero_hash,
Error::BadAttestation
);
let pending_attestation = PendingAttestationRecord {
data: attestation.data.clone(),
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
custody_bitfield: attestation.custody_bitfield.clone(),
slot_included: state.slot,
};
state.latest_attestations.push(pending_attestation);
}
/*
* Deposits
*/
ensure!(
block.body.deposits.len() as u64 <= self.spec.max_deposits,
Error::MaxDepositsExceeded
);
// TODO: process deposits.
/*
* Exits
*/
ensure!(
block.body.exits.len() as u64 <= self.spec.max_exits,
Error::MaxExitsExceeded
);
for exit in &block.body.exits {
let validator = state
.validator_registry
.get(exit.validator_index as usize)
.ok_or(Error::BadExit)?;
ensure!(
validator.exit_slot > state.slot + self.spec.entry_exit_delay,
Error::BadExit
);
ensure!(state.slot >= exit.slot, Error::BadExit);
let exit_message = {
let exit_struct = Exit {
slot: exit.slot,
validator_index: exit.validator_index,
signature: self.spec.empty_signature.clone(),
};
hash_tree_root(&exit_struct)
};
ensure!(
bls_verify(
&validator.pubkey,
&exit_message,
&exit.signature,
get_domain(&state.fork_data, exit.slot, DOMAIN_EXIT)
),
Error::BadProposerSlashing
);
initiate_validator_exit(&state, exit.validator_index);
}
/*
* Custody
*/
ensure!(
block.body.custody_reseeds.is_empty(),
Error::BadCustodyReseeds
);
ensure!(
block.body.custody_challenges.is_empty(),
Error::BadCustodyChallenges
);
ensure!(
block.body.custody_responses.is_empty(),
Error::BadCustodyResponses
);
Ok(state)
}
fn per_slot_processing(
&self,
state: &mut BeaconState,
previous_block_root: &Hash256,
) -> Result<(), Error> {
let epoch_length = self.spec.epoch_length;
let latest_randao_mixes_length = self.spec.latest_randao_mixes_length;
let latest_block_roots_length = self.spec.latest_block_roots_length;
// Misc counters.
state.slot += 1;
let block_proposer = get_beacon_proposer_index(&state, state.slot, epoch_length)
.ok_or(Error::NoBlockProducer)?;
state.validator_registry[block_proposer].proposer_slots += 1;
state.latest_randao_mixes[(state.slot % latest_randao_mixes_length) as usize] =
state.latest_randao_mixes[((state.slot - 1) % latest_randao_mixes_length) as usize];
// Block roots.
state.latest_block_roots
[((state.slot - 1) % self.spec.latest_block_roots_length) as usize] =
*previous_block_root;
if state.slot % latest_block_roots_length == 0 {
let root = merkle_root(&state.latest_block_roots[..]);
state.batched_block_roots.push(root);
}
Ok(())
}
}
fn initiate_validator_exit(_state: &BeaconState, _index: u32) {
// TODO: stubbed out.
}
fn get_attestation_participants(
_state: &BeaconState,
_attestation_data: &AttestationData,
_aggregation_bitfield: &BooleanBitfield,
) -> Vec<usize> {
vec![0, 1]
}
fn get_block_root(
state: &BeaconState,
slot: u64,
latest_block_roots_length: u64,
) -> Option<&Hash256> {
// TODO: test
if state.slot <= slot + latest_block_roots_length && slot <= state.slot {
state
.latest_block_roots
.get((slot % latest_block_roots_length) as usize)
} else {
None
}
}
fn penalize_validator(_state: &BeaconState, _proposer_index: usize) {
// TODO: stubbed out.
}
fn hash<T>(_input: &T) -> Hash256 {
// TODO: stubbed out.
Hash256::zero()
}
fn get_domain(_fork: &ForkData, _slot: u64, _domain_type: u64) -> u64 {
// TODO: stubbed out.
0
}
fn bls_verify(_pubkey: &PublicKey, _message: &[u8], _signature: &Signature, _domain: u64) -> bool {
// TODO: stubbed out.
true
}
fn bls_verify_aggregate(
_pubkey: &AggregatePublicKey,
_message: &[u8],
_signature: &AggregateSignature,
_domain: u64,
) -> bool {
// TODO: stubbed out.
true
}
fn hash_tree_root<T>(_input: &T) -> Hash256 {
// TODO: stubbed out.
Hash256::zero()
}
fn merkle_root(_input: &[Hash256]) -> Hash256 {
// TODO: stubbed out.
Hash256::zero()
}
fn get_beacon_proposer_index(
_state: &BeaconState,
_slot: u64,
_epoch_length: u64,
) -> Option<usize> {
// TODO: stubbed out.
Some(0)
}
impl From<DBError> for Error {
fn from(e: DBError) -> Error {
Error::DBError(e.message)
}
}
impl From<TestingSlotClockError> for Error {
fn from(_: TestingSlotClockError) -> Error {
unreachable!(); // Testing clock never throws an error.
}
}
impl From<SystemTimeSlotClockError> for Error {
fn from(e: SystemTimeSlotClockError) -> Error {
Error::SlotClockError(e)
}
}

View File

@@ -1,4 +1,5 @@
use super::{BeaconChain, BlockProcessingOutcome};
use beacon_chain::{BeaconChain, BlockProcessingOutcome};
use bls::Signature;
use db::{
stores::{BeaconBlockStore, BeaconStateStore},
MemoryDB,
@@ -43,7 +44,7 @@ fn it_produces() {
fn it_processes_a_block_it_produces() {
let (_db, mut chain) = in_memory_test_chain(ChainSpec::foundation());
let (block, _state) = chain.produce_block().unwrap();
let (outcome, new_block_hash) = chain.process_block(&block).unwrap();
let (outcome, new_block_hash) = chain.process_block(block).unwrap();
assert_eq!(outcome, BlockProcessingOutcome::Processed);
assert_eq!(chain.canonical_leaf_block, new_block_hash);
}

View File

@@ -1,72 +0,0 @@
use super::{BeaconChain, ClientDB, DBError, SlotClock};
use slot_clock::TestingSlotClockError;
use ssz::{ssz_encode, Encodable};
use types::{readers::BeaconBlockReader, Hash256};
#[derive(Debug, PartialEq)]
pub enum Outcome {
FutureSlot,
Processed,
NewCanonicalBlock,
NewReorgBlock,
NewForkBlock,
}
#[derive(Debug, PartialEq)]
pub enum Error {
DBError(String),
NotImplemented,
PresentSlotIsNone,
}
impl<T, U> BeaconChain<T, U>
where
T: ClientDB,
U: SlotClock,
Error: From<<U as SlotClock>::Error>,
{
pub fn process_block<V>(&mut self, block: &V) -> Result<(Outcome, Hash256), Error>
where
V: BeaconBlockReader + Encodable + Sized,
{
let block_root = block.canonical_root();
let present_slot = self
.slot_clock
.present_slot()?
.ok_or(Error::PresentSlotIsNone)?;
// Block from future slots (i.e., greater than the present slot) should not be processed.
if block.slot() > present_slot {
return Ok((Outcome::FutureSlot, block_root));
}
// TODO: block processing has been removed.
// https://github.com/sigp/lighthouse/issues/98
// Update leaf blocks.
self.block_store.put(&block_root, &ssz_encode(block)[..])?;
if self.leaf_blocks.contains(&block.parent_root()) {
self.leaf_blocks.remove(&block.parent_root());
}
if self.canonical_leaf_block == block.parent_root() {
self.canonical_leaf_block = block_root;
}
self.leaf_blocks.insert(block_root);
Ok((Outcome::Processed, block_root))
}
}
impl From<DBError> for Error {
fn from(e: DBError) -> Error {
Error::DBError(e.message)
}
}
impl From<TestingSlotClockError> for Error {
fn from(_: TestingSlotClockError) -> Error {
unreachable!(); // Testing clock never throws an error.
}
}

View File

@@ -1,14 +1,13 @@
extern crate slog;
mod beacon_chain;
mod config;
mod rpc;
use std::path::PathBuf;
use self::beacon_chain::BeaconChain;
use crate::config::LighthouseConfig;
use crate::rpc::start_server;
use beacon_chain::BeaconChain;
use clap::{App, Arg};
use db::{
stores::{BeaconBlockStore, BeaconStateStore},