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`.
This commit is contained in:
parent
80dd615fff
commit
979b73c9b6
@ -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.
|
||||
|
@ -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,
|
||||
};
|
||||
@ -298,18 +299,13 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
||||
})?
|
||||
.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;
|
||||
|
@ -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<T: SlotClock, E: EthSpec> {
|
||||
pub api_secret: ApiSecret,
|
||||
pub validator_store: Option<Arc<ValidatorStore<T, E>>>,
|
||||
pub validator_dir: Option<PathBuf>,
|
||||
pub graffiti_file: Option<GraffitiFile>,
|
||||
pub graffiti_flag: Option<Graffiti>,
|
||||
pub spec: ChainSpec,
|
||||
pub config: Config,
|
||||
pub log: Logger,
|
||||
@ -177,6 +180,12 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
})
|
||||
});
|
||||
|
||||
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<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
})
|
||||
});
|
||||
|
||||
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<ValidatorStore<T, E>>,
|
||||
graffiti_file: Option<GraffitiFile>,
|
||||
graffiti_flag: Option<Graffiti>,
|
||||
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<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.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)
|
||||
|
@ -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,
|
||||
|
@ -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<Graffiti>> {
|
||||
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<Address> {
|
||||
|
@ -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<T: EthSpec> ProductionValidatorClient<T> {
|
||||
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<P: AsRef<Path>>(pem_path: P) -> Result<Certificate,
|
||||
.map_err(|e| format!("Unable to read certificate file: {}", e))?;
|
||||
Certificate::from_pem(&buf).map_err(|e| format!("Unable to parse certificate: {}", e))
|
||||
}
|
||||
|
||||
// Given the various graffiti control methods, determine the graffiti that will be used for
|
||||
// the next block produced by the validator with the given public key.
|
||||
pub fn determine_graffiti(
|
||||
validator_pubkey: &PublicKeyBytes,
|
||||
log: &Logger,
|
||||
graffiti_file: Option<GraffitiFile>,
|
||||
validator_definition_graffiti: Option<Graffiti>,
|
||||
graffiti_flag: Option<Graffiti>,
|
||||
) -> Option<Graffiti> {
|
||||
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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user