Skip to content

Commit d67ae92

Browse files
Implement /lighthouse/custody/info API (#8276)
Closes: - #8249 New `/lighthouse/custody` API including: - [x] Earliest custodied data column slot - [x] Node CGC - [x] Custodied columns Co-Authored-By: Michael Sproul <[email protected]>
1 parent ba706ce commit d67ae92

File tree

6 files changed

+172
-2
lines changed

6 files changed

+172
-2
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use beacon_chain::{BeaconChain, BeaconChainTypes};
2+
use eth2::lighthouse::CustodyInfo;
3+
use std::sync::Arc;
4+
use types::EthSpec;
5+
use warp_utils::reject::{custom_bad_request, custom_server_error};
6+
7+
pub fn info<T: BeaconChainTypes>(
8+
chain: Arc<BeaconChain<T>>,
9+
) -> Result<CustodyInfo, warp::Rejection> {
10+
if !chain.spec.is_fulu_scheduled() {
11+
return Err(custom_bad_request("Fulu is not scheduled".to_string()));
12+
}
13+
14+
let opt_data_column_custody_info = chain
15+
.store
16+
.get_data_column_custody_info()
17+
.map_err(|e| custom_server_error(format!("error reading DataColumnCustodyInfo: {e:?}")))?;
18+
19+
let column_data_availability_boundary = chain
20+
.column_data_availability_boundary()
21+
.ok_or_else(|| custom_server_error("unreachable: Fulu should be enabled".to_string()))?;
22+
23+
let earliest_custodied_data_column_slot = opt_data_column_custody_info
24+
.and_then(|info| info.earliest_data_column_slot)
25+
.unwrap_or_else(|| {
26+
// If there's no data column custody info/earliest data column slot, it means *column*
27+
// backfill is not running. Block backfill could still be running, so our earliest
28+
// available column is either the oldest block slot or the DA boundary, whichever is
29+
// more recent.
30+
let oldest_block_slot = chain.store.get_anchor_info().oldest_block_slot;
31+
column_data_availability_boundary
32+
.start_slot(T::EthSpec::slots_per_epoch())
33+
.max(oldest_block_slot)
34+
});
35+
let earliest_custodied_data_column_epoch =
36+
earliest_custodied_data_column_slot.epoch(T::EthSpec::slots_per_epoch());
37+
38+
// Compute the custody columns and the CGC *at the earliest custodied slot*. The node might
39+
// have some columns prior to this, but this value is the most up-to-date view of the data the
40+
// node is custodying.
41+
let custody_context = chain.data_availability_checker.custody_context();
42+
let custody_columns = custody_context
43+
.custody_columns_for_epoch(Some(earliest_custodied_data_column_epoch), &chain.spec)
44+
.to_vec();
45+
let custody_group_count = custody_context
46+
.custody_group_count_at_epoch(earliest_custodied_data_column_epoch, &chain.spec);
47+
48+
Ok(CustodyInfo {
49+
earliest_custodied_data_column_slot,
50+
custody_group_count,
51+
custody_columns,
52+
})
53+
}

beacon_node/http_api/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod block_packing_efficiency;
1313
mod block_rewards;
1414
mod build_block_contents;
1515
mod builder_states;
16+
mod custody;
1617
mod database;
1718
mod light_client;
1819
mod metrics;
@@ -4590,6 +4591,19 @@ pub fn serve<T: BeaconChainTypes>(
45904591
},
45914592
);
45924593

