Skip to content
Merged
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
914bb1e
persist light client updates
eserilev Apr 2, 2024
c308768
update beacon chain to serve light client updates
eserilev Apr 2, 2024
e332b00
resolve todos
eserilev Apr 2, 2024
3a93933
cache best update
eserilev Apr 3, 2024
b9bdd88
extend cache parts
eserilev Apr 10, 2024
9671423
is better light client update
eserilev Apr 10, 2024
5861417
resolve merge conflict
eserilev Apr 10, 2024
6e0fa26
initial api changes
eserilev Apr 11, 2024
c8af8fb
add lc update db column
eserilev Apr 15, 2024
45a5d8d
fmt
eserilev Apr 16, 2024
2545655
added tests
eserilev Apr 28, 2024
3029f2c
add sim
eserilev Apr 28, 2024
fad083a
Merge branch 'unstable' of https://github.com/sigp/lighthouse into pe…
eserilev May 14, 2024
49a7e61
fix some weird issues with the simulator
eserilev May 15, 2024
00d4fd0
tests
eserilev May 27, 2024
4b8683c
Merge branch 'unstable' of https://github.com/sigp/lighthouse into pe…
eserilev May 27, 2024
6f12133
test changes
eserilev May 29, 2024
9464518
merge conflict
eserilev May 29, 2024
22e9201
testing
eserilev May 31, 2024
1946100
started work on ef tests and some code clean up
eserilev Jun 3, 2024
2b1e47d
update tests
eserilev Jun 4, 2024
211601c
linting
eserilev Jun 4, 2024
891d677
noop pre altair, were still failing on electra though
eserilev Jun 5, 2024
d729fea
allow for zeroed light client header
eserilev Jun 10, 2024
58f178a
Merge branch 'unstable' of https://github.com/sigp/lighthouse into pe…
eserilev Jun 10, 2024
fe2b43d
merge unstable
eserilev Jun 10, 2024
1774e63
remove unwraps
eserilev Jun 10, 2024
af9b20d
remove unwraps
eserilev Jun 10, 2024
d82226b
fetch bootstrap without always querying for state
eserilev Jun 11, 2024
ce765f3
storing bootstrap parts in db
eserilev Jun 12, 2024
4a9b088
mroe code cleanup
eserilev Jun 13, 2024
7a0fdae
test
eserilev Jun 17, 2024
93a97ea
prune sync committee branches from dropped chains
eserilev Jun 24, 2024
d8e6891
Update light_client_update.rs
dapplion Jun 25, 2024
bfd6ca2
merge unstable
eserilev Jun 25, 2024
fa7cd25
move functionality to helper methods
eserilev Jun 25, 2024
bb69c48
refactor is best update fn
eserilev Jun 25, 2024
fa50339
refactor is best update fn
eserilev Jun 25, 2024
89ae007
improve organization of light client server cache logic
eserilev Jun 25, 2024
7fcf032
fork diget calc, and only spawn as many blcoks as we need for the lc …
eserilev Jun 25, 2024
b4c5775
resovle merge conflict
eserilev Jun 25, 2024
0fdf544
add electra bootstrap logic, add logic to cache current sync committee
eserilev Jun 25, 2024
d983c3b
add latest sync committe branch cache
eserilev Jun 26, 2024
8bf1bc6
fetch lc update from the cache if it exists
eserilev Jun 26, 2024
f70b93e
fmt
eserilev Jun 26, 2024
eb19b0d
Fix beacon_chain tests
dapplion Jul 5, 2024
bcda61b
Add debug code to update ranking_order ef test
dapplion Jul 5, 2024
e8691db
Fix compare code
dapplion Jul 5, 2024
50a29c5
merge conflicts
eserilev Jul 26, 2024
194bf5a
merge conflict
eserilev Jul 29, 2024
6458ec9
add better error messaging
eserilev Jul 29, 2024
206a629
resolve merge conflicts
eserilev Aug 12, 2024
807ec80
remove lc update from basicsim
eserilev Aug 12, 2024
8c1f980
rename sync comittte variable and fix persist condition
eserilev Aug 13, 2024
8ef297b
refactor get_light_client_update logic
eserilev Aug 13, 2024
e75b84f
add better comments, return helpful error messages over http and rpc
eserilev Aug 13, 2024
de331f0
pruning canonical non checkpoint slots
eserilev Aug 14, 2024
35c5453
fix test
eserilev Aug 15, 2024
091bff1
rerun test
eserilev Aug 15, 2024
dddc5a8
update pruning logic, add tests
eserilev Aug 22, 2024
d5eab8c
fix tests
eserilev Aug 22, 2024
c42c580
fix imports
eserilev Aug 22, 2024
8686eb2
fmt
eserilev Aug 22, 2024
ea0e0c4
refactor db code
eserilev Aug 22, 2024
430b00b
Refactor db method
eserilev Aug 22, 2024
5646203
Refactor db method
eserilev Aug 22, 2024
a202eac
add additional comments
eserilev Aug 28, 2024
a62e3c8
Merge branch 'unstable' of https://github.com/sigp/lighthouse into pe…
eserilev Sep 5, 2024
f66aa7c
fix merge
eserilev Sep 5, 2024
b9c3f44
linting
eserilev Sep 5, 2024
d8822a0
merge conflict
eserilev Sep 6, 2024
82a57b5
prevent overflow
eserilev Sep 6, 2024
e141c80
enable lc server for http api tests
eserilev Sep 6, 2024
521d5f2
fix tests
eserilev Sep 9, 2024
1873484
remove prints
eserilev Sep 9, 2024
05b6426
remove warning
eserilev Sep 9, 2024
2221562
revert change
eserilev Sep 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 11 additions & 25 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6789,32 +6789,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 (state_root, slot) = (block.state_root(), block.slot());

let Some(mut state) = self.get_state(&state_root, Some(slot))? 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 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 {
Expand Down
5 changes: 3 additions & 2 deletions beacon_node/beacon_chain/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ pub enum BeaconChainError {
ProposerHeadForkChoiceError(fork_choice::Error<proto_array::Error>),
UnableToPublish,
AvailabilityCheckError(AvailabilityCheckError),
LightClientError(LightClientError),
LightClientUpdateError(LightClientUpdateError),
LightClientBootstrapError(String),
UnsupportedFork,
MilhouseError(MilhouseError),
AttestationError(AttestationError),
Expand Down Expand Up @@ -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);

Expand Down
174 changes: 124 additions & 50 deletions beacon_node/beacon_chain/src/light_client_server_cache.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
///
Expand All @@ -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>>>,
}
Expand All @@ -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(),
}
}
Expand Down Expand Up @@ -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 {:?}",
Expand All @@ -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)?;
Expand Down Expand Up @@ -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;

store.hot_db.put_bytes(
column.into(),
&sync_committee_period.to_le_bytes(),
&light_client_update.as_ssz_bytes(),
)?;
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(());
}
};

*self.latest_light_client_update.write() = Some(light_client_update.clone());
if finalized_period >= sync_committee_period - 1 {
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>,
Expand All @@ -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));
}
Expand Down Expand Up @@ -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 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> {
Expand All @@ -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,
})
}
Expand Down
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading