From 661ef65de8263666bec7bc5d1acbadda0b950075 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Mon, 20 Jan 2020 02:22:59 +0530 Subject: [PATCH] Persist eth1 cache (#760) * Add intermediate structures for bytes conversion * Expose byte conversion methods from `Eth1Service` * Add eth1 ssz containers * Fix type errors * Load eth1 cache on restart * Fix compile errors * Update Cargo.lock * Add comments and minor formatting * Add test for eth1 cache persistence * Restrict Deposit and Block cache field visibility * Add checks * Fix `SszDepositCache` check * Implement Encode/Decode directly on `BlockCache` --- Cargo.lock | 1 + beacon_node/beacon_chain/src/beacon_chain.rs | 5 +- beacon_node/beacon_chain/src/builder.rs | 18 +-- beacon_node/beacon_chain/src/eth1_chain.rs | 110 +++++++++++++++--- .../src/persisted_beacon_chain.rs | 2 + beacon_node/beacon_chain/src/test_utils.rs | 3 +- beacon_node/client/src/builder.rs | 21 ++-- beacon_node/eth1/Cargo.toml | 1 + beacon_node/eth1/src/block_cache.rs | 5 +- beacon_node/eth1/src/deposit_cache.rs | 43 +++++++ beacon_node/eth1/src/deposit_log.rs | 3 +- beacon_node/eth1/src/inner.rs | 50 +++++++- beacon_node/eth1/src/lib.rs | 1 + beacon_node/eth1/src/service.rs | 14 +++ beacon_node/eth1/tests/test.rs | 74 ++++++++++++ 15 files changed, 315 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0a3f76bd8..05787dde38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1087,6 +1087,7 @@ dependencies = [ "eth1_test_rig 0.1.0", "eth2_hashing 0.1.1", "eth2_ssz 0.1.2", + "eth2_ssz_derive 0.1.0", "exit-future 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 99e8f4b050..cf55297a77 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -116,7 +116,7 @@ pub trait BeaconChainTypes: Send + Sync + 'static { type StoreMigrator: store::Migrate; type SlotClock: slot_clock::SlotClock; type LmdGhost: LmdGhost; - type Eth1Chain: Eth1ChainBackend; + type Eth1Chain: Eth1ChainBackend; type EthSpec: types::EthSpec; type EventHandler: EventHandler; } @@ -135,7 +135,7 @@ pub struct BeaconChain { /// inclusion in a block. pub op_pool: OperationPool, /// Provides information from the Ethereum 1 (PoW) chain. - pub eth1_chain: Option>, + pub eth1_chain: Option>, /// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received. pub(crate) canonical_head: TimeoutRwLock>, /// The root of the genesis block. @@ -190,6 +190,7 @@ impl BeaconChain { genesis_block_root: self.genesis_block_root, ssz_head_tracker: self.head_tracker.to_ssz_container(), fork_choice: self.fork_choice.as_ssz_container(), + eth1_cache: self.eth1_chain.as_ref().map(|x| x.as_ssz_container()), block_root_tree: self.block_root_tree.as_ssz_container(), }; diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 25201c0910..5738d494e8 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -57,7 +57,7 @@ where TStoreMigrator: store::Migrate + 'static, TSlotClock: SlotClock + 'static, TLmdGhost: LmdGhost + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -87,7 +87,7 @@ pub struct BeaconChainBuilder { genesis_block_root: Option, op_pool: Option>, fork_choice: Option>, - eth1_chain: Option>, + eth1_chain: Option>, event_handler: Option, slot_clock: Option, persisted_beacon_chain: Option>, @@ -114,7 +114,7 @@ where TStoreMigrator: store::Migrate + 'static, TSlotClock: SlotClock + 'static, TLmdGhost: LmdGhost + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -175,7 +175,7 @@ where /// Attempt to load an existing chain from the builder's `Store`. /// /// May initialize several components; including the op_pool and finalized checkpoints. - pub fn resume_from_db(mut self) -> Result { + pub fn resume_from_db(mut self, config: Eth1Config) -> Result { let log = self .log .as_ref() @@ -226,6 +226,10 @@ where HeadTracker::from_ssz_container(&p.ssz_head_tracker) .map_err(|e| format!("Failed to decode head tracker for database: {:?}", e))?, ); + self.eth1_chain = match &p.eth1_cache { + Some(cache) => Some(Eth1Chain::from_ssz_container(cache, config, store, log)?), + None => None, + }; self.block_root_tree = Some(Arc::new(p.block_root_tree.clone().into())); self.persisted_beacon_chain = Some(p); @@ -422,7 +426,7 @@ where TStore: Store + 'static, TStoreMigrator: store::Migrate + 'static, TSlotClock: SlotClock + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -541,7 +545,7 @@ where TStore: Store + 'static, TStoreMigrator: store::Migrate + 'static, TLmdGhost: LmdGhost + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -583,7 +587,7 @@ where TStoreMigrator: store::Migrate + 'static, TSlotClock: SlotClock + 'static, TLmdGhost: LmdGhost + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, { /// Sets the `BeaconChain` event handler to `NullEventHandler`. diff --git a/beacon_node/beacon_chain/src/eth1_chain.rs b/beacon_node/beacon_chain/src/eth1_chain.rs index ce11b75d7e..9b7f772048 100644 --- a/beacon_node/beacon_chain/src/eth1_chain.rs +++ b/beacon_node/beacon_chain/src/eth1_chain.rs @@ -6,6 +6,7 @@ use futures::Future; use integer_sqrt::IntegerSquareRoot; use rand::prelude::*; use slog::{crit, debug, error, trace, Logger}; +use ssz_derive::{Decode, Encode}; use state_processing::per_block_processing::get_new_eth1_data; use std::collections::HashMap; use std::iter::DoubleEndedIterator; @@ -48,23 +49,31 @@ pub enum Error { UnknownPreviousEth1BlockHash, } +#[derive(Encode, Decode, Clone)] +pub struct SszEth1 { + use_dummy_backend: bool, + backend_bytes: Vec, +} + /// Holds an `Eth1ChainBackend` and serves requests from the `BeaconChain`. -pub struct Eth1Chain +pub struct Eth1Chain where - T: Eth1ChainBackend, + T: Eth1ChainBackend, E: EthSpec, + S: Store, { backend: T, /// When `true`, the backend will be ignored and dummy data from the 2019 Canada interop method /// will be used instead. pub use_dummy_backend: bool, - _phantom: PhantomData, + _phantom: PhantomData<(E, S)>, } -impl Eth1Chain +impl Eth1Chain where - T: Eth1ChainBackend, + T: Eth1ChainBackend, E: EthSpec, + S: Store, { pub fn new(backend: T) -> Self { Self { @@ -82,7 +91,8 @@ where spec: &ChainSpec, ) -> Result { if self.use_dummy_backend { - DummyEth1ChainBackend::default().eth1_data(state, spec) + let dummy_backend: DummyEth1ChainBackend = DummyEth1ChainBackend::default(); + dummy_backend.eth1_data(state, spec) } else { self.backend.eth1_data(state, spec) } @@ -103,14 +113,41 @@ where spec: &ChainSpec, ) -> Result, Error> { if self.use_dummy_backend { - DummyEth1ChainBackend::default().queued_deposits(state, eth1_data_vote, spec) + let dummy_backend: DummyEth1ChainBackend = DummyEth1ChainBackend::default(); + dummy_backend.queued_deposits(state, eth1_data_vote, spec) } else { self.backend.queued_deposits(state, eth1_data_vote, spec) } } + + /// Instantiate `Eth1Chain` from a persisted `SszEth1`. + /// + /// The `Eth1Chain` will have the same caches as the persisted `SszEth1`. + pub fn from_ssz_container( + ssz_container: &SszEth1, + config: Eth1Config, + store: Arc, + log: &Logger, + ) -> Result { + let backend = + Eth1ChainBackend::from_bytes(&ssz_container.backend_bytes, config, store, log.clone())?; + Ok(Self { + use_dummy_backend: ssz_container.use_dummy_backend, + backend, + _phantom: PhantomData, + }) + } + + /// Return a `SszEth1` containing the state of `Eth1Chain`. + pub fn as_ssz_container(&self) -> SszEth1 { + SszEth1 { + use_dummy_backend: self.use_dummy_backend, + backend_bytes: self.backend.as_bytes(), + } + } } -pub trait Eth1ChainBackend: Sized + Send + Sync { +pub trait Eth1ChainBackend>: Sized + Send + Sync { /// Returns the `Eth1Data` that should be included in a block being produced for the given /// `state`. fn eth1_data(&self, beacon_state: &BeaconState, spec: &ChainSpec) @@ -129,6 +166,17 @@ pub trait Eth1ChainBackend: Sized + Send + Sync { eth1_data_vote: &Eth1Data, spec: &ChainSpec, ) -> Result, Error>; + + /// Encode the `Eth1ChainBackend` instance to bytes. + fn as_bytes(&self) -> Vec; + + /// Create a `Eth1ChainBackend` instance given encoded bytes. + fn from_bytes( + bytes: &[u8], + config: Eth1Config, + store: Arc, + log: Logger, + ) -> Result; } /// Provides a simple, testing-only backend that generates deterministic, meaningless eth1 data. @@ -136,9 +184,9 @@ pub trait Eth1ChainBackend: Sized + Send + Sync { /// Never creates deposits, therefore the validator set is static. /// /// This was used in the 2019 Canada interop workshops. -pub struct DummyEth1ChainBackend(PhantomData); +pub struct DummyEth1ChainBackend>(PhantomData<(T, S)>); -impl Eth1ChainBackend for DummyEth1ChainBackend { +impl> Eth1ChainBackend for DummyEth1ChainBackend { /// Produce some deterministic junk based upon the current epoch. fn eth1_data(&self, state: &BeaconState, _spec: &ChainSpec) -> Result { let current_epoch = state.current_epoch(); @@ -164,9 +212,24 @@ impl Eth1ChainBackend for DummyEth1ChainBackend { ) -> Result, Error> { Ok(vec![]) } + + /// Return empty Vec for dummy backend. + fn as_bytes(&self) -> Vec { + Vec::new() + } + + /// Create dummy eth1 backend. + fn from_bytes( + _bytes: &[u8], + _config: Eth1Config, + _store: Arc, + _log: Logger, + ) -> Result { + Ok(Self(PhantomData)) + } } -impl Default for DummyEth1ChainBackend { +impl> Default for DummyEth1ChainBackend { fn default() -> Self { Self(PhantomData) } @@ -214,7 +277,7 @@ impl> CachingEth1Backend { } } -impl> Eth1ChainBackend for CachingEth1Backend { +impl> Eth1ChainBackend for CachingEth1Backend { fn eth1_data(&self, state: &BeaconState, spec: &ChainSpec) -> Result { // Note: we do not return random junk if this function call fails as it would be caused by // an internal error. @@ -346,6 +409,27 @@ impl> Eth1ChainBackend for CachingEth1Backend { .map(|(_deposit_root, deposits)| deposits) } } + + /// Return encoded byte representation of the block and deposit caches. + fn as_bytes(&self) -> Vec { + self.core.as_bytes() + } + + /// Recover the cached backend from encoded bytes. + fn from_bytes( + bytes: &[u8], + config: Eth1Config, + store: Arc, + log: Logger, + ) -> Result { + let inner = HttpService::from_bytes(bytes, config, log.clone())?; + Ok(Self { + core: inner, + store, + log, + _phantom: PhantomData, + }) + } } /// Produces an `Eth1Data` with all fields sourced from `rand::thread_rng()`. @@ -584,7 +668,7 @@ mod test { use store::MemoryStore; use types::test_utils::{generate_deterministic_keypair, TestingDepositBuilder}; - fn get_eth1_chain() -> Eth1Chain>, E> { + fn get_eth1_chain() -> Eth1Chain>, E, MemoryStore> { let eth1_config = Eth1Config { ..Eth1Config::default() }; diff --git a/beacon_node/beacon_chain/src/persisted_beacon_chain.rs b/beacon_node/beacon_chain/src/persisted_beacon_chain.rs index 7f42466e22..a0e281a98c 100644 --- a/beacon_node/beacon_chain/src/persisted_beacon_chain.rs +++ b/beacon_node/beacon_chain/src/persisted_beacon_chain.rs @@ -1,3 +1,4 @@ +use crate::eth1_chain::SszEth1; use crate::fork_choice::SszForkChoice; use crate::head_tracker::SszHeadTracker; use crate::{BeaconChainTypes, CheckPoint}; @@ -18,6 +19,7 @@ pub struct PersistedBeaconChain { pub genesis_block_root: Hash256, pub ssz_head_tracker: SszHeadTracker, pub fork_choice: SszForkChoice, + pub eth1_cache: Option, pub block_root_tree: SszBlockRootTree, } diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 9aad237e1f..fbf437fd18 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -4,6 +4,7 @@ use crate::{ events::NullEventHandler, AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, BlockProcessingOutcome, }; +use eth1::Config as Eth1Config; use genesis::interop_genesis_state; use lmd_ghost::ThreadSafeReducedTree; use rayon::prelude::*; @@ -175,7 +176,7 @@ impl BeaconChainHarness> { .custom_spec(spec.clone()) .store(store.clone()) .store_migrator( as Migrate<_, E>>::new(store)) - .resume_from_db() + .resume_from_db(Eth1Config::default()) .expect("should resume beacon chain from db") .dummy_eth1_backend() .expect("should build dummy backend") diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index 4a219ef0b1..d35fbeb46f 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -84,7 +84,7 @@ where TStoreMigrator: store::Migrate, TSlotClock: SlotClock + Clone + 'static, TLmdGhost: LmdGhost + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -241,7 +241,10 @@ where Box::new(future) } ClientGenesis::Resume => { - let future = builder.resume_from_db().into_future().map(|v| (v, None)); + let future = builder + .resume_from_db(config) + .into_future() + .map(|v| (v, None)); Box::new(future) } @@ -401,7 +404,7 @@ where TStore: Store + 'static, TStoreMigrator: store::Migrate, TSlotClock: SlotClock + Clone + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -449,7 +452,7 @@ where TStoreMigrator: store::Migrate, TSlotClock: SlotClock + 'static, TLmdGhost: LmdGhost + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, { /// Specifies that the `BeaconChain` should publish events using the WebSocket server. @@ -498,7 +501,7 @@ where TSlotClock: SlotClock + 'static, TStoreMigrator: store::Migrate, TEthSpec> + 'static, TLmdGhost: LmdGhost, TEthSpec> + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend> + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -548,7 +551,7 @@ where TSlotClock: SlotClock + 'static, TStoreMigrator: store::Migrate, TEthSpec> + 'static, TLmdGhost: LmdGhost, TEthSpec> + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend> + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -576,7 +579,7 @@ impl where TSlotClock: SlotClock + 'static, TLmdGhost: LmdGhost, TEthSpec> + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend> + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -606,7 +609,7 @@ impl where TSlotClock: SlotClock + 'static, TLmdGhost: LmdGhost, TEthSpec> + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend> + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { @@ -737,7 +740,7 @@ where TStore: Store + 'static, TStoreMigrator: store::Migrate, TLmdGhost: LmdGhost + 'static, - TEth1Backend: Eth1ChainBackend + 'static, + TEth1Backend: Eth1ChainBackend + 'static, TEthSpec: EthSpec + 'static, TEventHandler: EventHandler + 'static, { diff --git a/beacon_node/eth1/Cargo.toml b/beacon_node/eth1/Cargo.toml index 604e0a2bc5..03e592940b 100644 --- a/beacon_node/eth1/Cargo.toml +++ b/beacon_node/eth1/Cargo.toml @@ -19,6 +19,7 @@ hex = "0.3" types = { path = "../../eth2/types"} merkle_proof = { path = "../../eth2/utils/merkle_proof"} eth2_ssz = { path = "../../eth2/utils/ssz"} +eth2_ssz_derive = "0.1.0" tree_hash = { path = "../../eth2/utils/tree_hash"} eth2_hashing = { path = "../../eth2/utils/eth2_hashing"} parking_lot = "0.7" diff --git a/beacon_node/eth1/src/block_cache.rs b/beacon_node/eth1/src/block_cache.rs index bdf4060b90..088d8dcfdc 100644 --- a/beacon_node/eth1/src/block_cache.rs +++ b/beacon_node/eth1/src/block_cache.rs @@ -1,3 +1,4 @@ +use ssz_derive::{Decode, Encode}; use std::ops::RangeInclusive; use types::{Eth1Data, Hash256}; @@ -17,7 +18,7 @@ pub enum Error { /// A block of the eth1 chain. /// /// Contains all information required to add a `BlockCache` entry. -#[derive(Debug, PartialEq, Clone, Eq, Hash)] +#[derive(Debug, PartialEq, Clone, Eq, Hash, Encode, Decode)] pub struct Eth1Block { pub hash: Hash256, pub timestamp: u64, @@ -38,7 +39,7 @@ impl Eth1Block { /// Stores block and deposit contract information and provides queries based upon the block /// timestamp. -#[derive(Debug, PartialEq, Clone, Default)] +#[derive(Debug, PartialEq, Clone, Default, Encode, Decode)] pub struct BlockCache { blocks: Vec, } diff --git a/beacon_node/eth1/src/deposit_cache.rs b/beacon_node/eth1/src/deposit_cache.rs index eb31bccc51..b3fe67b7ec 100644 --- a/beacon_node/eth1/src/deposit_cache.rs +++ b/beacon_node/eth1/src/deposit_cache.rs @@ -1,5 +1,6 @@ use crate::DepositLog; use eth2_hashing::hash; +use ssz_derive::{Decode, Encode}; use tree_hash::TreeHash; use types::{Deposit, Hash256, DEPOSIT_TREE_DEPTH}; @@ -79,6 +80,48 @@ impl DepositDataTree { } } +#[derive(Encode, Decode, Clone)] +pub struct SszDepositCache { + logs: Vec, + leaves: Vec, + deposit_contract_deploy_block: u64, + deposit_roots: Vec, +} + +impl SszDepositCache { + pub fn from_deposit_cache(cache: &DepositCache) -> Self { + Self { + logs: cache.logs.clone(), + leaves: cache.leaves.clone(), + deposit_contract_deploy_block: cache.deposit_contract_deploy_block, + deposit_roots: cache.deposit_roots.clone(), + } + } + + pub fn to_deposit_cache(&self) -> Result { + let deposit_tree = + DepositDataTree::create(&self.leaves, self.leaves.len(), DEPOSIT_TREE_DEPTH); + // Check for invalid SszDepositCache conditions + if self.leaves.len() != self.logs.len() { + return Err("Invalid SszDepositCache: logs and leaves should have equal length".into()); + } + // `deposit_roots` also includes the zero root + if self.leaves.len() + 1 != self.deposit_roots.len() { + return Err( + "Invalid SszDepositCache: deposit_roots length must be only one more than leaves" + .into(), + ); + } + Ok(DepositCache { + logs: self.logs.clone(), + leaves: self.leaves.clone(), + deposit_contract_deploy_block: self.deposit_contract_deploy_block, + deposit_tree, + deposit_roots: self.deposit_roots.clone(), + }) + } +} + /// Mirrors the merkle tree of deposits in the eth1 deposit contract. /// /// Provides `Deposit` objects with merkle proofs included. diff --git a/beacon_node/eth1/src/deposit_log.rs b/beacon_node/eth1/src/deposit_log.rs index d42825c756..4acb902a73 100644 --- a/beacon_node/eth1/src/deposit_log.rs +++ b/beacon_node/eth1/src/deposit_log.rs @@ -1,5 +1,6 @@ use super::http::Log; use ssz::Decode; +use ssz_derive::{Decode, Encode}; use types::{DepositData, Hash256, PublicKeyBytes, SignatureBytes}; /// The following constants define the layout of bytes in the deposit contract `DepositEvent`. The @@ -16,7 +17,7 @@ const INDEX_START: usize = SIG_START + 96 + 32; const INDEX_LEN: usize = 8; /// A fully parsed eth1 deposit contract log. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Encode, Decode)] pub struct DepositLog { pub deposit_data: DepositData, /// The block number of the log that included this `DepositData`. diff --git a/beacon_node/eth1/src/inner.rs b/beacon_node/eth1/src/inner.rs index 76b19cb0af..eac5834e10 100644 --- a/beacon_node/eth1/src/inner.rs +++ b/beacon_node/eth1/src/inner.rs @@ -1,6 +1,11 @@ use crate::Config; -use crate::{block_cache::BlockCache, deposit_cache::DepositCache}; +use crate::{ + block_cache::BlockCache, + deposit_cache::{DepositCache, SszDepositCache}, +}; use parking_lot::RwLock; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; #[derive(Default)] pub struct DepositUpdater { @@ -34,4 +39,47 @@ impl Inner { self.block_cache.write().truncate(block_cache_truncation); } } + + /// Encode the eth1 block and deposit cache as bytes. + pub fn as_bytes(&self) -> Vec { + let ssz_eth1_cache = SszEth1Cache::from_inner(&self); + ssz_eth1_cache.as_ssz_bytes() + } + + /// Recover `Inner` given byte representation of eth1 deposit and block caches. + pub fn from_bytes(bytes: &[u8], config: Config) -> Result { + let ssz_cache = SszEth1Cache::from_ssz_bytes(bytes) + .map_err(|e| format!("Ssz decoding error: {:?}", e))?; + Ok(ssz_cache.to_inner(config)?) + } +} + +#[derive(Encode, Decode, Clone)] +pub struct SszEth1Cache { + block_cache: BlockCache, + deposit_cache: SszDepositCache, + last_processed_block: Option, +} + +impl SszEth1Cache { + pub fn from_inner(inner: &Inner) -> Self { + let deposit_updater = inner.deposit_cache.read(); + let block_cache = inner.block_cache.read(); + Self { + block_cache: (*block_cache).clone(), + deposit_cache: SszDepositCache::from_deposit_cache(&deposit_updater.cache), + last_processed_block: deposit_updater.last_processed_block, + } + } + + pub fn to_inner(&self, config: Config) -> Result { + Ok(Inner { + block_cache: RwLock::new(self.block_cache.clone()), + deposit_cache: RwLock::new(DepositUpdater { + cache: self.deposit_cache.to_deposit_cache()?, + last_processed_block: self.last_processed_block, + }), + config: RwLock::new(config), + }) + } } diff --git a/beacon_node/eth1/src/lib.rs b/beacon_node/eth1/src/lib.rs index b3352c81a7..f5f018bd17 100644 --- a/beacon_node/eth1/src/lib.rs +++ b/beacon_node/eth1/src/lib.rs @@ -12,4 +12,5 @@ mod service; pub use block_cache::{BlockCache, Eth1Block}; pub use deposit_cache::DepositCache; pub use deposit_log::DepositLog; +pub use inner::SszEth1Cache; pub use service::{BlockCacheUpdateOutcome, Config, DepositCacheUpdateOutcome, Error, Service}; diff --git a/beacon_node/eth1/src/service.rs b/beacon_node/eth1/src/service.rs index e6a763abf6..383df1d4a6 100644 --- a/beacon_node/eth1/src/service.rs +++ b/beacon_node/eth1/src/service.rs @@ -151,6 +151,20 @@ impl Service { } } + /// Return byte representation of deposit and block caches. + pub fn as_bytes(&self) -> Vec { + self.inner.as_bytes() + } + + /// Recover the deposit and block caches from encoded bytes. + pub fn from_bytes(bytes: &[u8], config: Config, log: Logger) -> Result { + let inner = Inner::from_bytes(bytes, config)?; + Ok(Self { + inner: Arc::new(inner), + log, + }) + } + /// Provides access to the block cache. pub fn blocks(&self) -> &RwLock { &self.inner.block_cache diff --git a/beacon_node/eth1/tests/test.rs b/beacon_node/eth1/tests/test.rs index f6312f07e6..d8597b9849 100644 --- a/beacon_node/eth1/tests/test.rs +++ b/beacon_node/eth1/tests/test.rs @@ -803,3 +803,77 @@ mod fast { } } } + +mod persist { + use super::*; + #[test] + fn test_persisit_caches() { + let mut env = new_env(); + let log = env.core_context().log; + let runtime = env.runtime(); + + let eth1 = runtime + .block_on(GanacheEth1Instance::new()) + .expect("should start eth1 environment"); + let deposit_contract = ð1.deposit_contract; + let web3 = eth1.web3(); + + let now = get_block_number(runtime, &web3); + let config = Config { + endpoint: eth1.endpoint(), + deposit_contract_address: deposit_contract.address(), + deposit_contract_deploy_block: now, + lowest_cached_block_number: now, + follow_distance: 0, + block_cache_truncation: None, + ..Config::default() + }; + let service = Service::new(config.clone(), log.clone()); + let n = 10; + let deposits: Vec<_> = (0..n).into_iter().map(|_| random_deposit_data()).collect(); + for deposit in &deposits { + deposit_contract + .deposit(runtime, deposit.clone()) + .expect("should perform a deposit"); + } + + runtime + .block_on(service.update_deposit_cache()) + .expect("should perform update"); + + assert!( + service.deposit_cache_len() >= n, + "should have imported n deposits" + ); + + let deposit_count = service.deposit_cache_len(); + + runtime + .block_on(service.update_block_cache()) + .expect("should perform update"); + + assert!( + service.block_cache_len() >= n, + "should have imported n eth1 blocks" + ); + + let block_count = service.block_cache_len(); + + let eth1_bytes = service.as_bytes(); + + // Drop service and recover from bytes + drop(service); + + let recovered_service = Service::from_bytes(ð1_bytes, config, log).unwrap(); + assert_eq!( + recovered_service.block_cache_len(), + block_count, + "Should have equal cached blocks as before recovery" + ); + assert_eq!( + recovered_service.deposit_cache_len(), + deposit_count, + "Should have equal cached deposits as before recovery" + ); + } +}