Skip to content

Commit 979b73c

Browse files
committed
Add API endpoint to get VC graffiti (#3779)
## Issue Addressed #3766 ## Proposed Changes Adds an endpoint to get the graffiti that will be used for the next block proposal for each validator. ## Usage ```bash curl -H "Authorization: Bearer api-token" http://localhost:9095/lighthouse/ui/graffiti | jq ``` ```json { "data": { "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e": "mr f was here", "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b": "mr v was here", "0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f": null } } ``` ## Additional Info This will only return graffiti that the validator client knows about. That is from these 3 sources: 1. Graffiti File 2. validator_definitions.yml 3. The `--graffiti` flag on the VC If the graffiti is set on the BN, it will not be returned. This may warrant an additional endpoint on the BN side which can be used in the event the endpoint returns `null`.
1 parent 80dd615 commit 979b73c

File tree

6 files changed

+118
-16
lines changed

6 files changed

+118
-16
lines changed

book/src/api-vc-endpoints.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,31 @@ Returns information regarding the health of the host machine.
117117
}
118118
```
119119

120+
## `GET /lighthouse/ui/graffiti`
121+
122+
Returns the graffiti that will be used for the next block proposal of each validator.
123+
124+
### HTTP Specification
125+
126+
| Property | Specification |
127+
|-------------------|--------------------------------------------|
128+
| Path | `/lighthouse/ui/graffiti` |
129+
| Method | GET |
130+
| Required Headers | [`Authorization`](./api-vc-auth-header.md) |
131+
| Typical Responses | 200 |
132+
133+
### Example Response Body
134+
135+
```json
136+
{
137+
"data": {
138+
"0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e": "mr f was here",
139+
"0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b": "mr v was here",
140+
"0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f": null
141+
}
142+
}
143+
```
144+
120145
## `GET /lighthouse/spec`
121146

122147
Returns the Ethereum proof-of-stake consensus specification loaded for this validator.

validator_client/src/block_service.rs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::beacon_node_fallback::{Error as FallbackError, Errors};
22
use crate::{
33
beacon_node_fallback::{BeaconNodeFallback, RequireSynced},
4+
determine_graffiti,
45
graffiti_file::GraffitiFile,
56
OfflineOnFailure,
67
};
@@ -298,18 +299,13 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
298299
})?
299300
.into();
300301

301-
let graffiti = self
302-
.graffiti_file
303-
.clone()
304-
.and_then(|mut g| match g.load_graffiti(&validator_pubkey) {
305-
Ok(g) => g,
306-
Err(e) => {
307-
warn!(log, "Failed to read graffiti file"; "error" => ?e);
308-
None
309-
}
310-
})
311-
.or_else(|| self.validator_store.graffiti(&validator_pubkey))
312-
.or(self.graffiti);
302+
let graffiti = determine_graffiti(
303+
&validator_pubkey,
304+
log,
305+
self.graffiti_file.clone(),
306+
self.validator_store.graffiti(&validator_pubkey),
307+
self.graffiti,
308+
);
313309

314310
let randao_reveal_ref = &randao_reveal;
315311
let self_ref = &self;

validator_client/src/http_api/mod.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod keystores;
44
mod remotekeys;
55
mod tests;
66

7-
use crate::ValidatorStore;
7+
use crate::{determine_graffiti, GraffitiFile, ValidatorStore};
88
use account_utils::{
99
mnemonic_from_phrase,
1010
validator_definitions::{SigningDefinition, ValidatorDefinition, Web3SignerDefinition},
@@ -13,13 +13,14 @@ pub use api_secret::ApiSecret;
1313
use create_validator::{create_validators_mnemonic, create_validators_web3signer};
1414
use eth2::lighthouse_vc::{
1515
std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse},
16-
types::{self as api_types, GenericResponse, PublicKey, PublicKeyBytes},
16+
types::{self as api_types, GenericResponse, Graffiti, PublicKey, PublicKeyBytes},
1717
};
1818
use lighthouse_version::version_with_platform;
1919
use parking_lot::RwLock;
2020
use serde::{Deserialize, Serialize};
2121
use slog::{crit, info, warn, Logger};
2222
use slot_clock::SlotClock;
23+
use std::collections::HashMap;
2324
use std::future::Future;
2425
use std::marker::PhantomData;
2526
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
@@ -65,6 +66,8 @@ pub struct Context<T: SlotClock, E: EthSpec> {
6566
pub api_secret: ApiSecret,
6667
pub validator_store: Option<Arc<ValidatorStore<T, E>>>,
6768
pub validator_dir: Option<PathBuf>,
69+
pub graffiti_file: Option<GraffitiFile>,
70+
pub graffiti_flag: Option<Graffiti>,
6871
pub spec: ChainSpec,
6972
pub config: Config,
7073
pub log: Logger,
@@ -177,6 +180,12 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
177180
})
178181
});
179182

183+
let inner_graffiti_file = ctx.graffiti_file.clone();
184+
let graffiti_file_filter = warp::any().map(move || inner_graffiti_file.clone());
185+
186+
let inner_graffiti_flag = ctx.graffiti_flag;
187+
let graffiti_flag_filter = warp::any().map(move || inner_graffiti_flag);
188+
180189
let inner_ctx = ctx.clone();
181190
let log_filter = warp::any().map(move || inner_ctx.log.clone());
182191

@@ -329,6 +338,42 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
329338
})
330339
});
331340

341+
let get_lighthouse_ui_graffiti = warp::path("lighthouse")
342+
.and(warp::path("ui"))
343+
.and(warp::path("graffiti"))
344+
.and(warp::path::end())
345+
.and(validator_store_filter.clone())
346+
.and(graffiti_file_filter)
347+
.and(graffiti_flag_filter)
348+
.and(signer.clone())
349+
.and(log_filter.clone())
350+
.and_then(
351+
|validator_store: Arc<ValidatorStore<T, E>>,
352+
graffiti_file: Option<GraffitiFile>,
353+
graffiti_flag: Option<Graffiti>,
354+
signer,
355+
log| {
356+
blocking_signed_json_task(signer, move || {
357+
let mut result = HashMap::new();
358+
for (key, graffiti_definition) in validator_store
359+
.initialized_validators()
360+
.read()
361+
.get_all_validators_graffiti()
362+
{
363+
let graffiti = determine_graffiti(
364+
key,
365+
&log,
366+
graffiti_file.clone(),
367+
graffiti_definition,
368+
graffiti_flag,
369+
);
370+
result.insert(key.to_string(), graffiti.map(|g| g.as_utf8_lossy()));
371+
}
372+
Ok(api_types::GenericResponse::from(result))
373+
})
374+
},
375+
);
376+
332377
// POST lighthouse/validators/
333378
let post_validators = warp::path("lighthouse")
334379
.and(warp::path("validators"))
@@ -945,6 +990,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
945990
.or(get_lighthouse_validators)
946991
.or(get_lighthouse_validators_pubkey)
947992
.or(get_lighthouse_ui_health)
993+
.or(get_lighthouse_ui_graffiti)
948994
.or(get_fee_recipient)
949995
.or(get_gas_limit)
950996
.or(get_std_keystores)

validator_client/src/http_api/tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ impl ApiTester {
120120
api_secret,
121121
validator_dir: Some(validator_dir.path().into()),
122122
validator_store: Some(validator_store.clone()),
123+
graffiti_file: None,
124+
graffiti_flag: Some(Graffiti::default()),
123125
spec: E::default_spec(),
124126
config: HttpConfig {
125127
enabled: true,

validator_client/src/initialized_validators.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,15 @@ impl InitializedValidators {
634634
self.validators.get(public_key).and_then(|v| v.graffiti)
635635
}
636636

637+
/// Returns a `HashMap` of `public_key` -> `graffiti` for all initialized validators.
638+
pub fn get_all_validators_graffiti(&self) -> HashMap<&PublicKeyBytes, Option<Graffiti>> {
639+
let mut result = HashMap::new();
640+
for public_key in self.validators.keys() {
641+
result.insert(public_key, self.graffiti(public_key));
642+
}
643+
result
644+
}
645+
637646
/// Returns the `suggested_fee_recipient` for a given public key specified in the
638647
/// `ValidatorDefinitions`.
639648
pub fn suggested_fee_recipient(&self, public_key: &PublicKeyBytes) -> Option<Address> {

validator_client/src/lib.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ use crate::beacon_node_fallback::{
3030
RequireSynced,
3131
};
3232
use crate::doppelganger_service::DoppelgangerService;
33+
use crate::graffiti_file::GraffitiFile;
3334
use account_utils::validator_definitions::ValidatorDefinitions;
3435
use attestation_service::{AttestationService, AttestationServiceBuilder};
3536
use block_service::{BlockService, BlockServiceBuilder};
3637
use clap::ArgMatches;
3738
use duties_service::DutiesService;
3839
use environment::RuntimeContext;
39-
use eth2::{reqwest::ClientBuilder, BeaconNodeHttpClient, StatusCode, Timeouts};
40+
use eth2::{reqwest::ClientBuilder, types::Graffiti, BeaconNodeHttpClient, StatusCode, Timeouts};
4041
use http_api::ApiSecret;
4142
use notifier::spawn_notifier;
4243
use parking_lot::RwLock;
@@ -57,7 +58,7 @@ use tokio::{
5758
sync::mpsc,
5859
time::{sleep, Duration},
5960
};
60-
use types::{EthSpec, Hash256};
61+
use types::{EthSpec, Hash256, PublicKeyBytes};
6162
use validator_store::ValidatorStore;
6263

6364
/// The interval between attempts to contact the beacon node during startup.
@@ -526,6 +527,8 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
526527
api_secret,
527528
validator_store: Some(self.validator_store.clone()),
528529
validator_dir: Some(self.config.validator_dir.clone()),
530+
graffiti_file: self.config.graffiti_file.clone(),
531+
graffiti_flag: self.config.graffiti,
529532
spec: self.context.eth2_config.spec.clone(),
530533
config: self.config.http_api.clone(),
531534
log: log.clone(),
@@ -726,3 +729,24 @@ pub fn load_pem_certificate<P: AsRef<Path>>(pem_path: P) -> Result<Certificate,
726729
.map_err(|e| format!("Unable to read certificate file: {}", e))?;
727730
Certificate::from_pem(&buf).map_err(|e| format!("Unable to parse certificate: {}", e))
728731
}
732+
733+
// Given the various graffiti control methods, determine the graffiti that will be used for
734+
// the next block produced by the validator with the given public key.
735+
pub fn determine_graffiti(
736+
validator_pubkey: &PublicKeyBytes,
737+
log: &Logger,
738+
graffiti_file: Option<GraffitiFile>,
739+
validator_definition_graffiti: Option<Graffiti>,
740+
graffiti_flag: Option<Graffiti>,
741+
) -> Option<Graffiti> {
742+
graffiti_file
743+
.and_then(|mut g| match g.load_graffiti(validator_pubkey) {
744+
Ok(g) => g,
745+
Err(e) => {
746+
warn!(log, "Failed to read graffiti file"; "error" => ?e);
747+
None
748+
}
749+
})
750+
.or(validator_definition_graffiti)
751+
.or(graffiti_flag)
752+
}

0 commit comments

Comments
 (0)