Skip to content

Commit

Permalink
Add Capella & Deneb light client support (#4946)
Browse files Browse the repository at this point in the history
* rebase and add comment

* conditional test

* test

* optimistic chould be working now

* finality should be working now

* try again

* try again

* clippy fix

* add lc bootstrap beacon api

* add lc optimistic/finality update to events

* fmt

* That error isn't occuring on my computer but I think this should fix it

* Merge branch 'unstable' into light_client_beacon_api_1

# Conflicts:
#	beacon_node/beacon_chain/src/events.rs
#	beacon_node/http_api/src/lib.rs
#	beacon_node/http_api/src/test_utils.rs
#	beacon_node/http_api/tests/main.rs
#	beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs
#	beacon_node/lighthouse_network/src/rpc/methods.rs
#	beacon_node/lighthouse_network/src/service/api_types.rs
#	beacon_node/network/src/beacon_processor/worker/rpc_methods.rs
#	beacon_node/tests/test.rs
#	common/eth2/src/types.rs
#	lighthouse/src/main.rs

* Add missing test file

* Update light client types to comply with Altair light client spec.

* Fix test compilation

* Merge branch 'unstable' into light_client_beacon_api_1

* Support deserializing light client structures for the Bellatrix fork

* Move `get_light_client_bootstrap` logic to `BeaconChain`. `LightClientBootstrap` API to return `ForkVersionedResponse`.

* Misc fixes.
- log cleanup
- move http_api config mutation to `config::get_config` for consistency
- fix light client API responses

* Add light client bootstrap API test and fix existing ones.

* Merge branch 'unstable' into light_client_beacon_api_1

* Fix test for `light-client-server` http api config.

* Appease clippy

* Add Altair light client SSZ tests

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into light_client_beacon_api_1

* updates to light client header

* light client header from signed beacon block

* using options

* implement helper functions

* placeholder conversion from vec hash256 to exec branch

* add deneb

* using fixed vector

* remove unwraps

* by epoch

* compute merkle proof

* merkle proof

* update comments

* resolve merge conflicts

* linting

* Merge branch 'unstable' into light-client-ssz-tests

# Conflicts:
#	beacon_node/beacon_chain/src/beacon_chain.rs
#	consensus/types/src/light_client_bootstrap.rs
#	consensus/types/src/light_client_header.rs

* superstruct attempt

* superstruct changes

* lint

* altair

* update

* update

* changes to light_client_optimistic_ and finality

* merge unstable

* refactor

* resolved merge conflicts

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into capella_deneb_light_client_types

* block_to_light_client_header fork aware

* fmt

* comment fix

* comment fix

* include merge fork, update deserialize_by_fork, refactor

* fmt

* pass by ref to prevent clone

* rename merkle proof fn

* add FIXME

* LightClientHeader TestRandom

* fix comments

* fork version deserialize

* merge unstable

* move fn arguments, fork name calc

* use task executor

* remove unneeded fns

* remove dead code

* add manual ssz decoding/encoding and add ssz_tests_by_fork macro

* merge deneb types with tests

* merge ssz tests, revert code deletion, cleanup

* move chainspec

* update ssz tests

* fmt

* light client ssz tests

* change to superstruct

* changes from feedback

* linting

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into capella_deneb_light_client_types

* test fix

* cleanup

* Remove unused `derive`.

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into capella_deneb_light_client_types

* beta compiler fix

* merge
  • Loading branch information
eserilev authored Mar 25, 2024
1 parent 19b0db2 commit e4d4e43
Show file tree
Hide file tree
Showing 24 changed files with 1,083 additions and 323 deletions.
21 changes: 13 additions & 8 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1347,11 +1347,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
(parent_root, slot, sync_aggregate): LightClientProducerEvent<T::EthSpec>,
) -> Result<(), Error> {
self.light_client_server_cache.recompute_and_cache_updates(
&self.log,
self.store.clone(),
&parent_root,
slot,
&sync_aggregate,
&self.log,
&self.spec,
)
}

Expand Down Expand Up @@ -6635,13 +6636,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
&self,
block_root: &Hash256,
) -> Result<Option<(LightClientBootstrap<T::EthSpec>, ForkName)>, Error> {
let Some((state_root, slot)) = self
.get_blinded_block(block_root)?
.map(|block| (block.state_root(), block.slot()))
else {
let handle = self
.task_executor
.handle()
.ok_or(BeaconChainError::RuntimeShutdown)?;

let Some(block) = handle.block_on(async { self.get_block(block_root).await })? 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);
};
Expand All @@ -6651,12 +6656,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map_err(Error::InconsistentFork)?;

