mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 12:56:12 +00:00
Implement checkpoint sync (#2244)
## Issue Addressed Closes #1891 Closes #1784 ## Proposed Changes Implement checkpoint sync for Lighthouse, enabling it to start from a weak subjectivity checkpoint. ## Additional Info - [x] Return unavailable status for out-of-range blocks requested by peers (#2561) - [x] Implement sync daemon for fetching historical blocks (#2561) - [x] Verify chain hashes (either in `historical_blocks.rs` or the calling module) - [x] Consistency check for initial block + state - [x] Fetch the initial state and block from a beacon node HTTP endpoint - [x] Don't crash fetching beacon states by slot from the API - [x] Background service for state reconstruction, triggered by CLI flag or API call. Considered out of scope for this PR: - Drop the requirement to provide the `--checkpoint-block` (this would require some pretty heavy refactoring of block verification) Co-authored-by: Diva M <divma@protonmail.com>
This commit is contained in:
@@ -28,8 +28,8 @@ use std::time::Duration;
|
||||
use store::{Error as StoreError, HotColdDB, ItemStore};
|
||||
use task_executor::ShutdownReason;
|
||||
use types::{
|
||||
BeaconBlock, BeaconState, ChainSpec, EthSpec, Graffiti, Hash256, PublicKeyBytes, Signature,
|
||||
SignedBeaconBlock, Slot,
|
||||
BeaconBlock, BeaconState, ChainSpec, Checkpoint, EthSpec, Graffiti, Hash256, PublicKeyBytes,
|
||||
Signature, SignedBeaconBlock, Slot,
|
||||
};
|
||||
|
||||
/// An empty struct used to "witness" all the `BeaconChainTypes` traits. It has no user-facing
|
||||
@@ -282,12 +282,19 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Starts a new chain from a genesis state.
|
||||
pub fn genesis_state(
|
||||
/// Store the genesis state & block in the DB.
|
||||
///
|
||||
/// Do *not* initialize fork choice, or do anything that assumes starting from genesis.
|
||||
///
|
||||
/// Return the `BeaconSnapshot` representing genesis as well as the mutated builder.
|
||||
fn set_genesis_state(
|
||||
mut self,
|
||||
mut beacon_state: BeaconState<TEthSpec>,
|
||||
) -> Result<Self, String> {
|
||||
let store = self.store.clone().ok_or("genesis_state requires a store")?;
|
||||
) -> Result<(BeaconSnapshot<TEthSpec>, Self), String> {
|
||||
let store = self
|
||||
.store
|
||||
.clone()
|
||||
.ok_or("set_genesis_state requires a store")?;
|
||||
|
||||
let beacon_block = genesis_block(&mut beacon_state, &self.spec)?;
|
||||
|
||||
@@ -298,9 +305,6 @@ where
|
||||
let beacon_state_root = beacon_block.message().state_root();
|
||||
let beacon_block_root = beacon_block.canonical_root();
|
||||
|
||||
self.genesis_state_root = Some(beacon_state_root);
|
||||
self.genesis_block_root = Some(beacon_block_root);
|
||||
|
||||
store
|
||||
.put_state(&beacon_state_root, &beacon_state)
|
||||
.map_err(|e| format!("Failed to store genesis state: {:?}", e))?;
|
||||
@@ -318,11 +322,26 @@ where
|
||||
)
|
||||
})?;
|
||||
|
||||
let genesis = BeaconSnapshot {
|
||||
beacon_block,
|
||||
beacon_block_root,
|
||||
beacon_state,
|
||||
};
|
||||
self.genesis_state_root = Some(beacon_state_root);
|
||||
self.genesis_block_root = Some(beacon_block_root);
|
||||
self.genesis_time = Some(beacon_state.genesis_time());
|
||||
|
||||
Ok((
|
||||
BeaconSnapshot {
|
||||
beacon_block_root,
|
||||
beacon_block,
|
||||
beacon_state,
|
||||
},
|
||||
self,
|
||||
))
|
||||
}
|
||||
|
||||
/// Starts a new chain from a genesis state.
|
||||
pub fn genesis_state(mut self, beacon_state: BeaconState<TEthSpec>) -> Result<Self, String> {
|
||||
let store = self.store.clone().ok_or("genesis_state requires a store")?;
|
||||
|
||||
let (genesis, updated_builder) = self.set_genesis_state(beacon_state)?;
|
||||
self = updated_builder;
|
||||
|
||||
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &genesis);
|
||||
|
||||
@@ -332,10 +351,115 @@ where
|
||||
&genesis.beacon_block,
|
||||
&genesis.beacon_state,
|
||||
)
|
||||
.map_err(|e| format!("Unable to build initialize ForkChoice: {:?}", e))?;
|
||||
.map_err(|e| format!("Unable to initialize ForkChoice: {:?}", e))?;
|
||||
|
||||
self.fork_choice = Some(fork_choice);
|
||||
|
||||
Ok(self.empty_op_pool())
|
||||
}
|
||||
|
||||
/// Start the chain from a weak subjectivity state.
|
||||
pub fn weak_subjectivity_state(
|
||||
mut self,
|
||||
mut weak_subj_state: BeaconState<TEthSpec>,
|
||||
weak_subj_block: SignedBeaconBlock<TEthSpec>,
|
||||
genesis_state: BeaconState<TEthSpec>,
|
||||
) -> Result<Self, String> {
|
||||
let store = self.store.clone().ok_or("genesis_state requires a store")?;
|
||||
|
||||
let weak_subj_slot = weak_subj_state.slot();
|
||||
let weak_subj_block_root = weak_subj_block.canonical_root();
|
||||
let weak_subj_state_root = weak_subj_block.state_root();
|
||||
|
||||
// Check that the given block lies on an epoch boundary. Due to the database only storing
|
||||
// full states on epoch boundaries and at restore points it would be difficult to support
|
||||
// starting from a mid-epoch state.
|
||||
if weak_subj_slot % TEthSpec::slots_per_epoch() != 0 {
|
||||
return Err(format!(
|
||||
"Checkpoint block at slot {} is not aligned to epoch start. \
|
||||
Please supply an aligned checkpoint with block.slot % 32 == 0",
|
||||
weak_subj_block.slot(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check that the block and state have consistent slots and state roots.
|
||||
if weak_subj_state.slot() != weak_subj_block.slot() {
|
||||
return Err(format!(
|
||||
"Slot of snapshot block ({}) does not match snapshot state ({})",
|
||||
weak_subj_block.slot(),
|
||||
weak_subj_state.slot(),
|
||||
));
|
||||
}
|
||||
|
||||
let computed_state_root = weak_subj_state
|
||||
.update_tree_hash_cache()
|
||||
.map_err(|e| format!("Error computing checkpoint state root: {:?}", e))?;
|
||||
|
||||
if weak_subj_state_root != computed_state_root {
|
||||
return Err(format!(
|
||||
"Snapshot state root does not match block, expected: {:?}, got: {:?}",
|
||||
weak_subj_state_root, computed_state_root
|
||||
));
|
||||
}
|
||||
|
||||
// Check that the checkpoint state is for the same network as the genesis state.
|
||||
// This check doesn't do much for security but should prevent mistakes.
|
||||
if weak_subj_state.genesis_validators_root() != genesis_state.genesis_validators_root() {
|
||||
return Err(format!(
|
||||
"Snapshot state appears to be from the wrong network. Genesis validators root \
|
||||
is {:?} but should be {:?}",
|
||||
weak_subj_state.genesis_validators_root(),
|
||||
genesis_state.genesis_validators_root()
|
||||
));
|
||||
}
|
||||
|
||||
// Set the store's split point *before* storing genesis so that genesis is stored
|
||||
// immediately in the freezer DB.
|
||||
store.set_split(weak_subj_slot, weak_subj_state_root);
|
||||
store
|
||||
.store_split()
|
||||
.map_err(|e| format!("Error storing DB split point: {:?}", e))?;
|
||||
|
||||
let (_, updated_builder) = self.set_genesis_state(genesis_state)?;
|
||||
self = updated_builder;
|
||||
|
||||
store
|
||||
.put_state(&weak_subj_state_root, &weak_subj_state)
|
||||
.map_err(|e| format!("Failed to store weak subjectivity state: {:?}", e))?;
|
||||
store
|
||||
.put_block(&weak_subj_block_root, weak_subj_block.clone())
|
||||
.map_err(|e| format!("Failed to store weak subjectivity block: {:?}", e))?;
|
||||
|
||||
// Store anchor info (context for weak subj sync).
|
||||
store
|
||||
.init_anchor_info(weak_subj_block.message())
|
||||
.map_err(|e| format!("Failed to initialize anchor info: {:?}", e))?;
|
||||
|
||||
// Store pruning checkpoint to prevent attempting to prune before the anchor state.
|
||||
store
|
||||
.store_pruning_checkpoint(Checkpoint {
|
||||
root: weak_subj_block_root,
|
||||
epoch: weak_subj_state.slot().epoch(TEthSpec::slots_per_epoch()),
|
||||
})
|
||||
.map_err(|e| format!("Failed to write pruning checkpoint: {:?}", e))?;
|
||||
|
||||
let snapshot = BeaconSnapshot {
|
||||
beacon_block_root: weak_subj_block_root,
|
||||
beacon_block: weak_subj_block,
|
||||
beacon_state: weak_subj_state,
|
||||
};
|
||||
|
||||
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &snapshot);
|
||||
|
||||
let fork_choice = ForkChoice::from_anchor(
|
||||
fc_store,
|
||||
snapshot.beacon_block_root,
|
||||
&snapshot.beacon_block,
|
||||
&snapshot.beacon_state,
|
||||
)
|
||||
.map_err(|e| format!("Unable to initialize ForkChoice: {:?}", e))?;
|
||||
|
||||
self.fork_choice = Some(fork_choice);
|
||||
self.genesis_time = Some(genesis.beacon_state.genesis_time());
|
||||
|
||||
Ok(self.empty_op_pool())
|
||||
}
|
||||
@@ -520,12 +644,13 @@ where
|
||||
let fc_finalized = fork_choice.finalized_checkpoint();
|
||||
let head_finalized = canonical_head.beacon_state.finalized_checkpoint();
|
||||
if fc_finalized != head_finalized {
|
||||
if head_finalized.root == Hash256::zero()
|
||||
let is_genesis = head_finalized.root.is_zero()
|
||||
&& head_finalized.epoch == fc_finalized.epoch
|
||||
&& fc_finalized.root == genesis_block_root
|
||||
{
|
||||
// This is a legal edge-case encountered during genesis.
|
||||
} else {
|
||||
&& fc_finalized.root == genesis_block_root;
|
||||
let is_wss = store.get_anchor_slot().map_or(false, |anchor_slot| {
|
||||
fc_finalized.epoch == anchor_slot.epoch(TEthSpec::slots_per_epoch())
|
||||
});
|
||||
if !is_genesis && !is_wss {
|
||||
return Err(format!(
|
||||
"Database corrupt: fork choice is finalized at {:?} whilst head is finalized at \
|
||||
{:?}",
|
||||
@@ -654,6 +779,11 @@ where
|
||||
"head_slot" => format!("{}", head.beacon_block.slot()),
|
||||
);
|
||||
|
||||
// Check for states to reconstruct (in the background).
|
||||
if beacon_chain.config.reconstruct_historic_states {
|
||||
beacon_chain.store_migrator.process_reconstruction();
|
||||
}
|
||||
|
||||
Ok(beacon_chain)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user