Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement graffiti management API #4951

Merged
merged 17 commits into from
Dec 7, 2023
61 changes: 61 additions & 0 deletions common/eth2/src/lighthouse_vc/http_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,32 @@ impl ValidatorClientHttpClient {
ok_or_error(response).await
}

/// Perform a HTTP DELETE request, returning the `Response` for further processing.
async fn delete_response<U: IntoUrl>(&self, url: U) -> Result<Response, Error> {
let response = self
.client
.delete(url)
.headers(self.headers()?)
.send()
.await
.map_err(Error::from)?;
ok_or_error(response).await
}

async fn get<T: DeserializeOwned, U: IntoUrl>(&self, url: U) -> Result<T, Error> {
let response = self.get_response(url).await?;
self.signed_json(response).await
}

async fn delete<U: IntoUrl>(&self, url: U) -> Result<(), Error> {
let response = self.delete_response(url).await?;
if response.status().is_success() {
Ok(())
} else {
Err(Error::StatusCode(response.status()))
}
}

async fn get_unsigned<T: DeserializeOwned, U: IntoUrl>(&self, url: U) -> Result<T, Error> {
self.get_response(url)
.await?
Expand Down Expand Up @@ -537,6 +558,18 @@ impl ValidatorClientHttpClient {
Ok(url)
}

fn make_graffiti_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
let mut url = self.server.full.clone();
url.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
.push("v1")
.push("validator")
.push(&pubkey.to_string())
.push("graffiti");
Ok(url)
}

fn make_gas_limit_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
let mut url = self.server.full.clone();
url.path_segments_mut()
Expand Down Expand Up @@ -684,6 +717,34 @@ impl ValidatorClientHttpClient {

self.post(path, &()).await
}

/// `GET /eth/v1/validator/{pubkey}/graffiti`
pub async fn get_graffiti(
&self,
pubkey: &PublicKeyBytes,
) -> Result<GetGraffitiResponse, Error> {
let url = self.make_graffiti_url(pubkey)?;
self.get(url)
.await
.map(|generic: GenericResponse<GetGraffitiResponse>| generic.data)
}

/// `POST /eth/v1/validator/{pubkey}/graffiti`
pub async fn set_graffiti(
&self,
pubkey: &PublicKeyBytes,
graffiti: GraffitiString,
) -> Result<(), Error> {
let url = self.make_graffiti_url(pubkey)?;
let set_graffiti_request = SetGraffitiRequest { graffiti };
self.post(url, &set_graffiti_request).await
}

/// `DELETE /eth/v1/validator/{pubkey}/graffiti`
pub async fn delete_graffiti(&self, pubkey: &PublicKeyBytes) -> Result<(), Error> {
let url = self.make_graffiti_url(pubkey)?;
self.delete(url).await
}
}

/// Returns `Ok(response)` if the response is a `200 OK` response or a
Expand Down
8 changes: 7 additions & 1 deletion common/eth2/src/lighthouse_vc/std_types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use account_utils::ZeroizeString;
use eth2_keystore::Keystore;
use serde::{Deserialize, Serialize};
use types::{Address, PublicKeyBytes};
use types::{Address, Graffiti, PublicKeyBytes};

pub use slashing_protection::interchange::Interchange;