match fork_name {
ForkName::Altair | ForkName::Merge => {
LightClientBootstrap::from_beacon_state(&mut state)
ForkName::Altair | ForkName::Merge | ForkName::Capella | ForkName::Deneb => {
LightClientBootstrap::from_beacon_state(&mut state, &block, &self.spec)
.map(|bootstrap| Some((bootstrap, fork_name)))
.map_err(Error::LightClientError)
}
ForkName::Base | ForkName::Capella | ForkName::Deneb => Err(Error::UnsupportedFork),
ForkName::Base => Err(Error::UnsupportedFork),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ easy_from_to!(StateAdvanceError, BeaconChainError);
easy_from_to!(BlockReplayError, BeaconChainError);
easy_from_to!(InconsistentFork, BeaconChainError);
easy_from_to!(AvailabilityCheckError, BeaconChainError);
easy_from_to!(LightClientError, BeaconChainError);

#[derive(Debug)]
pub enum BlockProductionError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientFinalityUpdate<T> {
// verify that enough time has passed for the block to have been propagated
let start_time = chain
.slot_clock
.start_of(rcv_finality_update.signature_slot)
.start_of(*rcv_finality_update.signature_slot())
.ok_or(Error::SigSlotStartIsNone)?;
let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0);
if seen_timestamp + chain.spec.maximum_gossip_clock_disparity()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
// verify that enough time has passed for the block to have been propagated
let start_time = chain
.slot_clock
.start_of(rcv_optimistic_update.signature_slot)
.start_of(*rcv_optimistic_update.signature_slot())
.ok_or(Error::SigSlotStartIsNone)?;
let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0);
if seen_timestamp + chain.spec.maximum_gossip_clock_disparity()
Expand All @@ -65,10 +65,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
let head_block = &head.snapshot.beacon_block;
// check if we can process the optimistic update immediately
// otherwise queue
let canonical_root = rcv_optimistic_update
.attested_header
.beacon
.canonical_root();
let canonical_root = rcv_optimistic_update.get_canonical_root();

if canonical_root != head_block.message().parent_root() {
return Err(Error::UnknownBlockParentRoot(canonical_root));
Expand All @@ -84,7 +81,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
return Err(Error::InvalidLightClientOptimisticUpdate);
}

let parent_root = rcv_optimistic_update.attested_header.beacon.parent_root;
let parent_root = rcv_optimistic_update.get_parent_root();
Ok(Self {
light_client_optimistic_update: rcv_optimistic_update,
parent_root,
Expand Down
67 changes: 31 additions & 36 deletions beacon_node/beacon_chain/src/light_client_server_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use types::light_client_update::{FinalizedRootProofLen, FINALIZED_ROOT_INDEX};
use types::non_zero_usize::new_non_zero_usize;
use types::{
BeaconBlockRef, BeaconState, ChainSpec, EthSpec, ForkName, Hash256, LightClientFinalityUpdate,
LightClientHeader, LightClientOptimisticUpdate, Slot, SyncAggregate,
LightClientOptimisticUpdate, Slot, SyncAggregate,
};

/// A prev block cache miss requires to re-generate the state of the post-parent block. Items in the
Expand Down Expand Up @@ -71,24 +71,26 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
/// results are cached either on disk or memory to be served via p2p and rest API
pub fn recompute_and_cache_updates(
&self,
log: &Logger,
store: BeaconStore<T>,
block_parent_root: &Hash256,
block_slot: Slot,
sync_aggregate: &SyncAggregate<T::EthSpec>,
log: &Logger,
chain_spec: &ChainSpec,
) -> Result<(), BeaconChainError> {
let _timer =
metrics::start_timer(&metrics::LIGHT_CLIENT_SERVER_CACHE_RECOMPUTE_UPDATES_TIMES);

let signature_slot = block_slot;
let attested_block_root = block_parent_root;

let attested_block = store.get_blinded_block(attested_block_root)?.ok_or(
BeaconChainError::DBInconsistent(format!(
"Block not available {:?}",
attested_block_root
)),
)?;
let attested_block =
store
.get_full_block(attested_block_root)?
.ok_or(BeaconChainError::DBInconsistent(format!(
"Block not available {:?}",
attested_block_root
)))?;

let cached_parts = self.get_or_compute_prev_block_cache(
store.clone(),
Expand All @@ -109,11 +111,12 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
};
if is_latest_optimistic {
// can create an optimistic update, that is more recent
*self.latest_optimistic_update.write() = Some(LightClientOptimisticUpdate {
attested_header: block_to_light_client_header(attested_block.message()),
sync_aggregate: sync_aggregate.clone(),
*self.latest_optimistic_update.write() = Some(LightClientOptimisticUpdate::new(
&attested_block,
sync_aggregate.clone(),
signature_slot,
});
chain_spec,
)?);
};

// Spec: Full nodes SHOULD provide the LightClientFinalityUpdate with the highest
Expand All @@ -127,17 +130,16 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
if is_latest_finality & !cached_parts.finalized_block_root.is_zero() {
// Immediately after checkpoint sync the finalized block may not be available yet.
if let Some(finalized_block) =
store.get_blinded_block(&cached_parts.finalized_block_root)?
store.get_full_block(&cached_parts.finalized_block_root)?
{
*self.latest_finality_update.write() = Some(LightClientFinalityUpdate {
// TODO: may want to cache this result from latest_optimistic_update if producing a
// light_client header becomes expensive
attested_header: block_to_light_client_header(attested_block.message()),
finalized_header: block_to_light_client_header(finalized_block.message()),
finality_branch: cached_parts.finality_branch.clone(),
sync_aggregate: sync_aggregate.clone(),
*self.latest_finality_update.write() = Some(LightClientFinalityUpdate::new(
&attested_block,
&finalized_block,
cached_parts.finality_branch.clone(),
sync_aggregate.clone(),
signature_slot,
});
chain_spec,
)?);
} else {
debug!(
log,
Expand Down Expand Up @@ -214,7 +216,7 @@ impl LightClientCachedData {
}
}

// Implements spec priorization rules:
// Implements spec prioritization rules:
// > Full nodes SHOULD provide the LightClientFinalityUpdate with the highest attested_header.beacon.slot (if multiple, highest signature_slot)
//
// ref: https://github.com/ethereum/consensus-specs/blob/113c58f9bf9c08867f6f5f633c4d98e0364d612a/specs/altair/light-client/full-node.md#create_light_client_finality_update
Expand All @@ -223,14 +225,15 @@ fn is_latest_finality_update<T: EthSpec>(
attested_slot: Slot,
signature_slot: Slot,
) -> bool {
if attested_slot > prev.attested_header.beacon.slot {
let prev_slot = prev.get_attested_header_slot();
if attested_slot > prev_slot {
true
} else {
attested_slot == prev.attested_header.beacon.slot && signature_slot > prev.signature_slot
attested_slot == prev_slot && signature_slot > *prev.signature_slot()
}
}

// Implements spec priorization rules:
// Implements spec prioritization rules:
// > Full nodes SHOULD provide the LightClientOptimisticUpdate with the highest attested_header.beacon.slot (if multiple, highest signature_slot)
//
// ref: https://github.com/ethereum/consensus-specs/blob/113c58f9bf9c08867f6f5f633c4d98e0364d612a/specs/altair/light-client/full-node.md#create_light_client_optimistic_update
Expand All @@ -239,18 +242,10 @@ fn is_latest_optimistic_update<T: EthSpec>(
attested_slot: Slot,
signature_slot: Slot,
) -> bool {
if attested_slot > prev.attested_header.beacon.slot {
let prev_slot = prev.get_slot();
if attested_slot > prev_slot {
true
} else {
attested_slot == prev.attested_header.beacon.slot && signature_slot > prev.signature_slot
}
}

fn block_to_light_client_header<T: EthSpec>(
block: BeaconBlockRef<T, types::BlindedPayload<T>>,
) -> LightClientHeader {
// TODO: make fork aware
LightClientHeader {
beacon: block.block_header(),
attested_slot == prev_slot && signature_slot > *prev.signature_slot()
}
}
4 changes: 2 additions & 2 deletions beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2337,7 +2337,7 @@ pub fn serve<T: BeaconChainTypes>(

let fork_name = chain
.spec
.fork_name_at_slot::<T::EthSpec>(update.signature_slot);
.fork_name_at_slot::<T::EthSpec>(*update.signature_slot());
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
Expand Down Expand Up @@ -2384,7 +2384,7 @@ pub fn serve<T: BeaconChainTypes>(

let fork_name = chain
.spec
.fork_name_at_slot::<T::EthSpec>(update.signature_slot);
.fork_name_at_slot::<T::EthSpec>(*update.signature_slot());
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1717,7 +1717,7 @@ impl ApiTester {
};

let expected = block.slot();
assert_eq!(result.header.beacon.slot, expected);
assert_eq!(result.get_slot(), expected);

self
}
Expand Down
15 changes: 12 additions & 3 deletions beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,9 +590,18 @@ fn handle_rpc_response<T: EthSpec>(
SupportedProtocol::MetaDataV1 => Ok(Some(RPCResponse::MetaData(MetaData::V1(
MetaDataV1::from_ssz_bytes(decoded_buffer)?,
)))),
SupportedProtocol::LightClientBootstrapV1 => Ok(Some(RPCResponse::LightClientBootstrap(
LightClientBootstrap::from_ssz_bytes(decoded_buffer)?,
))),
SupportedProtocol::LightClientBootstrapV1 => match fork_name {
Some(fork_name) => Ok(Some(RPCResponse::LightClientBootstrap(Arc::new(
LightClientBootstrap::from_ssz_bytes(decoded_buffer, fork_name)?,
)))),
None => Err(RPCError::ErrorResponse(
RPCResponseErrorCode::InvalidRequest,
format!(
"No context bytes provided for {:?} response",
versioned_protocol
),
)),
},
// MetaData V2 responses have no context bytes, so behave similarly to V1 responses
SupportedProtocol::MetaDataV2 => Ok(Some(RPCResponse::MetaData(MetaData::V2(
MetaDataV2::from_ssz_bytes(decoded_buffer)?,
Expand Down
8 changes: 2 additions & 6 deletions beacon_node/lighthouse_network/src/rpc/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ pub enum RPCResponse<T: EthSpec> {
BlobsByRange(Arc<BlobSidecar<T>>),

/// A response to a get LIGHT_CLIENT_BOOTSTRAP request.
LightClientBootstrap(LightClientBootstrap<T>),
LightClientBootstrap(Arc<LightClientBootstrap<T>>),

/// A response to a get BLOBS_BY_ROOT request.
BlobsByRoot(Arc<BlobSidecar<T>>),
Expand Down Expand Up @@ -569,11 +569,7 @@ impl<T: EthSpec> std::fmt::Display for RPCResponse<T> {
RPCResponse::Pong(ping) => write!(f, "Pong: {}", ping.data),
RPCResponse::MetaData(metadata) => write!(f, "Metadata: {}", metadata.seq_number()),
RPCResponse::LightClientBootstrap(bootstrap) => {
write!(
f,
"LightClientBootstrap Slot: {}",
bootstrap.header.beacon.slot
)
write!(f, "LightClientBootstrap Slot: {}", bootstrap.get_slot())
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion beacon_node/lighthouse_network/src/service/api_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub enum Response<TSpec: EthSpec> {
/// A response to a get BLOBS_BY_ROOT request.
BlobsByRoot(Option<Arc<BlobSidecar<TSpec>>>),
/// A response to a LightClientUpdate request.
LightClientBootstrap(LightClientBootstrap<TSpec>),
LightClientBootstrap(Arc<LightClientBootstrap<TSpec>>),
}

impl<TSpec: EthSpec> std::convert::From<Response<TSpec>> for RPCCodedResponse<TSpec> {
Expand Down
26 changes: 20 additions & 6 deletions beacon_node/lighthouse_network/src/types/pubsub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,31 @@ impl<T: EthSpec> PubsubMessage<T> {
)))
}
GossipKind::LightClientFinalityUpdate => {
let light_client_finality_update =
LightClientFinalityUpdate::from_ssz_bytes(data)
.map_err(|e| format!("{:?}", e))?;
let light_client_finality_update = match fork_context.from_context_bytes(gossip_topic.fork_digest) {
Some(&fork_name) => {
LightClientFinalityUpdate::from_ssz_bytes(data, fork_name)
.map_err(|e| format!("{:?}", e))?
},
None => return Err(format!(
"light_client_finality_update topic invalid for given fork digest {:?}",
gossip_topic.fork_digest
)),
};
Ok(PubsubMessage::LightClientFinalityUpdate(Box::new(
light_client_finality_update,
)))
}
GossipKind::LightClientOptimisticUpdate => {
let light_client_optimistic_update =
LightClientOptimisticUpdate::from_ssz_bytes(data)
.map_err(|e| format!("{:?}", e))?;
let light_client_optimistic_update = match fork_context.from_context_bytes(gossip_topic.fork_digest) {
Some(&fork_name) => {
LightClientOptimisticUpdate::from_ssz_bytes(data, fork_name)
.map_err(|e| format!("{:?}", e))?
},
None => return Err(format!(
"light_client_optimistic_update topic invalid for given fork digest {:?}",
gossip_topic.fork_digest
)),
};
Ok(PubsubMessage::LightClientOptimisticUpdate(Box::new(
light_client_optimistic_update,
)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
match self.chain.get_light_client_bootstrap(&block_root) {
Ok(Some((bootstrap, _))) => self.send_response(
peer_id,
Response::LightClientBootstrap(bootstrap),
Response::LightClientBootstrap(Arc::new(bootstrap)),
request_id,
),
Ok(None) => self.send_error_response(
Expand Down
Loading

0 comments on commit e4d4e43

Please sign in to comment.