Skip to content

Commit 8a599ec

Browse files
API for LightClientBootstrap, LightClientFinalityUpdate, LightClientOptimisticUpdate and light client events (#3954)
* 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 * Add missing test file * Update light client types to comply with Altair light client spec. * Fix test compilation * 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. * Fix test for `light-client-server` http api config. * Appease clippy * Efficiency improvement when retrieving beacon state. --------- Co-authored-by: Jimmy Chen <[email protected]>
1 parent 44c1817 commit 8a599ec

File tree

24 files changed

+629
-108
lines changed

24 files changed

+629
-108
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6446,6 +6446,39 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
64466446
pub fn data_availability_boundary(&self) -> Option<Epoch> {
64476447
self.data_availability_checker.data_availability_boundary()
64486448
}
6449+
6450+
/// Gets the `LightClientBootstrap` object for a requested block root.
6451+
///
6452+
/// Returns `None` when the state or block is not found in the database.
6453+
#[allow(clippy::type_complexity)]
6454+
pub fn get_light_client_bootstrap(
6455+
&self,
6456+
block_root: &Hash256,
6457+
) -> Result<Option<(LightClientBootstrap<T::EthSpec>, ForkName)>, Error> {
6458+
let Some((state_root, slot)) = self
6459+
.get_blinded_block(block_root)?
6460+
.map(|block| (block.state_root(), block.slot()))
6461+
else {
6462+
return Ok(None);
6463+
};
6464+
6465+
let Some(mut state) = self.get_state(&state_root, Some(slot))? else {
6466+
return Ok(None);
6467+
};
6468+
6469+
let fork_name = state
6470+
.fork_name(&self.spec)
6471+
.map_err(Error::InconsistentFork)?;
6472+
6473+
match fork_name {
6474+
ForkName::Altair | ForkName::Merge => {
6475+
LightClientBootstrap::from_beacon_state(&mut state)
6476+
.map(|bootstrap| Some((bootstrap, fork_name)))
6477+
.map_err(Error::LightClientError)
6478+
}
6479+
ForkName::Base | ForkName::Capella | ForkName::Deneb => Err(Error::UnsupportedFork),
6480+
}
6481+
}
64496482
}
64506483

