From 817c22ebf5ea7ee5f8ebe71353645dc8fc56abdf Mon Sep 17 00:00:00 2001 From: Mac L Date: Fri, 9 Dec 2022 09:20:13 +0000 Subject: [PATCH] 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`. --- book/src/api-vc-endpoints.md | 25 ++++++++++ validator_client/src/block_service.rs | 20 +++----- validator_client/src/http_api/mod.rs | 50 ++++++++++++++++++- validator_client/src/http_api/tests.rs | 2 + .../src/initialized_validators.rs | 9 ++++ validator_client/src/lib.rs | 28 ++++++++++- 6 files changed, 118 insertions(+), 16 deletions(-) diff --git a/book/src/api-vc-endpoints.md b/book/src/api-vc-endpoints.md index 76cffc0e4f5..80a14ae7710 100644 --- a/book/src/api-vc-endpoints.md +++ b/book/src/api-vc-endpoints.md @@ -117,6 +117,31 @@ Returns information regarding the health of the host machine. } ``` +## `GET /lighthouse/ui/graffiti` + +Returns the graffiti that will be used for the next block proposal of each validator. + +### HTTP Specification + +| Property | Specification | +|-------------------|--------------------------------------------| +| Path | `/lighthouse/ui/graffiti` | +| Method | GET | +| Required Headers | [`Authorization`](./api-vc-auth-header.md) | +| Typical Responses | 200 | + +### Example Response Body + +```json +{ + "data": { + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e": "mr f was here", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b": "mr v was here", + "0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f": null + } +} +``` + ## `GET /lighthouse/spec` Returns the Ethereum proof-of-stake consensus specification loaded for this validator. diff --git a/validator_client/src/block_service.rs b/validator_client/src/block_service.rs index 42f62681980..c7b8d856e65 100644 --- a/validator_client/src/block_service.rs +++ b/validator_client/src/block_service.rs @@ -1,6 +1,7 @@ use crate::beacon_node_fallback::{Error as FallbackError, Errors}; use crate::{ beacon_node_fallback::{BeaconNodeFallback, RequireSynced}, + determine_graffiti, graffiti_file::GraffitiFile, OfflineOnFailure, }; @@ -300,18 +301,13 @@ impl BlockService { })? .into(); - let graffiti = self - .graffiti_file - .clone() - .and_then(|mut g| match g.load_graffiti(&validator_pubkey) { - Ok(g) => g, - Err(e) => { - warn!(log, "Failed to read graffiti file"; "error" => ?e); - None - } - }) - .or_else(|| self.validator_store.graffiti(&validator_pubkey)) - .or(self.graffiti); + let graffiti = determine_graffiti( + &validator_pubkey, + log, + self.graffiti_file.clone(), + self.validator_store.graffiti(&validator_pubkey), + self.graffiti, + ); let randao_reveal_ref = &randao_reveal; let self_ref = &self; diff --git a/validator_client/src/http_api/mod.rs b/validator_client/src/http_api/mod.rs index df5d0c606e9..600e7a4c683 100644 --- a/validator_client/src/http_api/mod.rs +++ b/validator_client/src/http_api/mod.rs @@ -4,7 +4,7 @@ mod keystores; mod remotekeys; mod tests; -use crate::ValidatorStore; +use crate::{determine_graffiti, GraffitiFile, ValidatorStore}; use account_utils::{ mnemonic_from_phrase, validator_definitions::{SigningDefinition, ValidatorDefinition, Web3SignerDefinition}, @@ -13,13 +13,14 @@ pub use api_secret::ApiSecret; use create_validator::{create_validators_mnemonic, create_validators_web3signer}; use eth2::lighthouse_vc::{ std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse}, - types::{self as api_types, GenericResponse, PublicKey, PublicKeyBytes}, + types::{self as api_types, GenericResponse, Graffiti, PublicKey, PublicKeyBytes}, }; use lighthouse_version::version_with_platform; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use slog::{crit, info, warn, Logger}; use slot_clock::SlotClock; +use std::collections::HashMap; use std::future::Future; use std::marker::PhantomData; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -65,6 +66,8 @@ pub struct Context { pub api_secret: ApiSecret, pub validator_store: Option>>, pub validator_dir: Option, + pub graffiti_file: Option, + pub graffiti_flag: Option, pub spec: ChainSpec, pub config: Config, pub log: Logger, @@ -177,6 +180,12 @@ pub fn serve( }) }); + let inner_graffiti_file = ctx.graffiti_file.clone(); + let graffiti_file_filter = warp::any().map(move || inner_graffiti_file.clone()); + + let inner_graffiti_flag = ctx.graffiti_flag; + let graffiti_flag_filter = warp::any().map(move || inner_graffiti_flag); + let inner_ctx = ctx.clone(); let log_filter = warp::any().map(move || inner_ctx.log.clone()); @@ -329,6 +338,42 @@ pub fn serve( }) }); + let get_lighthouse_ui_graffiti = warp::path("lighthouse") + .and(warp::path("ui")) + .and(warp::path("graffiti")) + .and(warp::path::end()) + .and(validator_store_filter.clone()) + .and(graffiti_file_filter) + .and(graffiti_flag_filter) + .and(signer.clone()) + .and(log_filter.clone()) + .and_then( + |validator_store: Arc>, + graffiti_file: Option, + graffiti_flag: Option, + signer, + log| { + blocking_signed_json_task(signer, move || { + let mut result = HashMap::new(); + for (key, graffiti_definition) in validator_store + .initialized_validators() + .read() + .get_all_validators_graffiti() + { + let graffiti = determine_graffiti( + key, + &log, + graffiti_file.clone(), + graffiti_definition, + graffiti_flag, + ); + result.insert(key.to_string(), graffiti.map(|g| g.as_utf8_lossy())); + } + Ok(api_types::GenericResponse::from(result)) + }) + }, + ); + // POST lighthouse/validators/ let post_validators = warp::path("lighthouse") .and(warp::path("validators")) @@ -945,6 +990,7 @@ pub fn serve( .or(get_lighthouse_validators) .or(get_lighthouse_validators_pubkey) .or(get_lighthouse_ui_health) + .or(get_lighthouse_ui_graffiti) .or(get_fee_recipient) .or(get_gas_limit) .or(get_std_keystores) diff --git a/validator_client/src/http_api/tests.rs b/validator_client/src/http_api/tests.rs index b121dda5b1a..5aa24a2b022 100644 --- a/validator_client/src/http_api/tests.rs +++ b/validator_client/src/http_api/tests.rs @@ -120,6 +120,8 @@ impl ApiTester { api_secret, validator_dir: Some(validator_dir.path().into()), validator_store: Some(validator_store.clone()), + graffiti_file: None, + graffiti_flag: Some(Graffiti::default()), spec: E::default_spec(), config: HttpConfig { enabled: true, diff --git a/validator_client/src/initialized_validators.rs b/validator_client/src/initialized_validators.rs index 8d9fbe281fc..e8fe6ff2ff9 100644 --- a/validator_client/src/initialized_validators.rs +++ b/validator_client/src/initialized_validators.rs @@ -634,6 +634,15 @@ impl InitializedValidators { self.validators.get(public_key).and_then(|v| v.graffiti) } + /// Returns a `HashMap` of `public_key` -> `graffiti` for all initialized validators. + pub fn get_all_validators_graffiti(&self) -> HashMap<&PublicKeyBytes, Option> { + let mut result = HashMap::new(); + for public_key in self.validators.keys() { + result.insert(public_key, self.graffiti(public_key)); + } + result + } + /// Returns the `suggested_fee_recipient` for a given public key specified in the /// `ValidatorDefinitions`. pub fn suggested_fee_recipient(&self, public_key: &PublicKeyBytes) -> Option
{ diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 1f869562d19..819efec93c9 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -30,13 +30,14 @@ use crate::beacon_node_fallback::{ RequireSynced, }; use crate::doppelganger_service::DoppelgangerService; +use crate::graffiti_file::GraffitiFile; use account_utils::validator_definitions::ValidatorDefinitions; use attestation_service::{AttestationService, AttestationServiceBuilder}; use block_service::{BlockService, BlockServiceBuilder}; use clap::ArgMatches; use duties_service::DutiesService; use environment::RuntimeContext; -use eth2::{reqwest::ClientBuilder, BeaconNodeHttpClient, StatusCode, Timeouts}; +use eth2::{reqwest::ClientBuilder, types::Graffiti, BeaconNodeHttpClient, StatusCode, Timeouts}; use http_api::ApiSecret; use notifier::spawn_notifier; use parking_lot::RwLock; @@ -57,7 +58,7 @@ use tokio::{ sync::mpsc, time::{sleep, Duration}, }; -use types::{EthSpec, Hash256}; +use types::{EthSpec, Hash256, PublicKeyBytes}; use validator_store::ValidatorStore; /// The interval between attempts to contact the beacon node during startup. @@ -526,6 +527,8 @@ impl ProductionValidatorClient { api_secret, validator_store: Some(self.validator_store.clone()), validator_dir: Some(self.config.validator_dir.clone()), + graffiti_file: self.config.graffiti_file.clone(), + graffiti_flag: self.config.graffiti, spec: self.context.eth2_config.spec.clone(), config: self.config.http_api.clone(), log: log.clone(), @@ -726,3 +729,24 @@ pub fn load_pem_certificate>(pem_path: P) -> Result, + validator_definition_graffiti: Option, + graffiti_flag: Option, +) -> Option { + graffiti_file + .and_then(|mut g| match g.load_graffiti(validator_pubkey) { + Ok(g) => g, + Err(e) => { + warn!(log, "Failed to read graffiti file"; "error" => ?e); + None + } + }) + .or(validator_definition_graffiti) + .or(graffiti_flag) +}