Skip to content

Commit

Permalink
Add API endpoint to get VC graffiti (#3779)
Browse files Browse the repository at this point in the history
## 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`.
  • Loading branch information
macladson committed Dec 9, 2022
1 parent 80dd615 commit 979b73c
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 16 deletions.
25 changes: 25 additions & 0 deletions book/src/api-vc-endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 8 additions & 12 deletions validator_client/src/block_service.rs
Original file line number Diff line number Diff line change
@@ -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,
};
Expand Down Expand Up @@ -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;
Expand Down
50 changes: 48 additions & 2 deletions validator_client/src/http_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions validator_client/src/http_api/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions validator_client/src/initialized_validators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
28 changes: 26 additions & 2 deletions validator_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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)
}

0 comments on commit 979b73c

Please sign in to comment.