Expand Down Expand Up @@ -172,3 +172,9 @@ pub enum DeleteRemotekeyStatus {
pub struct DeleteRemotekeysResponse {
pub data: Vec<Status<DeleteRemotekeyStatus>>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct GetGraffitiResponse {
pub pubkey: PublicKeyBytes,
pub graffiti: Graffiti,
}
5 changes: 5 additions & 0 deletions common/eth2/src/lighthouse_vc/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,8 @@ pub struct SingleExportKeystoresResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub validating_keystore_password: Option<ZeroizeString>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SetGraffitiRequest {
pub graffiti: GraffitiString,
}
80 changes: 80 additions & 0 deletions validator_client/src/http_api/graffiti.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::validator_store::ValidatorStore;
use bls::PublicKey;
use slot_clock::SlotClock;
use std::sync::Arc;
use types::{graffiti::GraffitiString, EthSpec, Graffiti};

pub fn get_graffiti<T: 'static + SlotClock + Clone, E: EthSpec>(
validator_pubkey: PublicKey,
validator_store: Arc<ValidatorStore<T, E>>,
graffiti_flag: Option<Graffiti>,
) -> Result<Graffiti, warp::Rejection> {
let initialized_validators_rw_lock = validator_store.initialized_validators();
let initialized_validators = initialized_validators_rw_lock.read();
match initialized_validators.validator(&validator_pubkey.compress()) {
None => Err(warp_utils::reject::custom_not_found(
"The key was not found on the server".to_string(),
)),
Some(_) => {
let Some(graffiti) = initialized_validators.graffiti(&validator_pubkey.into()) else {
return graffiti_flag.ok_or(warp_utils::reject::custom_server_error(
"No graffiti found, unable to return the process-wide default".to_string(),
));
};
Ok(graffiti)
}
}
}

pub fn set_graffiti<T: 'static + SlotClock + Clone, E: EthSpec>(
validator_pubkey: PublicKey,
graffiti: GraffitiString,
validator_store: Arc<ValidatorStore<T, E>>,
) -> Result<(), warp::Rejection> {
let initialized_validators_rw_lock = validator_store.initialized_validators();
let mut initialized_validators = initialized_validators_rw_lock.write();
match initialized_validators.validator(&validator_pubkey.compress()) {
None => Err(warp_utils::reject::custom_not_found(
"The key was not found on the server, nothing to update".to_string(),
)),
Some(initialized_validator) => {
if initialized_validator.get_graffiti() == Some(graffiti.clone().into()) {
Ok(())
} else {
initialized_validators
.set_graffiti(&validator_pubkey, graffiti)
.map_err(|_| {
warp_utils::reject::custom_server_error(
"A graffiti was found, but failed to be updated.".to_string(),
)
})
}
}
}
}

pub fn delete_graffiti<T: 'static + SlotClock + Clone, E: EthSpec>(
validator_pubkey: PublicKey,
validator_store: Arc<ValidatorStore<T, E>>,
) -> Result<(), warp::Rejection> {
let initialized_validators_rw_lock = validator_store.initialized_validators();
let mut initialized_validators = initialized_validators_rw_lock.write();
match initialized_validators.validator(&validator_pubkey.compress()) {
None => Err(warp_utils::reject::custom_not_found(
"The key was not found on the server, nothing to delete".to_string(),
)),
Some(initialized_validator) => {
if initialized_validator.get_graffiti().is_none() {
Ok(())
} else {
initialized_validators
.delete_graffiti(&validator_pubkey)
.map_err(|_| {
warp_utils::reject::custom_server_error(
"A graffiti was found, but failed to be removed.".to_string(),
)
})
}
}
}
}
93 changes: 91 additions & 2 deletions validator_client/src/http_api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
mod api_secret;
mod create_signed_voluntary_exit;
mod create_validator;
mod graffiti;
mod keystores;
mod remotekeys;
mod tests;

pub mod test_utils;

use crate::http_api::graffiti::{delete_graffiti, get_graffiti, set_graffiti};

use crate::http_api::create_signed_voluntary_exit::create_signed_voluntary_exit;
use crate::{determine_graffiti, GraffitiFile, ValidatorStore};
use account_utils::{
Expand All @@ -19,7 +22,10 @@ use create_validator::{
};
use eth2::lighthouse_vc::{
std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse},
types::{self as api_types, GenericResponse, Graffiti, PublicKey, PublicKeyBytes},
types::{
self as api_types, GenericResponse, GetGraffitiResponse, Graffiti, PublicKey,
PublicKeyBytes, SetGraffitiRequest,
},
};
use lighthouse_version::version_with_platform;
use logging::SSELoggingComponents;
Expand Down Expand Up @@ -653,7 +659,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
.and(warp::path::end())
.and(warp::body::json())
.and(validator_store_filter.clone())
.and(graffiti_file_filter)
.and(graffiti_file_filter.clone())
.and(signer.clone())
.and(task_executor_filter.clone())
.and_then(
Expand Down Expand Up @@ -1028,6 +1034,86 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
},
);