64516484
impl<T: BeaconChainTypes> Drop for BeaconChain<T> {

beacon_node/beacon_chain/src/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ pub enum BeaconChainError {
221221
ProposerHeadForkChoiceError(fork_choice::Error<proto_array::Error>),
222222
UnableToPublish,
223223
AvailabilityCheckError(AvailabilityCheckError),
224+
LightClientError(LightClientError),
225+
UnsupportedFork,
224226
}
225227

226228
easy_from_to!(SlotProcessingError, BeaconChainError);

beacon_node/beacon_chain/src/events.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub struct ServerSentEventHandler<T: EthSpec> {
1717
contribution_tx: Sender<EventKind<T>>,
1818
payload_attributes_tx: Sender<EventKind<T>>,
1919
late_head: Sender<EventKind<T>>,
20+
light_client_finality_update_tx: Sender<EventKind<T>>,
21+
light_client_optimistic_update_tx: Sender<EventKind<T>>,
2022
block_reward_tx: Sender<EventKind<T>>,
2123
log: Logger,
2224
}
@@ -40,6 +42,8 @@ impl<T: EthSpec> ServerSentEventHandler<T> {
4042
let (contribution_tx, _) = broadcast::channel(capacity);
4143
let (payload_attributes_tx, _) = broadcast::channel(capacity);
4244
let (late_head, _) = broadcast::channel(capacity);
45+
let (light_client_finality_update_tx, _) = broadcast::channel(capacity);
46+
let (light_client_optimistic_update_tx, _) = broadcast::channel(capacity);
4347
let (block_reward_tx, _) = broadcast::channel(capacity);
4448

4549
Self {
@@ -53,6 +57,8 @@ impl<T: EthSpec> ServerSentEventHandler<T> {
5357
contribution_tx,
5458
payload_attributes_tx,
5559
late_head,
60+
light_client_finality_update_tx,
61+
light_client_optimistic_update_tx,
5662
block_reward_tx,
5763
log,
5864
}
@@ -108,6 +114,14 @@ impl<T: EthSpec> ServerSentEventHandler<T> {
108114
.late_head
109115
.send(kind)
110116
.map(|count| log_count("late head", count)),
117+
EventKind::LightClientFinalityUpdate(_) => self
118+
.light_client_finality_update_tx
119+
.send(kind)
120+
.map(|count| log_count("light client finality update", count)),
121+
EventKind::LightClientOptimisticUpdate(_) => self
122+
.light_client_optimistic_update_tx
123+
.send(kind)
124+
.map(|count| log_count("light client optimistic update", count)),
111125
EventKind::BlockReward(_) => self
112126
.block_reward_tx
113127
.send(kind)
@@ -158,6 +172,14 @@ impl<T: EthSpec> ServerSentEventHandler<T> {
158172
self.late_head.subscribe()
159173
}
160174

175+
pub fn subscribe_light_client_finality_update(&self) -> Receiver<EventKind<T>> {
176+
self.light_client_finality_update_tx.subscribe()
177+
}
178+
179+
pub fn subscribe_light_client_optimistic_update(&self) -> Receiver<EventKind<T>> {
180+
self.light_client_optimistic_update_tx.subscribe()
181+
}
182+
161183
pub fn subscribe_block_reward(&self) -> Receiver<EventKind<T>> {
162184
self.block_reward_tx.subscribe()
163185
}

beacon_node/beacon_chain/src/light_client_finality_update_verification.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientFinalityUpdate<T> {
6767
chain: &BeaconChain<T>,
6868
seen_timestamp: Duration,
6969
) -> Result<Self, Error> {
70-
let gossiped_finality_slot = light_client_finality_update.finalized_header.slot;
70+
let gossiped_finality_slot = light_client_finality_update.finalized_header.beacon.slot;
7171
let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0);
7272
let signature_slot = light_client_finality_update.signature_slot;
7373
let start_time = chain.slot_clock.start_of(signature_slot);
@@ -88,7 +88,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientFinalityUpdate<T> {
8888
.get_blinded_block(&finalized_block_root)?
8989
.ok_or(Error::FailedConstructingUpdate)?;
9090
let latest_seen_finality_update_slot = match latest_seen_finality_update.as_ref() {
91-
Some(update) => update.finalized_header.slot,
91+
Some(update) => update.finalized_header.beacon.slot,
9292
None => Slot::new(0),
9393
};
9494

beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
7171
chain: &BeaconChain<T>,
7272
seen_timestamp: Duration,
7373
) -> Result<Self, Error> {
74-
let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.slot;
74+
let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.beacon.slot;
7575
let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0);
7676
let signature_slot = light_client_optimistic_update.signature_slot;
7777
let start_time = chain.slot_clock.start_of(signature_slot);
@@ -88,7 +88,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
8888
.get_state(&attested_block.state_root(), Some(attested_block.slot()))?
8989
.ok_or(Error::FailedConstructingUpdate)?;
9090
let latest_seen_optimistic_update_slot = match latest_seen_optimistic_update.as_ref() {
91-
Some(update) => update.attested_header.slot,
91+
Some(update) => update.attested_header.beacon.slot,
9292
None => Slot::new(0),
9393
};
9494

@@ -114,6 +114,7 @@ impl<T: BeaconChainTypes> VerifiedLightClientOptimisticUpdate<T> {
114114
// otherwise queue
115115
let canonical_root = light_client_optimistic_update
116116
.attested_header
117+
.beacon
117118
.canonical_root();
118119

119120
if canonical_root != head_block.message().parent_root() {

beacon_node/http_api/src/lib.rs

Lines changed: 194 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ use tokio_stream::{
7777
use types::{
7878
Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError,
7979
BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName,
80-
ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof,
81-
SignedBlsToExecutionChange, SignedContributionAndProof, SignedValidatorRegistrationData,
82-
SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData,
80+
ForkVersionedResponse, Hash256, ProposerPreparationData, ProposerSlashing, RelativeEpoch,
81+
SignedAggregateAndProof, SignedBlsToExecutionChange, SignedContributionAndProof,
82+
SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncCommitteeMessage,
83+
SyncContributionData,
8384
};
8485
use validator::pubkey_to_validator_index;
8586
use version::{
@@ -143,6 +144,7 @@ pub struct Config {
143144
pub enable_beacon_processor: bool,
144145
#[serde(with = "eth2::types::serde_status_code")]
145146
pub duplicate_block_status_code: StatusCode,
147+
pub enable_light_client_server: bool,
146148
}
147149

148150
impl Default for Config {
@@ -159,6 +161,7 @@ impl Default for Config {
159161
sse_capacity_multiplier: 1,
160162
enable_beacon_processor: true,
161163
duplicate_block_status_code: StatusCode::ACCEPTED,
164+
enable_light_client_server: false,
162165
}
163166
}
164167
}
@@ -279,6 +282,18 @@ pub fn prometheus_metrics() -> warp::filters::log::Log<impl Fn(warp::filters::lo
279282
})
280283
}
281284

285+
fn enable(is_enabled: bool) -> impl Filter<Extract = (), Error = warp::Rejection> + Clone {
286+
warp::any()
287+
.and_then(move || async move {
288+
if is_enabled {
289+
Ok(())
290+
} else {
291+
Err(warp::reject::not_found())
292+
}
293+
})
294+
.untuple_one()
295+
}
296+
282297
/// Creates a server that will serve requests using information from `ctx`.
283298
///
284299
/// The server will shut down gracefully when the `shutdown` future resolves.
@@ -2379,6 +2394,164 @@ pub fn serve<T: BeaconChainTypes>(
23792394
},
23802395
);
23812396

2397+
/*
2398+
* beacon/light_client
2399+
*/
2400+
2401+
let beacon_light_client_path = eth_v1
2402+
.and(warp::path("beacon"))
2403+
.and(warp::path("light_client"))
2404+
.and(chain_filter.clone());
2405+
2406+
// GET beacon/light_client/bootstrap/{block_root}
2407+
let get_beacon_light_client_bootstrap = beacon_light_client_path
2408+
.clone()
2409+
.and(task_spawner_filter.clone())
2410+
.and(warp::path("bootstrap"))
2411+
.and(warp::path::param::<Hash256>().or_else(|_| async {
2412+
Err(warp_utils::reject::custom_bad_request(
2413+
"Invalid block root value".to_string(),
2414+
))
2415+
}))
2416+
.and(warp::path::end())
2417+
.and(warp::header::optional::<api_types::Accept>("accept"))
2418+
.then(
2419+
|chain: Arc<BeaconChain<T>>,
2420+
task_spawner: TaskSpawner<T::EthSpec>,
2421+
block_root: Hash256,
2422+
accept_header: Option<api_types::Accept>| {
2423+
task_spawner.blocking_response_task(Priority::P1, move || {
2424+
let (bootstrap, fork_name) = match chain.get_light_client_bootstrap(&block_root)
2425+
{
2426+
Ok(Some(res)) => res,
2427+
Ok(None) => {
2428+
return Err(warp_utils::reject::custom_not_found(
2429+
"Light client bootstrap unavailable".to_string(),
2430+
));
2431+
}
2432+
Err(e) => {
2433+
return Err(warp_utils::reject::custom_server_error(format!(
2434+
"Unable to obtain LightClientBootstrap instance: {e:?}"
2435+
)));
2436+
}
2437+
};
2438+
2439+
match accept_header {
2440+
Some(api_types::Accept::Ssz) => Response::builder()
2441+
.status(200)
2442+
.header("Content-Type", "application/octet-stream")
2443+
.body(bootstrap.as_ssz_bytes().into())
2444+
.map_err(|e| {
2445+
warp_utils::reject::custom_server_error(format!(
2446+
"failed to create response: {}",
2447+
e
2448+
))
2449+
}),
2450+
_ => Ok(warp::reply::json(&ForkVersionedResponse {
2451+
version: Some(fork_name),
2452+
data: bootstrap,
2453+
})
2454+
.into_response()),
2455+
}
2456+
.map(|resp| add_consensus_version_header(resp, fork_name))
2457+
})
2458+
},
2459+
);
2460+
2461+
// GET beacon/light_client/optimistic_update
2462+
let get_beacon_light_client_optimistic_update = beacon_light_client_path
2463+
.clone()
2464+
.and(task_spawner_filter.clone())
2465+
.and(warp::path("optimistic_update"))
2466+
.and(warp::path::end())
2467+
.and(warp::header::optional::<api_types::Accept>("accept"))
2468+
.then(
2469+
|chain: Arc<BeaconChain<T>>,
2470+
task_spawner: TaskSpawner<T::EthSpec>,
2471+
accept_header: Option<api_types::Accept>| {
2472+
task_spawner.blocking_response_task(Priority::P1, move || {
2473+
let update = chain
2474+
.latest_seen_optimistic_update
2475+
.lock()
2476+
.clone()
2477+
.ok_or_else(|| {
2478+
warp_utils::reject::custom_not_found(
2479+
"No LightClientOptimisticUpdate is available".to_string(),
2480+
)
2481+
})?;
2482+
2483+
let fork_name = chain
2484+
.spec
2485+
.fork_name_at_slot::<T::EthSpec>(update.signature_slot);
2486+
match accept_header {
2487+
Some(api_types::Accept::Ssz) => Response::builder()
2488+
.status(200)
2489+
.header("Content-Type", "application/octet-stream")
2490+
.body(update.as_ssz_bytes().into())
2491+
.map_err(|e| {
2492+
warp_utils::reject::custom_server_error(format!(
2493+
"failed to create response: {}",
2494+
e
2495+
))
2496+
}),
2497+
_ => Ok(warp::reply::json(&ForkVersionedResponse {
2498+
version: Some(fork_name),
2499+
data: update,
2500+
})
2501+
.into_response()),
2502+
}
2503+
.map(|resp| add_consensus_version_header(resp, fork_name))
2504+
})
2505+
},
2506+
);
2507+
2508+
// GET beacon/light_client/finality_update
2509+
let get_beacon_light_client_finality_update = beacon_light_client_path
2510+
.clone()
2511+
.and(task_spawner_filter.clone())
2512+
.and(warp::path("finality_update"))
2513+
.and(warp::path::end())
2514+
.and(warp::header::optional::<api_types::Accept>("accept"))
2515+
.then(
2516+
|chain: Arc<BeaconChain<T>>,
2517+
task_spawner: TaskSpawner<T::EthSpec>,
2518+
accept_header: Option<api_types::Accept>| {
2519+
task_spawner.blocking_response_task(Priority::P1, move || {
2520+
let update = chain
2521+
.latest_seen_finality_update
2522+
.lock()
2523+
.clone()
2524+
.ok_or_else(|| {
2525+
warp_utils::reject::custom_not_found(
2526+
"No LightClientFinalityUpdate is available".to_string(),
2527+
)
2528+
})?;
2529+
2530+
let fork_name = chain
2531+
.spec
2532+
.fork_name_at_slot::<T::EthSpec>(update.signature_slot);
2533+
match accept_header {
2534+
Some(api_types::Accept::Ssz) => Response::builder()
2535+
.status(200)
2536+
.header("Content-Type", "application/octet-stream")
2537+
.body(update.as_ssz_bytes().into())
2538+
.map_err(|e| {
2539+
warp_utils::reject::custom_server_error(format!(
2540+
"failed to create response: {}",
2541+
e
2542+
))
2543+
}),
2544+
_ => Ok(warp::reply::json(&ForkVersionedResponse {
2545+
version: Some(fork_name),
2546+
data: update,
2547+
})
2548+
.into_response()),
2549+
}
2550+
.map(|resp| add_consensus_version_header(resp, fork_name))
2551+
})
2552+
},
2553+
);
2554+
23822555
/*
23832556
* beacon/rewards
23842557
*/
@@ -4339,6 +4512,12 @@ pub fn serve<T: BeaconChainTypes>(
43394512
api_types::EventTopic::LateHead => {
43404513
event_handler.subscribe_late_head()
43414514
}
4515+
api_types::EventTopic::LightClientFinalityUpdate => {
4516+
event_handler.subscribe_light_client_finality_update()
4517+
}
4518+
api_types::EventTopic::LightClientOptimisticUpdate => {
4519+
event_handler.subscribe_light_client_optimistic_update()
4520+
}
43424521
api_types::EventTopic::BlockReward => {
43434522
event_handler.subscribe_block_reward()
43444523
}
@@ -4492,6 +4671,18 @@ pub fn serve<T: BeaconChainTypes>(
44924671
.uor(get_lighthouse_database_info)
44934672
.uor(get_lighthouse_block_rewards)
44944673
.uor(get_lighthouse_attestation_performance)
4674+
.uor(
4675+
enable(ctx.config.enable_light_client_server)
4676+
.and(get_beacon_light_client_optimistic_update),
4677+
)
4678+
.uor(
4679+
enable(ctx.config.enable_light_client_server)
4680+
.and(get_beacon_light_client_finality_update),
4681+
)
4682+
.uor(
4683+
enable(ctx.config.enable_light_client_server)
4684+
.and(get_beacon_light_client_bootstrap),
4685+
)
44954686
.uor(get_lighthouse_block_packing_efficiency)
44964687
.uor(get_lighthouse_merge_readiness)
44974688
.uor(get_events)

0 commit comments

Comments
 (0)