mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-29 20:27:14 +00:00
Persist light client bootstrap (#5915)
* persist light client updates * update beacon chain to serve light client updates * resolve todos * cache best update * extend cache parts * is better light client update * resolve merge conflict * initial api changes * add lc update db column * fmt * added tests * add sim * Merge branch 'unstable' of https://github.com/sigp/lighthouse into persist-light-client-updates * fix some weird issues with the simulator * tests * Merge branch 'unstable' of https://github.com/sigp/lighthouse into persist-light-client-updates * test changes * merge conflict * testing * started work on ef tests and some code clean up * update tests * linting * noop pre altair, were still failing on electra though * allow for zeroed light client header * Merge branch 'unstable' of https://github.com/sigp/lighthouse into persist-light-client-updates * merge unstable * remove unwraps * remove unwraps * fetch bootstrap without always querying for state * storing bootstrap parts in db * mroe code cleanup * test * prune sync committee branches from dropped chains * Update light_client_update.rs * merge unstable * move functionality to helper methods * refactor is best update fn * refactor is best update fn * improve organization of light client server cache logic * fork diget calc, and only spawn as many blcoks as we need for the lc update test * resovle merge conflict * add electra bootstrap logic, add logic to cache current sync committee * add latest sync committe branch cache * fetch lc update from the cache if it exists * fmt * Fix beacon_chain tests * Add debug code to update ranking_order ef test * Fix compare code * merge conflicts * merge conflict * add better error messaging * resolve merge conflicts * remove lc update from basicsim * rename sync comittte variable and fix persist condition * refactor get_light_client_update logic * add better comments, return helpful error messages over http and rpc * pruning canonical non checkpoint slots * fix test * rerun test * update pruning logic, add tests * fix tests * fix imports * fmt * refactor db code * Refactor db method * Refactor db method * add additional comments * Merge branch 'unstable' of https://github.com/sigp/lighthouse into persist-light-client-bootstrap * fix merge * linting * merge conflict * prevent overflow * enable lc server for http api tests * fix tests * remove prints * remove warning * revert change
This commit is contained in:
@@ -6987,32 +6987,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
) -> Result<Option<(LightClientBootstrap<T::EthSpec>, ForkName)>, Error> {
|
||||
let Some(block) = self.get_blinded_block(block_root)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let head_state = &self.head().snapshot.beacon_state;
|
||||
let finalized_period = head_state
|
||||
.finalized_checkpoint()
|
||||
.epoch
|
||||
.sync_committee_period(&self.spec)?;
|
||||
|
||||
let (state_root, slot) = (block.state_root(), block.slot());
|
||||
|
||||
let Some(mut state) = self.get_state(&state_root, Some(slot))? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let fork_name = state
|
||||
.fork_name(&self.spec)
|
||||
.map_err(Error::InconsistentFork)?;
|
||||
|
||||
match fork_name {
|
||||
ForkName::Altair
|
||||
| ForkName::Bellatrix
|
||||
| ForkName::Capella
|
||||
| ForkName::Deneb
|
||||
| ForkName::Electra => {
|
||||
LightClientBootstrap::from_beacon_state(&mut state, &block, &self.spec)
|
||||
.map(|bootstrap| Some((bootstrap, fork_name)))
|
||||
.map_err(Error::LightClientError)
|
||||
}
|
||||
ForkName::Base => Err(Error::UnsupportedFork),
|
||||
}
|
||||
self.light_client_server_cache.get_light_client_bootstrap(
|
||||
&self.store,
|
||||
block_root,
|
||||
finalized_period,
|
||||
&self.spec,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn metrics(&self) -> BeaconChainMetrics {
|
||||
|
||||
@@ -216,7 +216,8 @@ pub enum BeaconChainError {
|
||||
UnableToPublish,
|
||||
UnableToBuildColumnSidecar(String),
|
||||
AvailabilityCheckError(AvailabilityCheckError),
|
||||
LightClientError(LightClientError),
|
||||
LightClientUpdateError(LightClientUpdateError),
|
||||
LightClientBootstrapError(String),
|
||||
UnsupportedFork,
|
||||
MilhouseError(MilhouseError),
|
||||
EmptyRpcCustodyColumns,
|
||||
@@ -250,7 +251,7 @@ easy_from_to!(BlockReplayError, BeaconChainError);
|
||||
easy_from_to!(InconsistentFork, BeaconChainError);
|
||||
easy_from_to!(AvailabilityCheckError, BeaconChainError);
|
||||
easy_from_to!(EpochCacheError, BeaconChainError);
|
||||
easy_from_to!(LightClientError, BeaconChainError);
|
||||
easy_from_to!(LightClientUpdateError, BeaconChainError);
|
||||
easy_from_to!(MilhouseError, BeaconChainError);
|
||||
easy_from_to!(AttestationError, BeaconChainError);
|
||||
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
use crate::errors::BeaconChainError;
|
||||
use crate::{metrics, BeaconChainTypes, BeaconStore};
|
||||
use eth2::types::light_client_update::CurrentSyncCommitteeProofLen;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use safe_arith::SafeArith;
|
||||
use slog::{debug, Logger};
|
||||
use ssz::Decode;
|
||||
use ssz::Encode;
|
||||
use ssz_types::FixedVector;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::Arc;
|
||||
use store::DBColumn;
|
||||
use store::KeyValueStore;
|
||||
use tree_hash::TreeHash;
|
||||
use types::light_client_update::{
|
||||
FinalizedRootProofLen, NextSyncCommitteeProofLen, FINALIZED_ROOT_INDEX,
|
||||
NEXT_SYNC_COMMITTEE_INDEX,
|
||||
FinalizedRootProofLen, NextSyncCommitteeProofLen, CURRENT_SYNC_COMMITTEE_INDEX,
|
||||
FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_INDEX,
|
||||
};
|
||||
use types::non_zero_usize::new_non_zero_usize;
|
||||
use types::{
|
||||
BeaconBlockRef, BeaconState, ChainSpec, EthSpec, ForkName, Hash256, LightClientFinalityUpdate,
|
||||
LightClientOptimisticUpdate, LightClientUpdate, Slot, SyncAggregate, SyncCommittee,
|
||||
BeaconBlockRef, BeaconState, ChainSpec, Checkpoint, EthSpec, ForkName, Hash256,
|
||||
LightClientBootstrap, LightClientFinalityUpdate, LightClientOptimisticUpdate,
|
||||
LightClientUpdate, Slot, SyncAggregate, SyncCommittee,
|
||||
};
|
||||
|
||||
/// A prev block cache miss requires to re-generate the state of the post-parent block. Items in the
|
||||
@@ -28,7 +30,6 @@ const PREV_BLOCK_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(32);
|
||||
/// This cache computes light client messages ahead of time, required to satisfy p2p and API
|
||||
/// requests. These messages include proofs on historical states, so on-demand computation is
|
||||
/// expensive.
|
||||
///
|
||||
pub struct LightClientServerCache<T: BeaconChainTypes> {
|
||||
/// Tracks a single global latest finality update out of all imported blocks.
|
||||
///
|
||||
@@ -41,6 +42,8 @@ pub struct LightClientServerCache<T: BeaconChainTypes> {
|
||||
latest_optimistic_update: RwLock<Option<LightClientOptimisticUpdate<T::EthSpec>>>,
|
||||
/// Caches the most recent light client update
|
||||
latest_light_client_update: RwLock<Option<LightClientUpdate<T::EthSpec>>>,
|
||||
/// Caches the current sync committee,
|
||||
latest_written_current_sync_committee: RwLock<Option<Arc<SyncCommittee<T::EthSpec>>>>,
|
||||
/// Caches state proofs by block root
|
||||
prev_block_cache: Mutex<lru::LruCache<Hash256, LightClientCachedData<T::EthSpec>>>,
|
||||
}
|
||||
@@ -51,6 +54,7 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
|
||||
latest_finality_update: None.into(),
|
||||
latest_optimistic_update: None.into(),
|
||||
latest_light_client_update: None.into(),
|
||||
latest_written_current_sync_committee: None.into(),
|
||||
prev_block_cache: lru::LruCache::new(PREV_BLOCK_CACHE_SIZE).into(),
|
||||
}
|
||||
}
|
||||
@@ -96,6 +100,10 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
|
||||
let signature_slot = block_slot;
|
||||
let attested_block_root = block_parent_root;
|
||||
|
||||
let sync_period = block_slot
|
||||
.epoch(T::EthSpec::slots_per_epoch())
|
||||
.sync_committee_period(chain_spec)?;
|
||||
|
||||
let attested_block = store.get_blinded_block(attested_block_root)?.ok_or(
|
||||
BeaconChainError::DBInconsistent(format!(
|
||||
"Block not available {:?}",
|
||||
@@ -110,6 +118,18 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
|
||||
attested_block.slot(),
|
||||
)?;
|
||||
|
||||
let finalized_period = cached_parts
|
||||
.finalized_checkpoint
|
||||
.epoch
|
||||
.sync_committee_period(chain_spec)?;
|
||||
|
||||
store.store_sync_committee_branch(
|
||||
attested_block.message().tree_hash_root(),
|
||||
&cached_parts.current_sync_committee_branch,
|
||||
)?;
|
||||
|
||||
self.store_current_sync_committee(&store, &cached_parts, sync_period, finalized_period)?;
|
||||
|
||||
let attested_slot = attested_block.slot();
|
||||
|
||||
let maybe_finalized_block = store.get_blinded_block(&cached_parts.finalized_block_root)?;
|
||||
@@ -178,57 +198,57 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
|
||||
|
||||
// Spec: Full nodes SHOULD provide the best derivable LightClientUpdate (according to is_better_update)
|
||||
// for each sync committee period
|
||||
let prev_light_client_update = match &self.latest_light_client_update.read().clone() {
|
||||
Some(prev_light_client_update) => Some(prev_light_client_update.clone()),
|
||||
None => self.get_light_client_update(&store, sync_period, chain_spec)?,
|
||||
};
|
||||
let prev_light_client_update =
|
||||
self.get_light_client_update(&store, sync_period, chain_spec)?;
|
||||
|
||||
let should_persist_light_client_update =
|
||||
if let Some(prev_light_client_update) = prev_light_client_update {
|
||||
let prev_sync_period = prev_light_client_update
|
||||
.signature_slot()
|
||||
.epoch(T::EthSpec::slots_per_epoch())
|
||||
.sync_committee_period(chain_spec)?;
|
||||
|
||||
if sync_period != prev_sync_period {
|
||||
true
|
||||
} else {
|
||||
prev_light_client_update
|
||||
.is_better_light_client_update(&new_light_client_update, chain_spec)?
|
||||
}
|
||||
prev_light_client_update
|
||||
.is_better_light_client_update(&new_light_client_update, chain_spec)?
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if should_persist_light_client_update {
|
||||
self.store_light_client_update(&store, sync_period, &new_light_client_update)?;
|
||||
store.store_light_client_update(sync_period, &new_light_client_update)?;
|
||||
*self.latest_light_client_update.write() = Some(new_light_client_update);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn store_light_client_update(
|
||||
fn store_current_sync_committee(
|
||||
&self,
|
||||
store: &BeaconStore<T>,
|
||||
cached_parts: &LightClientCachedData<T::EthSpec>,
|
||||
sync_committee_period: u64,
|
||||
light_client_update: &LightClientUpdate<T::EthSpec>,
|
||||
finalized_period: u64,
|
||||
) -> Result<(), BeaconChainError> {
|
||||
let column = DBColumn::LightClientUpdate;
|
||||
if let Some(latest_sync_committee) =
|
||||
self.latest_written_current_sync_committee.read().clone()
|
||||
{
|
||||
if latest_sync_committee == cached_parts.current_sync_committee {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
store.hot_db.put_bytes(
|
||||
column.into(),
|
||||
&sync_committee_period.to_le_bytes(),
|
||||
&light_client_update.as_ssz_bytes(),
|
||||
)?;
|
||||
|
||||
*self.latest_light_client_update.write() = Some(light_client_update.clone());
|
||||
if finalized_period + 1 >= sync_committee_period {
|
||||
store.store_sync_committee(
|
||||
sync_committee_period,
|
||||
&cached_parts.current_sync_committee,
|
||||
)?;
|
||||
*self.latest_written_current_sync_committee.write() =
|
||||
Some(cached_parts.current_sync_committee.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Used to fetch the most recently persisted "best" light client update.
|
||||
// Should not be used outside the light client server, as it also caches the fetched
|
||||
// light client update.
|
||||
/// Used to fetch the most recently persisted light client update for the given `sync_committee_period`.
|
||||
/// It first checks the `latest_light_client_update` cache before querying the db.
|
||||
///
|
||||
/// Note: Should not be used outside the light client server, as it also caches the fetched
|
||||
/// light client update.
|
||||
fn get_light_client_update(
|
||||
&self,
|
||||
store: &BeaconStore<T>,
|
||||
@@ -245,21 +265,7 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
|
||||
}
|
||||
}
|
||||
|
||||
let column = DBColumn::LightClientUpdate;
|
||||
let res = store
|
||||
.hot_db
|
||||
.get_bytes(column.into(), &sync_committee_period.to_le_bytes())?;
|
||||
|
||||
if let Some(light_client_update_bytes) = res {
|
||||
let epoch = sync_committee_period
|
||||
.safe_mul(chain_spec.epochs_per_sync_committee_period.into())?;
|
||||
|
||||
let fork_name = chain_spec.fork_name_at_epoch(epoch.into());
|
||||
|
||||
let light_client_update =
|
||||
LightClientUpdate::from_ssz_bytes(&light_client_update_bytes, &fork_name)
|
||||
.map_err(store::errors::Error::SszDecodeError)?;
|
||||
|
||||
if let Some(light_client_update) = store.get_light_client_update(sync_committee_period)? {
|
||||
*self.latest_light_client_update.write() = Some(light_client_update.clone());
|
||||
return Ok(Some(light_client_update));
|
||||
}
|
||||
@@ -340,6 +346,65 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
|
||||
pub fn get_latest_optimistic_update(&self) -> Option<LightClientOptimisticUpdate<T::EthSpec>> {
|
||||
self.latest_optimistic_update.read().clone()
|
||||
}
|
||||
|
||||
/// Fetches a light client bootstrap for a given finalized checkpoint `block_root`. We eagerly persist
|
||||
/// `sync_committee_branch and `sync_committee` to allow for a more efficient bootstrap construction.
|
||||
///
|
||||
/// Note: It should be the case that a `sync_committee_branch` and `sync_committee` exist in the db
|
||||
/// for a finalized checkpoint block root. However, we currently have no backfill mechanism for these values.
|
||||
/// Therefore, `sync_committee_branch` and `sync_committee` are only persisted while a node is synced.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn get_light_client_bootstrap(
|
||||
&self,
|
||||
store: &BeaconStore<T>,
|
||||
block_root: &Hash256,
|
||||
finalized_period: u64,
|
||||
chain_spec: &ChainSpec,
|
||||
) -> Result<Option<(LightClientBootstrap<T::EthSpec>, ForkName)>, BeaconChainError> {
|
||||
let Some(block) = store.get_blinded_block(block_root)? else {
|
||||
return Err(BeaconChainError::LightClientBootstrapError(format!(
|
||||
"Block root {block_root} not found"
|
||||
)));
|
||||
};
|
||||
|
||||
let (_, slot) = (block.state_root(), block.slot());
|
||||
|
||||
let fork_name = chain_spec.fork_name_at_slot::<T::EthSpec>(slot);
|
||||
|
||||
let sync_committee_period = block
|
||||
.slot()
|
||||
.epoch(T::EthSpec::slots_per_epoch())
|
||||
.sync_committee_period(chain_spec)?;
|
||||
|
||||
let Some(current_sync_committee_branch) = store.get_sync_committee_branch(block_root)?
|
||||
else {
|
||||
return Err(BeaconChainError::LightClientBootstrapError(format!(
|
||||
"Sync committee branch for block root {:?} not found",
|
||||
block_root
|
||||
)));
|
||||
};
|
||||
|
||||
if sync_committee_period > finalized_period {
|
||||
return Err(BeaconChainError::LightClientBootstrapError(
|
||||
format!("The blocks sync committee period {sync_committee_period} is greater than the current finalized period {finalized_period}"),
|
||||
));
|
||||
}
|
||||
|
||||
let Some(current_sync_committee) = store.get_sync_committee(sync_committee_period)? else {
|
||||
return Err(BeaconChainError::LightClientBootstrapError(format!(
|
||||
"Sync committee for period {sync_committee_period} not found"
|
||||
)));
|
||||
};
|
||||
|
||||
let light_client_bootstrap = LightClientBootstrap::new(
|
||||
&block,
|
||||
Arc::new(current_sync_committee),
|
||||
current_sync_committee_branch,
|
||||
chain_spec,
|
||||
)?;
|
||||
|
||||
Ok(Some((light_client_bootstrap, fork_name)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> Default for LightClientServerCache<T> {
|
||||
@@ -350,23 +415,32 @@ impl<T: BeaconChainTypes> Default for LightClientServerCache<T> {
|
||||
|
||||
type FinalityBranch = FixedVector<Hash256, FinalizedRootProofLen>;
|
||||
type NextSyncCommitteeBranch = FixedVector<Hash256, NextSyncCommitteeProofLen>;
|
||||
type CurrentSyncCommitteeBranch = FixedVector<Hash256, CurrentSyncCommitteeProofLen>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LightClientCachedData<E: EthSpec> {
|
||||
finalized_checkpoint: Checkpoint,
|
||||
finality_branch: FinalityBranch,
|
||||
next_sync_committee_branch: NextSyncCommitteeBranch,
|
||||
current_sync_committee_branch: CurrentSyncCommitteeBranch,
|
||||
next_sync_committee: Arc<SyncCommittee<E>>,
|
||||
current_sync_committee: Arc<SyncCommittee<E>>,
|
||||
finalized_block_root: Hash256,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> LightClientCachedData<E> {
|
||||
fn from_state(state: &mut BeaconState<E>) -> Result<Self, BeaconChainError> {
|
||||
Ok(Self {
|
||||
finalized_checkpoint: state.finalized_checkpoint(),
|
||||
finality_branch: state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?.into(),
|
||||
next_sync_committee: state.next_sync_committee()?.clone(),
|
||||
current_sync_committee: state.current_sync_committee()?.clone(),
|
||||
next_sync_committee_branch: state
|
||||
.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?
|
||||
.into(),
|
||||
current_sync_committee_branch: state
|
||||
.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?
|
||||
.into(),
|
||||
finalized_block_root: state.finalized_checkpoint().root,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -676,6 +676,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
|
||||
StoreOp::DeleteBlock(block_root),
|
||||
StoreOp::DeleteExecutionPayload(block_root),
|
||||
StoreOp::DeleteBlobs(block_root),
|
||||
StoreOp::DeleteSyncCommitteeBranch(block_root),
|
||||
]
|
||||
})
|
||||
.chain(
|
||||
|
||||
@@ -143,6 +143,12 @@ pub enum SyncCommitteeStrategy {
|
||||
NoValidators,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LightClientStrategy {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// Indicates whether the `BeaconChainHarness` should use the `state.current_sync_committee` or
|
||||
/// `state.next_sync_committee` when creating sync messages or contributions.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -2224,6 +2230,96 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
fn update_light_client_server_cache(
|
||||
&self,
|
||||
state: &BeaconState<E>,
|
||||
slot: Slot,
|
||||
block_root: Hash256,
|
||||
) {
|
||||
let fork_name = state.fork_name(&self.spec).unwrap();
|
||||
if !fork_name.altair_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let log = self.logger();
|
||||
let contributions =
|
||||
self.make_sync_contributions(state, block_root, slot, RelativeSyncCommittee::Current);
|
||||
|
||||
for (_, contribution_and_proof) in contributions {
|
||||
let Some(contribution_and_proof) = contribution_and_proof else {
|
||||
continue;
|
||||
};
|
||||
let contribution = contribution_and_proof.message.contribution;
|
||||
self.chain
|
||||
.op_pool
|
||||
.insert_sync_contribution(contribution.clone())
|
||||
.unwrap();
|
||||
self.chain
|
||||
.op_pool
|
||||
.insert_sync_contribution(contribution)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let Some(sync_aggregate) = self.chain.op_pool.get_sync_aggregate(state).unwrap() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let _ = self
|
||||
.chain
|
||||
.light_client_server_cache
|
||||
.recompute_and_cache_updates(
|
||||
self.chain.store.clone(),
|
||||
slot,
|
||||
&block_root,
|
||||
&sync_aggregate,
|
||||
log,
|
||||
&self.spec,
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn add_attested_blocks_at_slots_with_lc_data(
|
||||
&self,
|
||||
mut state: BeaconState<E>,
|
||||
state_root: Hash256,
|
||||
slots: &[Slot],
|
||||
validators: &[usize],
|
||||
mut latest_block_hash: Option<SignedBeaconBlockHash>,
|
||||
sync_committee_strategy: SyncCommitteeStrategy,
|
||||
) -> AddBlocksResult<E> {
|
||||
assert!(
|
||||
slots.windows(2).all(|w| w[0] <= w[1]),
|
||||
"Slots have to be sorted"
|
||||
); // slice.is_sorted() isn't stabilized at the moment of writing this
|
||||
let mut block_hash_from_slot: HashMap<Slot, SignedBeaconBlockHash> = HashMap::new();
|
||||
let mut state_hash_from_slot: HashMap<Slot, BeaconStateHash> = HashMap::new();
|
||||
for slot in slots {
|
||||
let (block_hash, new_state) = self
|
||||
.add_attested_block_at_slot_with_sync(
|
||||
*slot,
|
||||
state,
|
||||
state_root,
|
||||
validators,
|
||||
sync_committee_strategy,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
state = new_state;
|
||||
|
||||
self.update_light_client_server_cache(&state, *slot, block_hash.into());
|
||||
|
||||
block_hash_from_slot.insert(*slot, block_hash);
|
||||
state_hash_from_slot.insert(*slot, state.canonical_root().unwrap().into());
|
||||
latest_block_hash = Some(block_hash);
|
||||
}
|
||||
(
|
||||
block_hash_from_slot,
|
||||
state_hash_from_slot,
|
||||
latest_block_hash.unwrap(),
|
||||
state,
|
||||
)
|
||||
}
|
||||
|
||||
async fn add_attested_blocks_at_slots_given_lbh(
|
||||
&self,
|
||||
mut state: BeaconState<E>,
|
||||
@@ -2250,7 +2346,9 @@ where
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
state = new_state;
|
||||
|
||||
block_hash_from_slot.insert(*slot, block_hash);
|
||||
state_hash_from_slot.insert(*slot, state.canonical_root().unwrap().into());
|
||||
latest_block_hash = Some(block_hash);
|
||||
@@ -2459,6 +2557,23 @@ where
|
||||
block_strategy,
|
||||
attestation_strategy,
|
||||
SyncCommitteeStrategy::NoValidators,
|
||||
LightClientStrategy::Disabled,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn extend_chain_with_light_client_data(
|
||||
&self,
|
||||
num_blocks: usize,
|
||||
block_strategy: BlockStrategy,
|
||||
attestation_strategy: AttestationStrategy,
|
||||
) -> Hash256 {
|
||||
self.extend_chain_with_sync(
|
||||
num_blocks,
|
||||
block_strategy,
|
||||
attestation_strategy,
|
||||
SyncCommitteeStrategy::NoValidators,
|
||||
LightClientStrategy::Enabled,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -2469,6 +2584,7 @@ where
|
||||
block_strategy: BlockStrategy,
|
||||
attestation_strategy: AttestationStrategy,
|
||||
sync_committee_strategy: SyncCommitteeStrategy,
|
||||
light_client_strategy: LightClientStrategy,
|
||||
) -> Hash256 {
|
||||
let (mut state, slots) = match block_strategy {
|
||||
BlockStrategy::OnCanonicalHead => {
|
||||
@@ -2500,15 +2616,30 @@ where
|
||||
};
|
||||
|
||||
let state_root = state.update_tree_hash_cache().unwrap();
|
||||
let (_, _, last_produced_block_hash, _) = self
|
||||
.add_attested_blocks_at_slots_with_sync(
|
||||
state,
|
||||
state_root,
|
||||
&slots,
|
||||
&validators,
|
||||
sync_committee_strategy,
|
||||
)
|
||||
.await;
|
||||
let (_, _, last_produced_block_hash, _) = match light_client_strategy {
|
||||
LightClientStrategy::Enabled => {
|
||||
self.add_attested_blocks_at_slots_with_lc_data(
|
||||
state,
|
||||
state_root,
|
||||
&slots,
|
||||
&validators,
|
||||
None,
|
||||
sync_committee_strategy,
|
||||
)
|
||||
.await
|
||||
}
|
||||
LightClientStrategy::Disabled => {
|
||||
self.add_attested_blocks_at_slots_with_sync(
|
||||
state,
|
||||
state_root,
|
||||
&slots,
|
||||
&validators,
|
||||
sync_committee_strategy,
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
||||
last_produced_block_hash.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use beacon_chain::block_verification_types::RpcBlock;
|
||||
use beacon_chain::builder::BeaconChainBuilder;
|
||||
use beacon_chain::data_availability_checker::AvailableBlock;
|
||||
use beacon_chain::schema_change::migrate_schema;
|
||||
use beacon_chain::test_utils::RelativeSyncCommittee;
|
||||
use beacon_chain::test_utils::SyncCommitteeStrategy;
|
||||
use beacon_chain::test_utils::{
|
||||
mock_execution_layer_from_parts, test_spec, AttestationStrategy, BeaconChainHarness,
|
||||
BlockStrategy, DiskHarnessType, KZG,
|
||||
@@ -104,6 +104,142 @@ fn get_harness_generic(
|
||||
harness
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn light_client_bootstrap_test() {
|
||||
let spec = test_spec::<E>();
|
||||
let Some(_) = spec.altair_fork_epoch else {
|
||||
// No-op prior to Altair.
|
||||
return;
|
||||
};
|
||||
|
||||
let checkpoint_slot = Slot::new(E::slots_per_epoch() * 6);
|
||||
let db_path = tempdir().unwrap();
|
||||
let log = test_logger();
|
||||
|
||||
let seconds_per_slot = spec.seconds_per_slot;
|
||||
let store = get_store_generic(
|
||||
&db_path,
|
||||
StoreConfig {
|
||||
slots_per_restore_point: 2 * E::slots_per_epoch(),
|
||||
..Default::default()
|
||||
},
|
||||
test_spec::<E>(),
|
||||
);
|
||||
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
|
||||
let all_validators = (0..LOW_VALIDATOR_COUNT).collect::<Vec<_>>();
|
||||
let num_initial_slots = E::slots_per_epoch() * 7;
|
||||
let slots: Vec<Slot> = (1..num_initial_slots).map(Slot::new).collect();
|
||||
|
||||
let (genesis_state, genesis_state_root) = harness.get_current_state_and_root();
|
||||
harness
|
||||
.add_attested_blocks_at_slots_with_lc_data(
|
||||
genesis_state.clone(),
|
||||
genesis_state_root,
|
||||
&slots,
|
||||
&all_validators,
|
||||
None,
|
||||
SyncCommitteeStrategy::NoValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
let wss_block_root = harness
|
||||
.chain
|
||||
.block_root_at_slot(checkpoint_slot, WhenSlotSkipped::Prev)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let wss_state_root = harness
|
||||
.chain
|
||||
.state_root_at_slot(checkpoint_slot)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let wss_block = harness
|
||||
.chain
|
||||
.store
|
||||
.get_full_block(&wss_block_root)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let wss_blobs_opt = harness.chain.store.get_blobs(&wss_block_root).unwrap();
|
||||
let wss_state = store
|
||||
.get_state(&wss_state_root, Some(checkpoint_slot))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let kzg = spec.deneb_fork_epoch.map(|_| KZG.clone());
|
||||
|
||||
let mock =
|
||||
mock_execution_layer_from_parts(&harness.spec, harness.runtime.task_executor.clone());
|
||||
|
||||
// Initialise a new beacon chain from the finalized checkpoint.
|
||||
// The slot clock must be set to a time ahead of the checkpoint state.
|
||||
let slot_clock = TestingSlotClock::new(
|
||||
Slot::new(0),
|
||||
Duration::from_secs(harness.chain.genesis_time),
|
||||
Duration::from_secs(seconds_per_slot),
|
||||
);
|
||||
slot_clock.set_slot(harness.get_current_slot().as_u64());
|
||||
|
||||
let (shutdown_tx, _shutdown_rx) = futures::channel::mpsc::channel(1);
|
||||
|
||||
let beacon_chain = BeaconChainBuilder::<DiskHarnessType<E>>::new(MinimalEthSpec)
|
||||
.store(store.clone())
|
||||
.custom_spec(test_spec::<E>())
|
||||
.task_executor(harness.chain.task_executor.clone())
|
||||
.logger(log.clone())
|
||||
.weak_subjectivity_state(
|
||||
wss_state,
|
||||
wss_block.clone(),
|
||||
wss_blobs_opt.clone(),
|
||||
genesis_state,
|
||||
)
|
||||
.unwrap()
|
||||
.store_migrator_config(MigratorConfig::default().blocking())
|
||||
.dummy_eth1_backend()
|
||||
.expect("should build dummy backend")
|
||||
.slot_clock(slot_clock)
|
||||
.shutdown_sender(shutdown_tx)
|
||||
.chain_config(ChainConfig::default())
|
||||
.event_handler(Some(ServerSentEventHandler::new_with_capacity(
|
||||
log.clone(),
|
||||
1,
|
||||
)))
|
||||
.execution_layer(Some(mock.el))
|
||||
.kzg(kzg)
|
||||
.build()
|
||||
.expect("should build");
|
||||
|
||||
let current_state = harness.get_current_state();
|
||||
|
||||
if ForkName::Electra == current_state.fork_name_unchecked() {
|
||||
// TODO(electra) fix beacon state `compute_merkle_proof`
|
||||
return;
|
||||
}
|
||||
|
||||
let finalized_checkpoint = beacon_chain
|
||||
.canonical_head
|
||||
.cached_head()
|
||||
.finalized_checkpoint();
|
||||
|
||||
let block_root = finalized_checkpoint.root;
|
||||
|
||||
let (lc_bootstrap, _) = harness
|
||||
.chain
|
||||
.get_light_client_bootstrap(&block_root)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let bootstrap_slot = match lc_bootstrap {
|
||||
LightClientBootstrap::Altair(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
|
||||
LightClientBootstrap::Capella(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
|
||||
LightClientBootstrap::Deneb(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
|
||||
LightClientBootstrap::Electra(lc_bootstrap) => lc_bootstrap.header.beacon.slot,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
bootstrap_slot.epoch(E::slots_per_epoch()),
|
||||
finalized_checkpoint.epoch
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn light_client_updates_test() {
|
||||
let spec = test_spec::<E>();
|
||||
@@ -170,7 +306,7 @@ async fn light_client_updates_test() {
|
||||
|
||||
harness.advance_slot();
|
||||
harness
|
||||
.extend_chain(
|
||||
.extend_chain_with_light_client_data(
|
||||
num_final_blocks as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
@@ -224,53 +360,6 @@ async fn light_client_updates_test() {
|
||||
return;
|
||||
}
|
||||
|
||||
let block_root = *current_state
|
||||
.get_block_root(current_state.slot() - Slot::new(1))
|
||||
.unwrap();
|
||||
|
||||
let contributions = harness.make_sync_contributions(
|
||||
¤t_state,
|
||||
block_root,
|
||||
current_state.slot() - Slot::new(1),
|
||||
RelativeSyncCommittee::Current,
|
||||
);
|
||||
|
||||
// generate sync aggregates
|
||||
for (_, contribution_and_proof) in contributions {
|
||||
let contribution = contribution_and_proof
|
||||
.expect("contribution exists for committee")
|
||||
.message
|
||||
.contribution;
|
||||
beacon_chain
|
||||
.op_pool
|
||||
.insert_sync_contribution(contribution.clone())
|
||||
.unwrap();
|
||||
beacon_chain
|
||||
.op_pool
|
||||
.insert_sync_contribution(contribution)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// check that we can fetch the newly generated sync aggregate
|
||||
let sync_aggregate = beacon_chain
|
||||
.op_pool
|
||||
.get_sync_aggregate(¤t_state)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// cache light client data
|
||||
beacon_chain
|
||||
.light_client_server_cache
|
||||
.recompute_and_cache_updates(
|
||||
store.clone(),
|
||||
current_state.slot() - Slot::new(1),
|
||||
&block_root,
|
||||
&sync_aggregate,
|
||||
&log,
|
||||
&spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// calculate the sync period from the previous slot
|
||||
let sync_period = (current_state.slot() - Slot::new(1))
|
||||
.epoch(E::slots_per_epoch())
|
||||
@@ -291,61 +380,13 @@ async fn light_client_updates_test() {
|
||||
}
|
||||
|
||||
harness
|
||||
.extend_chain(
|
||||
.extend_chain_with_light_client_data(
|
||||
num_final_blocks as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
let current_state = harness.get_current_state();
|
||||
|
||||
let block_root = *current_state
|
||||
.get_block_root(current_state.slot() - Slot::new(1))
|
||||
.unwrap();
|
||||
|
||||
let contributions = harness.make_sync_contributions(
|
||||
¤t_state,
|
||||
block_root,
|
||||
current_state.slot() - Slot::new(1),
|
||||
RelativeSyncCommittee::Current,
|
||||
);
|
||||
|
||||
// generate new sync aggregates from this new state
|
||||
for (_, contribution_and_proof) in contributions {
|
||||
let contribution = contribution_and_proof
|
||||
.expect("contribution exists for committee")
|
||||
.message
|
||||
.contribution;
|
||||
beacon_chain
|
||||
.op_pool
|
||||
.insert_sync_contribution(contribution.clone())
|
||||
.unwrap();
|
||||
beacon_chain
|
||||
.op_pool
|
||||
.insert_sync_contribution(contribution)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let sync_aggregate = beacon_chain
|
||||
.op_pool
|
||||
.get_sync_aggregate(¤t_state)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// cache new light client data
|
||||
beacon_chain
|
||||
.light_client_server_cache
|
||||
.recompute_and_cache_updates(
|
||||
store.clone(),
|
||||
current_state.slot() - Slot::new(1),
|
||||
&block_root,
|
||||
&sync_aggregate,
|
||||
&log,
|
||||
&spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// we should now have two light client updates in the db
|
||||
let lc_updates = beacon_chain
|
||||
.get_light_client_updates(sync_period, 100)
|
||||
|
||||
Reference in New Issue
Block a user