Implement graffiti management API (#4951)
* implement get graffiti * add set graffiti * add set graffiti * delete graffiti * set graffiti * set graffiti * fmt * added tests * add graffiti file check * update * fixed delete req * remove unused code * changes based on feedback * changes based on feedback * invalid auth test plus lint * fmt * remove unneeded async
This commit is contained in:
parent
d9d84242a7
commit
8ba39cbf2c
@ -226,11 +226,32 @@ impl ValidatorClientHttpClient {
|
|||||||
ok_or_error(response).await
|
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> {
|
async fn get<T: DeserializeOwned, U: IntoUrl>(&self, url: U) -> Result<T, Error> {
|
||||||
let response = self.get_response(url).await?;
|
let response = self.get_response(url).await?;
|
||||||
self.signed_json(response).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> {
|
async fn get_unsigned<T: DeserializeOwned, U: IntoUrl>(&self, url: U) -> Result<T, Error> {
|
||||||
self.get_response(url)
|
self.get_response(url)
|
||||||
.await?
|
.await?
|
||||||
@ -537,6 +558,18 @@ impl ValidatorClientHttpClient {
|
|||||||
Ok(url)
|
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> {
|
fn make_gas_limit_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
|
||||||
let mut url = self.server.full.clone();
|
let mut url = self.server.full.clone();
|
||||||
url.path_segments_mut()
|
url.path_segments_mut()
|
||||||
@ -684,6 +717,34 @@ impl ValidatorClientHttpClient {
|
|||||||
|
|
||||||
self.post(path, &()).await
|
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
|
/// Returns `Ok(response)` if the response is a `200 OK` response or a
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use account_utils::ZeroizeString;
|
use account_utils::ZeroizeString;
|
||||||
use eth2_keystore::Keystore;
|
use eth2_keystore::Keystore;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use types::{Address, PublicKeyBytes};
|
use types::{Address, Graffiti, PublicKeyBytes};
|
||||||
|
|
||||||
pub use slashing_protection::interchange::Interchange;
|
pub use slashing_protection::interchange::Interchange;
|
||||||
|
|
||||||
@ -172,3 +172,9 @@ pub enum DeleteRemotekeyStatus {
|
|||||||
pub struct DeleteRemotekeysResponse {
|
pub struct DeleteRemotekeysResponse {
|
||||||
pub data: Vec<Status<DeleteRemotekeyStatus>>,
|
pub data: Vec<Status<DeleteRemotekeyStatus>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct GetGraffitiResponse {
|
||||||
|
pub pubkey: PublicKeyBytes,
|
||||||
|
pub graffiti: Graffiti,
|
||||||
|
}
|
||||||
|
@ -168,3 +168,8 @@ pub struct SingleExportKeystoresResponse {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub validating_keystore_password: Option<ZeroizeString>,
|
pub validating_keystore_password: Option<ZeroizeString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct SetGraffitiRequest {
|
||||||
|
pub graffiti: GraffitiString,
|
||||||
|
}
|
||||||
|
80
validator_client/src/http_api/graffiti.rs
Normal file
80
validator_client/src/http_api/graffiti.rs
Normal file
@ -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(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,15 @@
|
|||||||
mod api_secret;
|
mod api_secret;
|
||||||
mod create_signed_voluntary_exit;
|
mod create_signed_voluntary_exit;
|
||||||
mod create_validator;
|
mod create_validator;
|
||||||
|
mod graffiti;
|
||||||
mod keystores;
|
mod keystores;
|
||||||
mod remotekeys;
|
mod remotekeys;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub mod test_utils;
|
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::http_api::create_signed_voluntary_exit::create_signed_voluntary_exit;
|
||||||
use crate::{determine_graffiti, GraffitiFile, ValidatorStore};
|
use crate::{determine_graffiti, GraffitiFile, ValidatorStore};
|
||||||
use account_utils::{
|
use account_utils::{
|
||||||
@ -19,7 +22,10 @@ use create_validator::{
|
|||||||
};
|
};
|
||||||
use eth2::lighthouse_vc::{
|
use eth2::lighthouse_vc::{
|
||||||
std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse},
|
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 lighthouse_version::version_with_platform;
|
||||||
use logging::SSELoggingComponents;
|
use logging::SSELoggingComponents;
|
||||||
@ -653,7 +659,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
|||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp::body::json())
|
.and(warp::body::json())
|
||||||
.and(validator_store_filter.clone())
|
.and(validator_store_filter.clone())
|
||||||
.and(graffiti_file_filter)
|
.and(graffiti_file_filter.clone())
|
||||||
.and(signer.clone())
|
.and(signer.clone())
|
||||||
.and(task_executor_filter.clone())
|
.and(task_executor_filter.clone())
|
||||||
.and_then(
|
.and_then(
|
||||||
@ -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(
|
||||||
|
"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
|
// GET /eth/v1/keystores
|
||||||
let get_std_keystores = std_keystores
|
let get_std_keystores = std_keystores
|
||||||
.and(signer.clone())
|
.and(signer.clone())
|
||||||
@ -1175,6 +1261,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
|||||||
.or(get_lighthouse_ui_graffiti)
|
.or(get_lighthouse_ui_graffiti)
|
||||||
.or(get_fee_recipient)
|
.or(get_fee_recipient)
|
||||||
.or(get_gas_limit)
|
.or(get_gas_limit)
|
||||||
|
.or(get_graffiti)
|
||||||
.or(get_std_keystores)
|
.or(get_std_keystores)
|
||||||
.or(get_std_remotekeys)
|
.or(get_std_remotekeys)
|
||||||
.recover(warp_utils::reject::handle_rejection),
|
.recover(warp_utils::reject::handle_rejection),
|
||||||
@ -1189,6 +1276,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
|||||||
.or(post_gas_limit)
|
.or(post_gas_limit)
|
||||||
.or(post_std_keystores)
|
.or(post_std_keystores)
|
||||||
.or(post_std_remotekeys)
|
.or(post_std_remotekeys)
|
||||||
|
.or(post_graffiti)
|
||||||
.recover(warp_utils::reject::handle_rejection),
|
.recover(warp_utils::reject::handle_rejection),
|
||||||
))
|
))
|
||||||
.or(warp::patch()
|
.or(warp::patch()
|
||||||
@ -1199,6 +1287,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
|||||||
.or(delete_gas_limit)
|
.or(delete_gas_limit)
|
||||||
.or(delete_std_keystores)
|
.or(delete_std_keystores)
|
||||||
.or(delete_std_remotekeys)
|
.or(delete_std_remotekeys)
|
||||||
|
.or(delete_graffiti)
|
||||||
.recover(warp_utils::reject::handle_rejection),
|
.recover(warp_utils::reject::handle_rejection),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
@ -640,6 +640,49 @@ impl ApiTester {
|
|||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn test_set_graffiti(self, index: usize, graffiti: &str) -> Self {
|
||||||
|
let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index];
|
||||||
|
let graffiti_str = GraffitiString::from_str(graffiti).unwrap();
|
||||||
|
let resp = self
|
||||||
|
.client
|
||||||
|
.set_graffiti(&validator.voting_pubkey, graffiti_str)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_delete_graffiti(self, index: usize) -> Self {
|
||||||
|
let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index];
|
||||||
|
let resp = self.client.get_graffiti(&validator.voting_pubkey).await;
|
||||||
|
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
let old_graffiti = resp.unwrap().graffiti;
|
||||||
|
|
||||||
|
let resp = self.client.delete_graffiti(&validator.voting_pubkey).await;
|
||||||
|
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
|
||||||
|
let resp = self.client.get_graffiti(&validator.voting_pubkey).await;
|
||||||
|
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
assert_ne!(old_graffiti, resp.unwrap().graffiti);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_get_graffiti(self, index: usize, expected_graffiti: &str) -> Self {
|
||||||
|
let validator = &self.client.get_lighthouse_validators().await.unwrap().data[index];
|
||||||
|
let expected_graffiti_str = GraffitiString::from_str(expected_graffiti).unwrap();
|
||||||
|
let resp = self.client.get_graffiti(&validator.voting_pubkey).await;
|
||||||
|
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
assert_eq!(&resp.unwrap().graffiti, &expected_graffiti_str.into());
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HdValidatorScenario {
|
struct HdValidatorScenario {
|
||||||
@ -771,6 +814,20 @@ async fn routes_with_invalid_auth() {
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
})
|
})
|
||||||
|
.await
|
||||||
|
.test_with_invalid_auth(|client| async move {
|
||||||
|
client.delete_graffiti(&PublicKeyBytes::empty()).await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.test_with_invalid_auth(|client| async move {
|
||||||
|
client.get_graffiti(&PublicKeyBytes::empty()).await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.test_with_invalid_auth(|client| async move {
|
||||||
|
client
|
||||||
|
.set_graffiti(&PublicKeyBytes::empty(), GraffitiString::default())
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -954,6 +1011,31 @@ async fn validator_graffiti() {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn validator_graffiti_api() {
|
||||||
|
ApiTester::new()
|
||||||
|
.await
|
||||||
|
.create_hd_validators(HdValidatorScenario {
|
||||||
|
count: 2,
|
||||||
|
specify_mnemonic: false,
|
||||||
|
key_derivation_path_offset: 0,
|
||||||
|
disabled: vec![],
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.assert_enabled_validators_count(2)
|
||||||
|
.assert_validators_count(2)
|
||||||
|
.set_graffiti(0, "Mr F was here")
|
||||||
|
.await
|
||||||
|
.test_get_graffiti(0, "Mr F was here")
|
||||||
|
.await
|
||||||
|
.test_set_graffiti(0, "Uncle Bill was here")
|
||||||
|
.await
|
||||||
|
.test_get_graffiti(0, "Uncle Bill was here")
|
||||||
|
.await
|
||||||
|
.test_delete_graffiti(0)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn keystore_validator_creation() {
|
async fn keystore_validator_creation() {
|
||||||
ApiTester::new()
|
ApiTester::new()
|
||||||
|
@ -716,6 +716,74 @@ impl InitializedValidators {
|
|||||||
self.validators.get(public_key).and_then(|v| v.graffiti)
|
self.validators.get(public_key).and_then(|v| v.graffiti)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the `InitializedValidator` and `ValidatorDefinition` `graffiti` values.
|
||||||
|
///
|
||||||
|
/// ## Notes
|
||||||
|
///
|
||||||
|
/// Setting a validator `graffiti` will cause `self.definitions` to be updated and saved to
|
||||||
|
/// disk.
|
||||||
|
///
|
||||||
|
/// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
|
||||||
|
pub fn set_graffiti(
|
||||||
|
&mut self,
|
||||||
|
voting_public_key: &PublicKey,
|
||||||
|
graffiti: GraffitiString,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if let Some(def) = self
|
||||||
|
.definitions
|
||||||
|
.as_mut_slice()
|
||||||
|
.iter_mut()
|
||||||
|
.find(|def| def.voting_public_key == *voting_public_key)
|
||||||
|
{
|
||||||
|
def.graffiti = Some(graffiti.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(val) = self
|
||||||
|
.validators
|
||||||
|
.get_mut(&PublicKeyBytes::from(voting_public_key))
|
||||||
|
{
|
||||||
|
val.graffiti = Some(graffiti.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.definitions
|
||||||
|
.save(&self.validators_dir)
|
||||||
|
.map_err(Error::UnableToSaveDefinitions)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the `InitializedValidator` and `ValidatorDefinition` `graffiti` values.
|
||||||
|
///
|
||||||
|
/// ## Notes
|
||||||
|
///
|
||||||
|
/// Removing a validator `graffiti` will cause `self.definitions` to be updated and saved to
|
||||||
|
/// disk. The graffiti for the validator will then fall back to the process level default if
|
||||||
|
/// it is set.
|
||||||
|
///
|
||||||
|
/// Saves the `ValidatorDefinitions` to file, even if no definitions were changed.
|
||||||
|
pub fn delete_graffiti(&mut self, voting_public_key: &PublicKey) -> Result<(), Error> {
|
||||||
|
if let Some(def) = self
|
||||||
|
.definitions
|
||||||
|
.as_mut_slice()
|
||||||
|
.iter_mut()
|
||||||
|
.find(|def| def.voting_public_key == *voting_public_key)
|
||||||
|
{
|
||||||
|
def.graffiti = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(val) = self
|
||||||
|
.validators
|
||||||
|
.get_mut(&PublicKeyBytes::from(voting_public_key))
|
||||||
|
{
|
||||||
|
val.graffiti = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.definitions
|
||||||
|
.save(&self.validators_dir)
|
||||||
|
.map_err(Error::UnableToSaveDefinitions)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a `HashMap` of `public_key` -> `graffiti` for all initialized validators.
|
/// Returns a `HashMap` of `public_key` -> `graffiti` for all initialized validators.
|
||||||
pub fn get_all_validators_graffiti(&self) -> HashMap<&PublicKeyBytes, Option<Graffiti>> {
|
pub fn get_all_validators_graffiti(&self) -> HashMap<&PublicKeyBytes, Option<Graffiti>> {
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
|
Loading…
Reference in New Issue
Block a user