4594+
// GET lighthouse/custody/info
4595+
let get_lighthouse_custody_info = warp::path("lighthouse")
4596+
.and(warp::path("custody"))
4597+
.and(warp::path("info"))
4598+
.and(warp::path::end())
4599+
.and(task_spawner_filter.clone())
4600+
.and(chain_filter.clone())
4601+
.then(
4602+
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
4603+
task_spawner.blocking_json_task(Priority::P1, move || custody::info(chain))
4604+
},
4605+
);
4606+
45934607
// GET lighthouse/analysis/block_rewards
45944608
let get_lighthouse_block_rewards = warp::path("lighthouse")
45954609
.and(warp::path("analysis"))
@@ -4891,6 +4905,7 @@ pub fn serve<T: BeaconChainTypes>(
48914905
.uor(get_lighthouse_validator_inclusion)
48924906
.uor(get_lighthouse_staking)
48934907
.uor(get_lighthouse_database_info)
4908+
.uor(get_lighthouse_custody_info)
48944909
.uor(get_lighthouse_block_rewards)
48954910
.uor(get_lighthouse_attestation_performance)
48964911
.uor(get_beacon_light_client_optimistic_update)

beacon_node/http_api/tests/interactive_tests.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
use beacon_chain::{
33
ChainConfig,
44
chain_config::{DisallowedReOrgOffsets, ReOrgThreshold},
5-
test_utils::{AttestationStrategy, BlockStrategy, LightClientStrategy, SyncCommitteeStrategy},
5+
test_utils::{
6+
AttestationStrategy, BlockStrategy, LightClientStrategy, SyncCommitteeStrategy, test_spec,
7+
},
68
};
79
use beacon_processor::{Work, WorkEvent, work_reprocessing_queue::ReprocessQueueMessage};
810
use eth2::types::ProduceBlockV3Response;
@@ -1047,3 +1049,77 @@ async fn proposer_duties_with_gossip_tolerance() {
10471049
proposer_duties_current_epoch
10481050
);
10491051
}
1052+
1053+
// Test that a request for next epoch proposer duties suceeds when the current slot clock is within
1054+
// gossip clock disparity (500ms) of the new epoch.
1055+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1056+
async fn lighthouse_custody_info() {
1057+
let mut spec = test_spec::<E>();
1058+
1059+
// Skip pre-Fulu.
1060+
if !spec.is_fulu_scheduled() {
1061+
return;
1062+
}
1063+
1064+
// Use a short DA expiry period so we can observe non-zero values for the oldest data column
1065+
// slot.
1066+
spec.min_epochs_for_blob_sidecars_requests = 2;
1067+
spec.min_epochs_for_data_column_sidecars_requests = 2;
1068+
1069+
let validator_count = 24;
1070+
1071+
let tester = InteractiveTester::<E>::new(Some(spec), validator_count).await;
1072+
let harness = &tester.harness;
1073+
let spec = &harness.spec;
1074+
let client = &tester.client;
1075+
1076+
let num_initial = 2 * E::slots_per_epoch();
1077+
let num_secondary = 2 * E::slots_per_epoch();
1078+
1079+
harness.advance_slot();
1080+
harness
1081+
.extend_chain_with_sync(
1082+
num_initial as usize,
1083+
BlockStrategy::OnCanonicalHead,
1084+
AttestationStrategy::AllValidators,
1085+
SyncCommitteeStrategy::NoValidators,
1086+
LightClientStrategy::Disabled,
1087+
)
1088+
.await;
1089+
1090+
assert_eq!(harness.chain.slot().unwrap(), num_initial);
1091+
1092+
let info = client.get_lighthouse_custody_info().await.unwrap();
1093+
assert_eq!(info.earliest_custodied_data_column_slot, 0);
1094+
assert_eq!(info.custody_group_count, spec.custody_requirement);
1095+
assert_eq!(
1096+
info.custody_columns.len(),
1097+
info.custody_group_count as usize
1098+
);
1099+
1100+
// Advance the chain some more to expire some blobs.
1101+
harness.advance_slot();
1102+
harness
1103+
.extend_chain_with_sync(
1104+
num_secondary as usize,
1105+
BlockStrategy::OnCanonicalHead,
1106+
AttestationStrategy::AllValidators,
1107+
SyncCommitteeStrategy::NoValidators,
1108+
LightClientStrategy::Disabled,
1109+
)
1110+
.await;
1111+
1112+
assert_eq!(harness.chain.slot().unwrap(), num_initial + num_secondary);
1113+
1114+
let info = client.get_lighthouse_custody_info().await.unwrap();
1115+
assert_eq!(
1116+
info.earliest_custodied_data_column_slot,
1117+
num_initial + num_secondary
1118+
- spec.min_epochs_for_data_column_sidecars_requests * E::slots_per_epoch()
1119+
);
1120+
assert_eq!(info.custody_group_count, spec.custody_requirement);
1121+
assert_eq!(
1122+
info.custody_columns.len(),
1123+
info.custody_group_count as usize
1124+
);
1125+
}

common/eth2/src/lighthouse.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
mod attestation_performance;
44
mod block_packing_efficiency;
55
mod block_rewards;
6+
mod custody;
67
pub mod sync_state;
78

89
use crate::{
@@ -22,6 +23,7 @@ pub use block_packing_efficiency::{
2223
BlockPackingEfficiency, BlockPackingEfficiencyQuery, ProposerInfo, UniqueAttestation,
2324
};
2425
pub use block_rewards::{AttestationRewards, BlockReward, BlockRewardMeta, BlockRewardsQuery};
26+
pub use custody::CustodyInfo;
2527

2628
// Define "legacy" implementations of `Option<T>` which use four bytes for encoding the union
2729
// selector.
@@ -193,6 +195,19 @@ impl BeaconNodeHttpClient {
193195
self.get(path).await
194196
}
195197

198+
/// `GET lighthouse/custody/info`
199+
pub async fn get_lighthouse_custody_info(&self) -> Result<CustodyInfo, Error> {
200+
let mut path = self.server.full.clone();
201+
202+
path.path_segments_mut()
203+
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
204+
.push("lighthouse")
205+
.push("custody")
206+
.push("info");
207+
208+
self.get(path).await
209+
}
210+
196211
/*
197212
* Note:
198213
*
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use serde::{Deserialize, Serialize};
2+
use types::Slot;
3+
4+
#[derive(Debug, PartialEq, Deserialize, Serialize)]
5+
pub struct CustodyInfo {
6+
pub earliest_custodied_data_column_slot: Slot,
7+
#[serde(with = "serde_utils::quoted_u64")]
8+
pub custody_group_count: u64,
9+
#[serde(with = "serde_utils::quoted_u64_vec")]
10+
pub custody_columns: Vec<u64>,
11+
}

consensus/types/src/chain_spec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ pub struct ChainSpec {
255255
* Networking Fulu
256256
*/
257257
pub(crate) blob_schedule: BlobSchedule,
258-
min_epochs_for_data_column_sidecars_requests: u64,
258+
pub min_epochs_for_data_column_sidecars_requests: u64,
259259

260260
/*
261261
* Networking Gloas

0 commit comments

Comments
 (0)