// GET /eth/v1/validator/{pubkey}/graffiti
let get_graffiti = eth_v1
.and(warp::path("validator"))
.and(warp::path::param::<PublicKey>())
.and(warp::path("graffiti"))
.and(warp::path::end())
.and(validator_store_filter.clone())
.and(graffiti_flag_filter)
.and(signer.clone())
.and_then(
|pubkey: PublicKey,
validator_store: Arc<ValidatorStore<T, E>>,
graffiti_flag: Option<Graffiti>,
signer| {
blocking_signed_json_task(signer, move || {
let graffiti = get_graffiti(pubkey.clone(), validator_store, graffiti_flag)?;
Ok(GenericResponse::from(GetGraffitiResponse {
pubkey: pubkey.into(),
graffiti,
}))
})
},
);

// POST /eth/v1/validator/{pubkey}/graffiti
let post_graffiti = eth_v1
.and(warp::path("validator"))
.and(warp::path::param::<PublicKey>())
.and(warp::path("graffiti"))
.and(warp::body::json())
.and(warp::path::end())
.and(validator_store_filter.clone())
.and(graffiti_file_filter.clone())
.and(signer.clone())
.and_then(
|pubkey: PublicKey,
query: SetGraffitiRequest,
validator_store: Arc<ValidatorStore<T, E>>,
graffiti_file: Option<GraffitiFile>,
signer| {
blocking_signed_json_task(signer, move || {
if graffiti_file.is_some() {
return Err(warp_utils::reject::invalid_auth(
"Unable to update graffiti as the \"--graffiti-file\" flag is set"
.to_string(),
));
}
set_graffiti(pubkey.clone(), query.graffiti, validator_store)
})
},
)
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::ACCEPTED));

// DELETE /eth/v1/validator/{pubkey}/graffiti
let delete_graffiti = eth_v1
.and(warp::path("validator"))
.and(warp::path::param::<PublicKey>())
.and(warp::path("graffiti"))
.and(warp::path::end())
.and(validator_store_filter.clone())
.and(graffiti_file_filter.clone())
.and(signer.clone())
.and_then(
|pubkey: PublicKey,
validator_store: Arc<ValidatorStore<T, E>>,
graffiti_file: Option<GraffitiFile>,
signer| {
blocking_signed_json_task(signer, move || {
if graffiti_file.is_some() {
return Err(warp_utils::reject::invalid_auth(
jimmygchen marked this conversation as resolved.
Show resolved Hide resolved
"Unable to delete graffiti as the \"--graffiti-file\" flag is set"
.to_string(),
));
}
delete_graffiti(pubkey.clone(), validator_store)
})
},
)
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NO_CONTENT));

// GET /eth/v1/keystores
let get_std_keystores = std_keystores
.and(signer.clone())
Expand Down Expand Up @@ -1175,6 +1261,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
.or(get_lighthouse_ui_graffiti)
.or(get_fee_recipient)
.or(get_gas_limit)
.or(get_graffiti)
.or(get_std_keystores)
.or(get_std_remotekeys)
.recover(warp_utils::reject::handle_rejection),
Expand All @@ -1189,6 +1276,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
.or(post_gas_limit)
.or(post_std_keystores)
.or(post_std_remotekeys)
.or(post_graffiti)
.recover(warp_utils::reject::handle_rejection),
))
.or(warp::patch()
Expand All @@ -1199,6 +1287,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
.or(delete_gas_limit)
.or(delete_std_keystores)
.or(delete_std_remotekeys)
.or(delete_graffiti)
.recover(warp_utils::reject::handle_rejection),
)),
)
Expand Down
Loading