-
Notifications
You must be signed in to change notification settings - Fork 932
Persist light client bootstrap #5915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 60 commits
914bb1e
c308768
e332b00
3a93933
b9bdd88
9671423
5861417
6e0fa26
c8af8fb
45a5d8d
2545655
3029f2c
fad083a
49a7e61
00d4fd0
4b8683c
6f12133
9464518
22e9201
1946100
2b1e47d
211601c
891d677
d729fea
58f178a
fe2b43d
1774e63
af9b20d
d82226b
ce765f3
4a9b088
7a0fdae
93a97ea
d8e6891
bfd6ca2
fa7cd25
bb69c48
fa50339
89ae007
7fcf032
b4c5775
0fdf544
d983c3b
8bf1bc6
f70b93e
eb19b0d
bcda61b
e8691db
50a29c5
194bf5a
6458ec9
206a629
807ec80
8c1f980
8ef297b
e75b84f
de331f0
35c5453
091bff1
dddc5a8
d5eab8c
c42c580
8686eb2
ea0e0c4
430b00b
5646203
a202eac
a62e3c8
f66aa7c
b9c3f44
d8822a0
82a57b5
e141c80
521d5f2
1873484
05b6426
2221562
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| 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}; | ||
|
|
@@ -10,14 +11,16 @@ 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 +31,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 +43,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 +55,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 +101,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 +119,19 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> { | |
| attested_block.slot(), | ||
| )?; | ||
|
|
||
| let finalized_period = cached_parts | ||
| .finalized_checkpoint | ||
| .epoch | ||
| .sync_committee_period(chain_spec)?; | ||
|
|
||
| self.store_current_sync_committee_branch( | ||
| &store, | ||
| &cached_parts, | ||
| attested_block.message().tree_hash_root(), | ||
| )?; | ||
|
|
||
| self.store_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,24 +200,13 @@ 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 | ||
| }; | ||
|
|
@@ -207,7 +218,52 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> { | |
| Ok(()) | ||
| } | ||
|
|
||
| fn store_light_client_update( | ||
| fn store_current_sync_committee_branch( | ||
| &self, | ||
| store: &BeaconStore<T>, | ||
| cached_parts: &LightClientCachedData<T::EthSpec>, | ||
| block_root: Hash256, | ||
| ) -> Result<(), BeaconChainError> { | ||
| let column = DBColumn::SyncCommitteeBranch; | ||
| store.hot_db.put_bytes( | ||
| column.into(), | ||
| &block_root.as_ssz_bytes(), | ||
| &cached_parts.current_sync_committee_branch.as_ssz_bytes(), | ||
| )?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn store_sync_committee( | ||
| &self, | ||
| store: &BeaconStore<T>, | ||
| cached_parts: &LightClientCachedData<T::EthSpec>, | ||
| sync_committee_period: u64, | ||
| finalized_period: u64, | ||
| ) -> Result<(), BeaconChainError> { | ||
| 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(()); | ||
| } | ||
| }; | ||
|
|
||
| let column = DBColumn::SyncCommittee; | ||
|
|
||
| if finalized_period >= sync_committee_period - 1 { | ||
| store.hot_db.put_bytes( | ||
| column.into(), | ||
| &sync_committee_period.to_le_bytes(), | ||
| &cached_parts.current_sync_committee.as_ssz_bytes(), | ||
| )?; | ||
| *self.latest_written_current_sync_committee.write() = | ||
| Some(cached_parts.current_sync_committee.clone()); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn store_light_client_update( | ||
| &self, | ||
| store: &BeaconStore<T>, | ||
| sync_committee_period: u64, | ||
|
|
@@ -226,9 +282,11 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> { | |
| 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>, | ||
|
|
@@ -340,6 +398,111 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> { | |
| pub fn get_latest_optimistic_update(&self) -> Option<LightClientOptimisticUpdate<T::EthSpec>> { | ||
| self.latest_optimistic_update.read().clone() | ||
| } | ||
|
|
||
| /// Fetches a `sync_committee_branch` for a given `block_root` | ||
| /// | ||
| /// Note: | ||
| pub fn get_sync_committee_branch( | ||
| &self, | ||
| store: &BeaconStore<T>, | ||
| block_root: &Hash256, | ||
| ) -> Result<Option<FixedVector<Hash256, CurrentSyncCommitteeProofLen>>, BeaconChainError> { | ||
| let column = DBColumn::SyncCommitteeBranch; | ||
|
|
||
| if let Some(bytes) = store | ||
| .hot_db | ||
| .get_bytes(column.into(), &block_root.as_ssz_bytes())? | ||
| { | ||
| let sync_committee_branch: FixedVector<Hash256, CurrentSyncCommitteeProofLen> = | ||
| FixedVector::from_ssz_bytes(&bytes) | ||
| .map_err(store::errors::Error::SszDecodeError)?; | ||
|
|
||
| return Ok(Some(sync_committee_branch)); | ||
| } | ||
|
|
||
| Ok(None) | ||
| } | ||
|
|
||
| pub fn get_sync_committee( | ||
|
||
| &self, | ||
| store: &BeaconStore<T>, | ||
| sync_committee_period: u64, | ||
| ) -> Result<Option<SyncCommittee<T::EthSpec>>, BeaconChainError> { | ||
| let column = DBColumn::SyncCommittee; | ||
|
|
||
| if let Some(bytes) = store | ||
| .hot_db | ||
| .get_bytes(column.into(), &sync_committee_period.as_ssz_bytes())? | ||
| { | ||
| let sync_committee: SyncCommittee<T::EthSpec> = | ||
| SyncCommittee::from_ssz_bytes(&bytes) | ||
| .map_err(store::errors::Error::SszDecodeError)?; | ||
| return Ok(Some(sync_committee)); | ||
| } | ||
|
|
||
| Ok(None) | ||
| } | ||
|
|
||
| /// 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) = | ||
| self.get_sync_committee_branch(store, 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) = self.get_sync_committee(store, sync_committee_period)? | ||
|
||
| else { | ||
| return Err(BeaconChainError::LightClientBootstrapError(format!( | ||
| "Sync committee for block root {block_root} 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 +513,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, | ||
| }) | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.