Skip to content

Commit 47a0bd4

Browse files
authored
Merge of #6551
2 parents d509246 + af6fe5c commit 47a0bd4

File tree

10 files changed

+203
-37
lines changed

10 files changed

+203
-37
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

book/src/api_vc_endpoints.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
| [`POST /lighthouse/validators/web3signer`](#post-lighthousevalidatorsweb3signer) | Add web3signer validators. |
2020
| [`GET /lighthouse/logs`](#get-lighthouselogs) | Get logs |
2121
| [`GET /lighthouse/beacon/health`](#get-lighthousebeaconhealth) | Get health information for each connected beacon node. |
22+
| [`POST /lighthouse/beacon/update`](#post-lighthousebeaconupdate) | Update the `--beacon-nodes` list. |
2223

2324
The query to Lighthouse API endpoints requires authorization, see [Authorization Header](./api_vc_auth_header.md).
2425

@@ -926,3 +927,57 @@ curl -X GET http://localhost:5062/lighthouse/beacon/health \
926927
}
927928
}
928929
```
930+
931+
## `POST /lighthouse/beacon/update`
932+
933+
Updates the list of beacon nodes originally specified by the `--beacon-nodes` CLI flag.
934+
Use this endpoint when you don't want to restart the VC to add, remove or reorder beacon nodes.
935+
936+
### HTTP Specification
937+
938+
| Property | Specification |
939+
|-------------------|--------------------------------------------|
940+
| Path | `/lighthouse/beacon/update` |
941+
| Method | POST |
942+
| Required Headers | [`Authorization`](./api_vc_auth_header.md) |
943+
| Typical Responses | 200, 400 |
944+
945+
### Example Request Body
946+
947+
```json
948+
{
949+
"beacon_nodes": [
950+
"http://beacon-node1:5052",
951+
"http://beacon-node2:5052",
952+
"http://beacon-node3:5052"
953+
]
954+
}
955+
```
956+
957+
Command:
958+
959+
```bash
960+
DATADIR=/var/lib/lighthouse
961+
curl -X POST http://localhost:5062/lighthouse/beacon/update \
962+
-H "Authorization: Bearer $(cat ${DATADIR}/validators/api-token.txt)" \
963+
-H "Content-Type: application/json" \
964+
-d "{\"beacon_nodes\":[\"http://beacon-node1:5052\",\"http://beacon-node2:5052\",\"http://beacon-node3:5052\"]}"
965+
```
966+
967+
### Example Response Body
968+
969+
```json
970+
{
971+
"data": {
972+
"new_beacon_nodes_list": [
973+
"http://beacon-node1:5052",
974+
"http://beacon-node2:5052",
975+
"http://beacon-node3:5052"
976+
]
977+
}
978+
}
979+
```
980+
981+
If successful, the response will be a copy of the new list included in the request.
982+
If unsuccessful, an error will be shown and the beacon nodes list will not be updated.
983+
You can verify the results of the endpoint by using the `/lighthouse/beacon/health` endpoint.

common/eth2/src/lib.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ pub const CONTENT_TYPE_HEADER: &str = "Content-Type";
5050
pub const SSZ_CONTENT_TYPE_HEADER: &str = "application/octet-stream";
5151
pub const JSON_CONTENT_TYPE_HEADER: &str = "application/json";
5252

53+
/// Specific optimized timeout constants for HTTP requests involved in different validator duties.
54+
/// This can help ensure that proper endpoint fallback occurs.
55+
const HTTP_ATTESTATION_TIMEOUT_QUOTIENT: u32 = 4;
56+
const HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
57+
const HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT: u32 = 24;
58+
const HTTP_LIVENESS_TIMEOUT_QUOTIENT: u32 = 4;
59+
const HTTP_PROPOSAL_TIMEOUT_QUOTIENT: u32 = 2;
60+
const HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
61+
const HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT: u32 = 4;
62+
const HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
63+
const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4;
64+
const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4;
65+
const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4;
66+
const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4;
67+
const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4;
68+
5369
#[derive(Debug)]
5470
pub enum Error {
5571
/// The `reqwest` client raised an error.
@@ -164,6 +180,26 @@ impl Timeouts {
164180
default: timeout,
165181
}
166182
}
183+
184+
pub fn use_optimized_timeouts(base_timeout: Duration) -> Self {
185+
Timeouts {
186+
attestation: base_timeout / HTTP_ATTESTATION_TIMEOUT_QUOTIENT,
187+
attester_duties: base_timeout / HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT,
188+
attestation_subscriptions: base_timeout
189+
/ HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT,
190+
liveness: base_timeout / HTTP_LIVENESS_TIMEOUT_QUOTIENT,
191+
proposal: base_timeout / HTTP_PROPOSAL_TIMEOUT_QUOTIENT,
192+
proposer_duties: base_timeout / HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT,
193+
sync_committee_contribution: base_timeout
194+
/ HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT,
195+
sync_duties: base_timeout / HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT,
196+
get_beacon_blocks_ssz: base_timeout / HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT,
197+
get_debug_beacon_states: base_timeout / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT,
198+
get_deposit_snapshot: base_timeout / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT,
199+
get_validator_block: base_timeout / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT,
200+
default: base_timeout / HTTP_DEFAULT_TIMEOUT_QUOTIENT,
201+
}
202+
}
167203
}
168204

169205
/// A wrapper around `reqwest::Client` which provides convenience methods for interfacing with a

common/eth2/src/lighthouse_vc/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,13 @@ pub struct SingleExportKeystoresResponse {
197197
pub struct SetGraffitiRequest {
198198
pub graffiti: GraffitiString,
199199
}
200+
201+
#[derive(Serialize, Deserialize, Debug)]
202+
pub struct UpdateCandidatesRequest {
203+
pub beacon_nodes: Vec<String>,
204+
}
205+
206+
#[derive(Serialize, Deserialize, Debug)]
207+
pub struct UpdateCandidatesResponse {
208+
pub new_beacon_nodes_list: Vec<String>,
209+
}

validator_client/beacon_node_fallback/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ clap = { workspace = true }
1313
eth2 = { workspace = true }
1414
futures = { workspace = true }
1515
itertools = { workspace = true }
16+
sensitive_url = { workspace = true }
1617
serde = { workspace = true }
1718
slot_clock = { workspace = true }
1819
strum = { workspace = true }

validator_client/beacon_node_fallback/src/lib.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ use beacon_node_health::{
88
IsOptimistic, SyncDistanceTier,
99
};
1010
use clap::ValueEnum;
11-
use eth2::BeaconNodeHttpClient;
11+
use eth2::{BeaconNodeHttpClient, Timeouts};
1212
use futures::future;
13+
use sensitive_url::SensitiveUrl;
1314
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
1415
use slot_clock::SlotClock;
1516
use std::cmp::Ordering;
@@ -455,6 +456,39 @@ impl<T: SlotClock> BeaconNodeFallback<T> {
455456
(candidate_info, num_available, num_synced)
456457
}
457458

459+
/// Update the list of candidates with a new list.
460+
/// Returns `Ok(new_list)` if the update was successful.
461+
/// Returns `Err(some_err)` if the list is empty.
462+
pub async fn update_candidates_list(
463+
&self,
464+
new_list: Vec<SensitiveUrl>,
465+
use_long_timeouts: bool,
466+
) -> Result<Vec<SensitiveUrl>, String> {
467+
if new_list.is_empty() {
468+
return Err("list cannot be empty".to_string());
469+
}
470+
471+
let timeouts: Timeouts = if new_list.len() == 1 || use_long_timeouts {
472+
Timeouts::set_all(Duration::from_secs(self.spec.seconds_per_slot))
473+
} else {
474+
Timeouts::use_optimized_timeouts(Duration::from_secs(self.spec.seconds_per_slot))
475+
};
476+
477+
let new_candidates: Vec<CandidateBeaconNode> = new_list
478+
.clone()
479+
.into_iter()
480+
.enumerate()
481+
.map(|(index, url)| {
482+
CandidateBeaconNode::new(BeaconNodeHttpClient::new(url, timeouts.clone()), index)
483+
})
484+
.collect();
485+
486+
let mut candidates = self.candidates.write().await;
487+
*candidates = new_candidates;
488+
489+
Ok(new_list)
490+
}
491+
458492
/// Loop through ALL candidates in `self.candidates` and update their sync status.
459493
///
460494
/// It is possible for a node to return an unsynced status while continuing to serve

validator_client/http_api/src/lib.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use account_utils::{
2222
};
2323
pub use api_secret::ApiSecret;
2424
use beacon_node_fallback::CandidateInfo;
25+
use core::convert::Infallible;
2526
use create_validator::{
2627
create_validators_mnemonic, create_validators_web3signer, get_voting_password_storage,
2728
};
@@ -30,14 +31,15 @@ use eth2::lighthouse_vc::{
3031
std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse},
3132
types::{
3233
self as api_types, GenericResponse, GetGraffitiResponse, Graffiti, PublicKey,
33-
PublicKeyBytes, SetGraffitiRequest,
34+
PublicKeyBytes, SetGraffitiRequest, UpdateCandidatesRequest, UpdateCandidatesResponse,
3435
},
3536
};
3637
use health_metrics::observe::Observe;
3738
use lighthouse_version::version_with_platform;
3839
use logging::crit;
3940
use logging::SSELoggingComponents;
4041
use parking_lot::RwLock;
42+
use sensitive_url::SensitiveUrl;
4143
use serde::{Deserialize, Serialize};
4244
use slot_clock::SlotClock;
4345
use std::collections::HashMap;
@@ -53,7 +55,8 @@ use tracing::{info, warn};
5355
use types::{ChainSpec, ConfigAndPreset, EthSpec};
5456
use validator_dir::Builder as ValidatorDirBuilder;
5557
use validator_services::block_service::BlockService;
56-
use warp::{sse::Event, Filter};
58+
use warp::{reply::Response, sse::Event, Filter};
59+
use warp_utils::reject::convert_rejection;
5760
use warp_utils::task::blocking_json_task;
5861

5962
#[derive(Debug)]
@@ -102,6 +105,7 @@ pub struct Config {
102105
pub allow_keystore_export: bool,
103106
pub store_passwords_in_secrets_dir: bool,
104107
pub http_token_path: PathBuf,
108+
pub bn_long_timeouts: bool,
105109
}
106110

107111
impl Default for Config {
@@ -121,6 +125,7 @@ impl Default for Config {
121125
allow_keystore_export: false,
122126
store_passwords_in_secrets_dir: false,
123127
http_token_path,
128+
bn_long_timeouts: false,
124129
}
125130
}
126131
}
@@ -147,6 +152,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
147152
let config = &ctx.config;
148153
let allow_keystore_export = config.allow_keystore_export;
149154
let store_passwords_in_secrets_dir = config.store_passwords_in_secrets_dir;
155+
let use_long_timeouts = config.bn_long_timeouts;
150156

151157
// Configure CORS.
152158
let cors_builder = {
@@ -839,6 +845,59 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
839845
})
840846
});
841847

848+
// POST /lighthouse/beacon/update
849+
let post_lighthouse_beacon_update = warp::path("lighthouse")
850+
.and(warp::path("beacon"))
851+
.and(warp::path("update"))
852+
.and(warp::path::end())
853+
.and(warp::body::json())
854+
.and(block_service_filter.clone())
855+
.then(
856+
move |request: UpdateCandidatesRequest,
857+
block_service: BlockService<LighthouseValidatorStore<T, E>, T>| async move {
858+
async fn parse_urls(urls: &[String]) -> Result<Vec<SensitiveUrl>, Response> {
859+
match urls
860+
.iter()
861+
.map(|url| SensitiveUrl::parse(url).map_err(|e| e.to_string()))
862+
.collect()
863+
{
864+
Ok(sensitive_urls) => Ok(sensitive_urls),
865+
Err(_) => Err(convert_rejection::<Infallible>(Err(
866+
warp_utils::reject::custom_bad_request(
867+
"one or more urls could not be parsed".to_string(),
868+
),
869+
))
870+
.await),
871+
}
872+
}
873+
874+
let beacons: Vec<SensitiveUrl> = match parse_urls(&request.beacon_nodes).await {
875+
Ok(new_beacons) => {
876+
match block_service
877+
.beacon_nodes
878+
.update_candidates_list(new_beacons, use_long_timeouts)
879+
.await
880+
{
881+
Ok(beacons) => beacons,
882+
Err(e) => {
883+
return convert_rejection::<Infallible>(Err(
884+
warp_utils::reject::custom_bad_request(e.to_string()),
885+
))
886+
.await
887+
}
888+
}
889+
}
890+
Err(e) => return e,
891+
};
892+
893+
let response: UpdateCandidatesResponse = UpdateCandidatesResponse {
894+
new_beacon_nodes_list: beacons.iter().map(|surl| surl.to_string()).collect(),
895+
};
896+
897+
blocking_json_task(move || Ok(api_types::GenericResponse::from(response))).await
898+
},
899+
);
900+
842901
// Standard key-manager endpoints.
843902
let eth_v1 = warp::path("eth").and(warp::path("v1"));
844903
let std_keystores = eth_v1.and(warp::path("keystores")).and(warp::path::end());
@@ -1316,6 +1375,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
13161375
.or(post_std_keystores)
13171376
.or(post_std_remotekeys)
13181377
.or(post_graffiti)
1378+
.or(post_lighthouse_beacon_update)
13191379
.recover(warp_utils::reject::handle_rejection),
13201380
))
13211381
.or(warp::patch()

validator_client/http_api/src/test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ impl ApiTester {
173173
allow_keystore_export: true,
174174
store_passwords_in_secrets_dir: false,
175175
http_token_path: tempdir().unwrap().path().join(PK_FILENAME),
176+
bn_long_timeouts: false,
176177
}
177178
}
178179

validator_client/http_api/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ impl ApiTester {
126126
allow_keystore_export: true,
127127
store_passwords_in_secrets_dir: false,
128128
http_token_path: token_path,
129+
bn_long_timeouts: false,
129130
},
130131
sse_logging_components: None,
131132
slot_clock: slot_clock.clone(),

validator_client/src/lib.rs

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,6 @@ const RETRY_DELAY: Duration = Duration::from_secs(2);
5353
/// The time between polls when waiting for genesis.
5454
const WAITING_FOR_GENESIS_POLL_TIME: Duration = Duration::from_secs(12);
5555

56-
/// Specific timeout constants for HTTP requests involved in different validator duties.
57-
/// This can help ensure that proper endpoint fallback occurs.
58-
const HTTP_ATTESTATION_TIMEOUT_QUOTIENT: u32 = 4;
59-
const HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
60-
const HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT: u32 = 24;
61-
const HTTP_LIVENESS_TIMEOUT_QUOTIENT: u32 = 4;
62-
const HTTP_PROPOSAL_TIMEOUT_QUOTIENT: u32 = 2;
63-
const HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
64-
const HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT: u32 = 4;
65-
const HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
66-
const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4;
67-
const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4;
68-
const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4;
69-
const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4;
70-
const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4;
71-
7256
const DOPPELGANGER_SERVICE_NAME: &str = "doppelganger";
7357

7458
type ValidatorStore<E> = LighthouseValidatorStore<SystemTimeSlotClock, E>;
@@ -291,24 +275,7 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
291275
// Use quicker timeouts if a fallback beacon node exists.
292276
let timeouts = if i < last_beacon_node_index && !config.use_long_timeouts {
293277
info!("Fallback endpoints are available, using optimized timeouts.");
294-
Timeouts {
295-
attestation: slot_duration / HTTP_ATTESTATION_TIMEOUT_QUOTIENT,
296-
attester_duties: slot_duration / HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT,
297-
attestation_subscriptions: slot_duration
298-
/ HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT,
299-
liveness: slot_duration / HTTP_LIVENESS_TIMEOUT_QUOTIENT,
300-
proposal: slot_duration / HTTP_PROPOSAL_TIMEOUT_QUOTIENT,
301-
proposer_duties: slot_duration / HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT,
302-
sync_committee_contribution: slot_duration
303-
/ HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT,
304-
sync_duties: slot_duration / HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT,
305-
get_beacon_blocks_ssz: slot_duration
306-
/ HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT,
307-
get_debug_beacon_states: slot_duration / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT,
308-
get_deposit_snapshot: slot_duration / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT,
309-
get_validator_block: slot_duration / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT,
310-
default: slot_duration / HTTP_DEFAULT_TIMEOUT_QUOTIENT,
311-
}
278+
Timeouts::use_optimized_timeouts(slot_duration)
312279
} else {
313280
Timeouts::set_all(slot_duration.saturating_mul(config.long_timeouts_multiplier))
314281
};

0 commit comments

Comments
 (0)