From 9ed8a4d3806e3bb9ba778cdb8e00ec03ada65998 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 27 May 2019 16:13:32 +1000 Subject: [PATCH] Implement basic `BeaconChain` persistence. --- beacon_node/beacon_chain/Cargo.toml | 1 + beacon_node/beacon_chain/src/beacon_chain.rs | 46 ++++++++++ beacon_node/beacon_chain/src/checkpoint.rs | 3 +- beacon_node/beacon_chain/src/lib.rs | 1 + .../src/persisted_beacon_chain.rs | 30 +++++++ beacon_node/client/src/beacon_chain_types.rs | 90 ++++++++----------- beacon_node/client/src/error.rs | 6 +- beacon_node/client/src/lib.rs | 7 +- beacon_node/eth2-libp2p/src/error.rs | 5 +- beacon_node/network/src/beacon_chain.rs | 4 +- beacon_node/network/src/error.rs | 5 +- beacon_node/rpc/src/beacon_chain.rs | 2 +- beacon_node/src/run.rs | 11 ++- 13 files changed, 137 insertions(+), 74 deletions(-) create mode 100644 beacon_node/beacon_chain/src/persisted_beacon_chain.rs diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 3a84256a7..bf19a56a5 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -21,6 +21,7 @@ serde_derive = "1.0" serde_json = "1.0" slot_clock = { path = "../../eth2/utils/slot_clock" } ssz = { path = "../../eth2/utils/ssz" } +ssz_derive = { path = "../../eth2/utils/ssz_derive" } state_processing = { path = "../../eth2/state_processing" } tree_hash = { path = "../../eth2/utils/tree_hash" } types = { path = "../../eth2/types" } diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index a614698c4..bf807188f 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1,5 +1,6 @@ use crate::checkpoint::CheckPoint; use crate::errors::{BeaconChainError as Error, BlockProductionError}; +use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY}; use fork_choice::{ForkChoice, ForkChoiceError}; use log::{debug, trace}; use operation_pool::DepositInsertStatus; @@ -140,6 +141,51 @@ impl BeaconChain { }) } + /// Attempt to load an existing instance from the given `store`. + pub fn from_store(store: Arc) -> Result>, Error> { + let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes()); + let p: PersistedBeaconChain = match store.get(&key) { + Err(e) => return Err(e.into()), + Ok(None) => return Ok(None), + Ok(Some(p)) => p, + }; + + let spec = T::EthSpec::spec(); + + let slot_clock = T::SlotClock::new( + spec.genesis_slot, + p.state.genesis_time, + spec.seconds_per_slot, + ); + + let fork_choice = T::ForkChoice::new(store.clone()); + + Ok(Some(BeaconChain { + store, + slot_clock, + op_pool: OperationPool::default(), + canonical_head: RwLock::new(p.canonical_head), + finalized_head: RwLock::new(p.finalized_head), + state: RwLock::new(p.state), + spec, + fork_choice: RwLock::new(fork_choice), + })) + } + + /// Attempt to save this instance to `self.store`. + pub fn persist(&self) -> Result<(), Error> { + let p: PersistedBeaconChain = PersistedBeaconChain { + canonical_head: self.canonical_head.read().clone(), + finalized_head: self.finalized_head.read().clone(), + state: self.state.read().clone(), + }; + + let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes()); + self.store.put(&key, &p)?; + + Ok(()) + } + /// Returns the beacon block body for each beacon block root in `roots`. /// /// Fails if any root in `roots` does not have a corresponding block. diff --git a/beacon_node/beacon_chain/src/checkpoint.rs b/beacon_node/beacon_chain/src/checkpoint.rs index c069ac104..c25e75a85 100644 --- a/beacon_node/beacon_chain/src/checkpoint.rs +++ b/beacon_node/beacon_chain/src/checkpoint.rs @@ -1,9 +1,10 @@ use serde_derive::Serialize; +use ssz_derive::{Decode, Encode}; use types::{BeaconBlock, BeaconState, EthSpec, Hash256}; /// Represents some block and it's associated state. Generally, this will be used for tracking the /// head, justified head and finalized head. -#[derive(Clone, Serialize, PartialEq, Debug)] +#[derive(Clone, Serialize, PartialEq, Debug, Encode, Decode)] pub struct CheckPoint { pub beacon_block: BeaconBlock, pub beacon_block_root: Hash256, diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 9f3058d0b..0e3e01a4b 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,6 +1,7 @@ mod beacon_chain; mod checkpoint; mod errors; +mod persisted_beacon_chain; pub use self::beacon_chain::{ BeaconChain, BeaconChainTypes, BlockProcessingOutcome, InvalidBlock, ValidBlock, diff --git a/beacon_node/beacon_chain/src/persisted_beacon_chain.rs b/beacon_node/beacon_chain/src/persisted_beacon_chain.rs new file mode 100644 index 000000000..cb34e995c --- /dev/null +++ b/beacon_node/beacon_chain/src/persisted_beacon_chain.rs @@ -0,0 +1,30 @@ +use crate::{BeaconChainTypes, CheckPoint}; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; +use store::{DBColumn, Error as StoreError, StoreItem}; +use types::BeaconState; + +/// 32-byte key for accessing the `PersistedBeaconChain`. +pub const BEACON_CHAIN_DB_KEY: &str = "PERSISTEDBEACONCHAINPERSISTEDBEA"; + +#[derive(Encode, Decode)] +pub struct PersistedBeaconChain { + pub canonical_head: CheckPoint, + pub finalized_head: CheckPoint, + // TODO: operations pool. + pub state: BeaconState, +} + +impl StoreItem for PersistedBeaconChain { + fn db_column() -> DBColumn { + DBColumn::BeaconChain + } + + fn as_store_bytes(&self) -> Vec { + self.as_ssz_bytes() + } + + fn from_store_bytes(bytes: &mut [u8]) -> Result { + Self::from_ssz_bytes(bytes).map_err(Into::into) + } +} diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index 1971f5603..7ffb26b8b 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -1,4 +1,3 @@ -use crate::ClientConfig; use beacon_chain::{ fork_choice::BitwiseLMDGhost, slot_clock::SystemTimeSlotClock, @@ -15,7 +14,7 @@ use types::{ /// Provides a new, initialized `BeaconChain` pub trait InitialiseBeaconChain { - fn initialise_beacon_chain(config: &ClientConfig) -> BeaconChain; + fn initialise_beacon_chain(store: Arc) -> BeaconChain; } /// A testnet-suitable BeaconChainType, using `MemoryStore`. @@ -29,16 +28,9 @@ impl BeaconChainTypes for TestnetMemoryBeaconChainTypes { type EthSpec = FewValidatorsEthSpec; } -impl InitialiseBeaconChain for TestnetMemoryBeaconChainTypes -where - T: BeaconChainTypes< - Store = MemoryStore, - SlotClock = SystemTimeSlotClock, - ForkChoice = BitwiseLMDGhost, - >, -{ - fn initialise_beacon_chain(_config: &ClientConfig) -> BeaconChain { - initialize_chain::<_, _, FewValidatorsEthSpec>(MemoryStore::open()) +impl InitialiseBeaconChain for TestnetMemoryBeaconChainTypes { + fn initialise_beacon_chain(store: Arc) -> BeaconChain { + maybe_load_from_store_for_testnet::<_, T::Store, T::EthSpec>(store) } } @@ -53,55 +45,49 @@ impl BeaconChainTypes for TestnetDiskBeaconChainTypes { type EthSpec = FewValidatorsEthSpec; } -impl InitialiseBeaconChain for TestnetDiskBeaconChainTypes -where - T: BeaconChainTypes< - Store = DiskStore, - SlotClock = SystemTimeSlotClock, - ForkChoice = BitwiseLMDGhost, - >, -{ - fn initialise_beacon_chain(config: &ClientConfig) -> BeaconChain { - let store = DiskStore::open(&config.db_name).expect("Unable to open DB."); - - initialize_chain::<_, _, FewValidatorsEthSpec>(store) +impl InitialiseBeaconChain for TestnetDiskBeaconChainTypes { + fn initialise_beacon_chain(store: Arc) -> BeaconChain { + maybe_load_from_store_for_testnet::<_, T::Store, T::EthSpec>(store) } } -/// Produces a `BeaconChain` given some pre-initialized `Store`. -fn initialize_chain(store: U) -> BeaconChain +/// Loads a `BeaconChain` from `store`, if it exists. Otherwise, create a new chain from genesis. +fn maybe_load_from_store_for_testnet(store: Arc) -> BeaconChain where T: BeaconChainTypes, T::ForkChoice: ForkChoice, { - let spec = T::EthSpec::spec(); + if let Ok(Some(beacon_chain)) = BeaconChain::from_store(store.clone()) { + beacon_chain + } else { + let spec = T::EthSpec::spec(); - let store = Arc::new(store); + let state_builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(8, &spec); + let (genesis_state, _keypairs) = state_builder.build(); - let state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(8, &spec); - let (genesis_state, _keypairs) = state_builder.build(); + let mut genesis_block = BeaconBlock::empty(&spec); + genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root()); - let mut genesis_block = BeaconBlock::empty(&spec); - genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root()); + // Slot clock + let slot_clock = T::SlotClock::new( + spec.genesis_slot, + genesis_state.genesis_time, + spec.seconds_per_slot, + ); + // Choose the fork choice + let fork_choice = T::ForkChoice::new(store.clone()); - // Slot clock - let slot_clock = T::SlotClock::new( - spec.genesis_slot, - genesis_state.genesis_time, - spec.seconds_per_slot, - ); - // Choose the fork choice - let fork_choice = T::ForkChoice::new(store.clone()); - - // Genesis chain - //TODO: Handle error correctly - BeaconChain::from_genesis( - store, - slot_clock, - genesis_state, - genesis_block, - spec.clone(), - fork_choice, - ) - .expect("Terminate if beacon chain generation fails") + // Genesis chain + //TODO: Handle error correctly + BeaconChain::from_genesis( + store, + slot_clock, + genesis_state, + genesis_block, + spec.clone(), + fork_choice, + ) + .expect("Terminate if beacon chain generation fails") + } } diff --git a/beacon_node/client/src/error.rs b/beacon_node/client/src/error.rs index 618813826..b0272400c 100644 --- a/beacon_node/client/src/error.rs +++ b/beacon_node/client/src/error.rs @@ -1,10 +1,6 @@ -// generates error types use network; -use error_chain::{ - error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed, - impl_extract_backtrace, -}; +use error_chain::error_chain; error_chain! { links { diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 40be9b7b8..df9eb8646 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -50,11 +50,14 @@ where /// Generate an instance of the client. Spawn and link all internal sub-processes. pub fn new( config: ClientConfig, + store: T::Store, log: slog::Logger, executor: &TaskExecutor, ) -> error::Result { - // generate a beacon chain - let beacon_chain = Arc::new(T::initialise_beacon_chain(&config)); + let store = Arc::new(store); + + // Load a `BeaconChain` from the store, or create a new one if it does not exist. + let beacon_chain = Arc::new(T::initialise_beacon_chain(store)); if beacon_chain.read_slot_clock().is_none() { panic!("Cannot start client before genesis!") diff --git a/beacon_node/eth2-libp2p/src/error.rs b/beacon_node/eth2-libp2p/src/error.rs index 163fe575d..a291e8fec 100644 --- a/beacon_node/eth2-libp2p/src/error.rs +++ b/beacon_node/eth2-libp2p/src/error.rs @@ -1,8 +1,5 @@ // generates error types -use error_chain::{ - error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed, - impl_extract_backtrace, -}; +use error_chain::error_chain; error_chain! {} diff --git a/beacon_node/network/src/beacon_chain.rs b/beacon_node/network/src/beacon_chain.rs index 6324e3a94..e38acbb72 100644 --- a/beacon_node/network/src/beacon_chain.rs +++ b/beacon_node/network/src/beacon_chain.rs @@ -5,9 +5,7 @@ use beacon_chain::{ AttestationValidationError, CheckPoint, }; use eth2_libp2p::rpc::HelloMessage; -use types::{ - Attestation, BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Epoch, EthSpec, Hash256, Slot, -}; +use types::{Attestation, BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Epoch, Hash256, Slot}; pub use beacon_chain::{BeaconChainError, BeaconChainTypes, BlockProcessingOutcome, InvalidBlock}; diff --git a/beacon_node/network/src/error.rs b/beacon_node/network/src/error.rs index cdd6b6209..fc061ff44 100644 --- a/beacon_node/network/src/error.rs +++ b/beacon_node/network/src/error.rs @@ -1,10 +1,7 @@ // generates error types use eth2_libp2p; -use error_chain::{ - error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed, - impl_extract_backtrace, -}; +use error_chain::error_chain; error_chain! { links { diff --git a/beacon_node/rpc/src/beacon_chain.rs b/beacon_node/rpc/src/beacon_chain.rs index b0a490137..a37c219f6 100644 --- a/beacon_node/rpc/src/beacon_chain.rs +++ b/beacon_node/rpc/src/beacon_chain.rs @@ -5,7 +5,7 @@ use beacon_chain::{ AttestationValidationError, BlockProductionError, }; pub use beacon_chain::{BeaconChainError, BeaconChainTypes, BlockProcessingOutcome}; -use types::{Attestation, AttestationData, BeaconBlock, EthSpec}; +use types::{Attestation, AttestationData, BeaconBlock}; /// The RPC's API to the beacon chain. pub trait BeaconChain: Send + Sync { diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index 6ec65a92d..d8ff202bf 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -6,6 +6,7 @@ use futures::sync::oneshot; use futures::Future; use slog::info; use std::cell::RefCell; +use store::{DiskStore, MemoryStore}; use tokio::runtime::Builder; use tokio::runtime::Runtime; use tokio::runtime::TaskExecutor; @@ -32,8 +33,11 @@ pub fn run_beacon_node(config: ClientConfig, log: &slog::Logger) -> error::Resul "BeaconNode starting"; "type" => "TestnetDiskBeaconChainTypes" ); + + let store = DiskStore::open(&config.db_name).expect("Unable to open DB."); + let client: Client = - Client::new(config, log.clone(), &executor)?; + Client::new(config, store, log.clone(), &executor)?; run(client, executor, runtime, log) } @@ -43,8 +47,11 @@ pub fn run_beacon_node(config: ClientConfig, log: &slog::Logger) -> error::Resul "BeaconNode starting"; "type" => "TestnetMemoryBeaconChainTypes" ); + + let store = MemoryStore::open(); + let client: Client = - Client::new(config, log.clone(), &executor)?; + Client::new(config, store, log.clone(), &executor)?; run(client, executor, runtime, log) }