Skip to content

Commit 2877c29

Browse files
committed
Add remotekey API support (#3162)
## Issue Addressed #3068 ## Proposed Changes Adds support for remote key API. ## Additional Info Needed to add `is_local_keystore` argument to `delete_definition_and_keystore` to know if we want to delete local or remote key. Previously this wasn't necessary because remotekeys(web3signers) could be deleted.
1 parent bb7e7d7 commit 2877c29

File tree

8 files changed

+1234
-68
lines changed

8 files changed

+1234
-68
lines changed

common/eth2/src/lighthouse_vc/http_client.rs

+34
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,16 @@ impl ValidatorClientHttpClient {
476476
Ok(url)
477477
}
478478

479+
fn make_remotekeys_url(&self) -> Result<Url, Error> {
480+
let mut url = self.server.full.clone();
481+
url.path_segments_mut()
482+
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
483+
.push("eth")
484+
.push("v1")
485+
.push("remotekeys");
486+
Ok(url)
487+
}
488+
479489
/// `GET lighthouse/auth`
480490
pub async fn get_auth(&self) -> Result<AuthResponse, Error> {
481491
let mut url = self.server.full.clone();
@@ -509,6 +519,30 @@ impl ValidatorClientHttpClient {
509519
let url = self.make_keystores_url()?;
510520
self.delete_with_unsigned_response(url, req).await
511521
}
522+
523+
/// `GET eth/v1/remotekeys`
524+
pub async fn get_remotekeys(&self) -> Result<ListRemotekeysResponse, Error> {
525+
let url = self.make_remotekeys_url()?;
526+
self.get_unsigned(url).await
527+
}
528+
529+
/// `POST eth/v1/remotekeys`
530+
pub async fn post_remotekeys(
531+
&self,
532+
req: &ImportRemotekeysRequest,
533+
) -> Result<ImportRemotekeysResponse, Error> {
534+
let url = self.make_remotekeys_url()?;
535+
self.post_with_unsigned_response(url, req).await
536+
}
537+
538+
/// `DELETE eth/v1/remotekeys`
539+
pub async fn delete_remotekeys(
540+
&self,
541+
req: &DeleteRemotekeysRequest,
542+
) -> Result<DeleteRemotekeysResponse, Error> {
543+
let url = self.make_remotekeys_url()?;
544+
self.delete_with_unsigned_response(url, req).await
545+
}
512546
}
513547

514548
/// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an

common/eth2/src/lighthouse_vc/std_types.rs

+56
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,59 @@ pub enum DeleteKeystoreStatus {
102102
NotFound,
103103
Error,
104104
}
105+
106+
#[derive(Debug, Deserialize, Serialize, PartialEq)]
107+
pub struct ListRemotekeysResponse {
108+
pub data: Vec<SingleListRemotekeysResponse>,
109+
}
110+
111+
#[derive(Debug, Deserialize, Serialize, PartialEq)]
112+
pub struct SingleListRemotekeysResponse {
113+
pub pubkey: PublicKeyBytes,
114+
pub url: String,
115+
pub readonly: bool,
116+
}
117+
118+
#[derive(Debug, Clone, Deserialize, Serialize)]
119+
#[serde(deny_unknown_fields)]
120+
pub struct ImportRemotekeysRequest {
121+
pub remote_keys: Vec<SingleImportRemotekeysRequest>,
122+
}
123+
124+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
125+
pub struct SingleImportRemotekeysRequest {
126+
pub pubkey: PublicKeyBytes,
127+
pub url: String,
128+
}
129+
130+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
131+
#[serde(rename_all = "lowercase")]
132+
pub enum ImportRemotekeyStatus {
133+
Imported,
134+
Duplicate,
135+
Error,
136+
}
137+
138+
#[derive(Debug, Deserialize, Serialize)]
139+
pub struct ImportRemotekeysResponse {
140+
pub data: Vec<Status<ImportRemotekeyStatus>>,
141+
}
142+
143+
#[derive(Debug, Deserialize, Serialize)]
144+
#[serde(deny_unknown_fields)]
145+
pub struct DeleteRemotekeysRequest {
146+
pub pubkeys: Vec<PublicKeyBytes>,
147+
}
148+
149+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
150+
#[serde(rename_all = "snake_case")]
151+
pub enum DeleteRemotekeyStatus {
152+
Deleted,
153+
NotFound,
154+
Error,
155+
}
156+
157+
#[derive(Debug, Deserialize, Serialize)]
158+
pub struct DeleteRemotekeysResponse {
159+
pub data: Vec<Status<DeleteRemotekeyStatus>>,
160+
}

validator_client/src/http_api/create_validator.rs

+4-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::ValidatorStore;
2-
use account_utils::validator_definitions::{SigningDefinition, ValidatorDefinition};
2+
use account_utils::validator_definitions::ValidatorDefinition;
33
use account_utils::{
44
eth2_wallet::{bip39::Mnemonic, WalletBuilder},
55
random_mnemonic, random_password, ZeroizeString,
@@ -164,24 +164,12 @@ pub async fn create_validators_mnemonic<P: AsRef<Path>, T: 'static + SlotClock,
164164
}
165165

166166
pub async fn create_validators_web3signer<T: 'static + SlotClock, E: EthSpec>(
167-
validator_requests: &[api_types::Web3SignerValidatorRequest],
167+
validators: Vec<ValidatorDefinition>,
168168
validator_store: &ValidatorStore<T, E>,
169169
) -> Result<(), warp::Rejection> {
170-
for request in validator_requests {
171-
let validator_definition = ValidatorDefinition {
172-
enabled: request.enable,
173-
voting_public_key: request.voting_public_key.clone(),
174-
graffiti: request.graffiti.clone(),
175-
suggested_fee_recipient: request.suggested_fee_recipient,
176-
description: request.description.clone(),
177-
signing_definition: SigningDefinition::Web3Signer {
178-
url: request.url.clone(),
179-
root_certificate_path: request.root_certificate_path.clone(),
180-
request_timeout_ms: request.request_timeout_ms,
181-
},
182-
};
170+
for validator in validators {
183171
validator_store
184-
.add_validator(validator_definition)
172+
.add_validator(validator)
185173
.await
186174
.map_err(|e| {
187175
warp_utils::reject::custom_server_error(format!(

validator_client/src/http_api/keystores.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Implementation of the standard keystore management API.
2-
use crate::{signing_method::SigningMethod, InitializedValidators, ValidatorStore};
2+
use crate::{
3+
initialized_validators::Error, signing_method::SigningMethod, InitializedValidators,
4+
ValidatorStore,
5+
};
36
use account_utils::ZeroizeString;
47
use eth2::lighthouse_vc::std_types::{
58
DeleteKeystoreStatus, DeleteKeystoresRequest, DeleteKeystoresResponse, ImportKeystoreStatus,
@@ -282,9 +285,14 @@ fn delete_single_keystore(
282285
.decompress()
283286
.map_err(|e| format!("invalid pubkey, {:?}: {:?}", pubkey_bytes, e))?;
284287

285-
runtime
286-
.block_on(initialized_validators.delete_definition_and_keystore(&pubkey))
287-
.map_err(|e| format!("unable to disable and delete: {:?}", e))
288+
match runtime.block_on(initialized_validators.delete_definition_and_keystore(&pubkey, true))
289+
{
290+
Ok(_) => Ok(DeleteKeystoreStatus::Deleted),
291+
Err(e) => match e {
292+
Error::ValidatorNotInitialized(_) => Ok(DeleteKeystoreStatus::NotFound),
293+
_ => Err(format!("unable to disable and delete: {:?}", e)),
294+
},
295+
}
288296
} else {
289297
Err("validator client shutdown".into())
290298
}

validator_client/src/http_api/mod.rs

+66-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
mod api_secret;
22
mod create_validator;
33
mod keystores;
4+
mod remotekeys;
45
mod tests;
56

67
use crate::ValidatorStore;
7-
use account_utils::mnemonic_from_phrase;
8+
use account_utils::{
9+
mnemonic_from_phrase,
10+
validator_definitions::{SigningDefinition, ValidatorDefinition},
11+
};
812
use create_validator::{create_validators_mnemonic, create_validators_web3signer};
913
use eth2::lighthouse_vc::{
1014
std_types::AuthResponse,
@@ -459,7 +463,25 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
459463
runtime: Weak<Runtime>| {
460464
blocking_signed_json_task(signer, move || {
461465
if let Some(runtime) = runtime.upgrade() {
462-
runtime.block_on(create_validators_web3signer(&body, &validator_store))?;
466+
let web3signers: Vec<ValidatorDefinition> = body
467+
.into_iter()
468+
.map(|web3signer| ValidatorDefinition {
469+
enabled: web3signer.enable,
470+
voting_public_key: web3signer.voting_public_key,
471+
graffiti: web3signer.graffiti,
472+
suggested_fee_recipient: web3signer.suggested_fee_recipient,
473+
description: web3signer.description,
474+
signing_definition: SigningDefinition::Web3Signer {
475+
url: web3signer.url,
476+
root_certificate_path: web3signer.root_certificate_path,
477+
request_timeout_ms: web3signer.request_timeout_ms,
478+
},
479+
})
480+
.collect();
481+
runtime.block_on(create_validators_web3signer(
482+
web3signers,
483+
&validator_store,
484+
))?;
463485
Ok(())
464486
} else {
465487
Err(warp_utils::reject::custom_server_error(
@@ -536,6 +558,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
536558
// Standard key-manager endpoints.
537559
let eth_v1 = warp::path("eth").and(warp::path("v1"));
538560
let std_keystores = eth_v1.and(warp::path("keystores")).and(warp::path::end());
561+
let std_remotekeys = eth_v1.and(warp::path("remotekeys")).and(warp::path::end());
539562

540563
// GET /eth/v1/keystores
541564
let get_std_keystores = std_keystores
@@ -563,14 +586,48 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
563586

564587
// DELETE /eth/v1/keystores
565588
let delete_std_keystores = std_keystores
589+
.and(warp::body::json())
590+
.and(signer.clone())
591+
.and(validator_store_filter.clone())
592+
.and(runtime_filter.clone())
593+
.and(log_filter.clone())
594+
.and_then(|request, signer, validator_store, runtime, log| {
595+
blocking_signed_json_task(signer, move || {
596+
keystores::delete(request, validator_store, runtime, log)
597+
})
598+
});
599+
600+
// GET /eth/v1/remotekeys
601+
let get_std_remotekeys = std_remotekeys
602+
.and(signer.clone())
603+
.and(validator_store_filter.clone())
604+
.and_then(|signer, validator_store: Arc<ValidatorStore<T, E>>| {
605+
blocking_signed_json_task(signer, move || Ok(remotekeys::list(validator_store)))
606+
});
607+
608+
// POST /eth/v1/remotekeys
609+
let post_std_remotekeys = std_remotekeys
610+
.and(warp::body::json())
611+
.and(signer.clone())
612+
.and(validator_store_filter.clone())
613+
.and(runtime_filter.clone())
614+
.and(log_filter.clone())
615+
.and_then(|request, signer, validator_store, runtime, log| {
616+
blocking_signed_json_task(signer, move || {
617+
remotekeys::import(request, validator_store, runtime, log)
618+
})
619+
});
620+
621+
// DELETE /eth/v1/remotekeys
622+
let delete_std_remotekeys = std_remotekeys
566623
.and(warp::body::json())
567624
.and(signer)
568625
.and(validator_store_filter)
569626
.and(runtime_filter)
570-
.and(log_filter)
627+
.and(log_filter.clone())
571628
.and_then(|request, signer, validator_store, runtime, log| {
572629
blocking_signed_json_task(signer, move || {
573-
keystores::delete(request, validator_store, runtime, log)
630+
remotekeys::delete(request, validator_store, runtime, log)
574631
})
575632
});
576633

@@ -588,17 +645,19 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
588645
.or(get_lighthouse_spec)
589646
.or(get_lighthouse_validators)
590647
.or(get_lighthouse_validators_pubkey)
591-
.or(get_std_keystores),
648+
.or(get_std_keystores)
649+
.or(get_std_remotekeys),
592650
)
593651
.or(warp::post().and(
594652
post_validators
595653
.or(post_validators_keystore)
596654
.or(post_validators_mnemonic)
597655
.or(post_validators_web3signer)
598-
.or(post_std_keystores),
656+
.or(post_std_keystores)
657+
.or(post_std_remotekeys),
599658
))
600659
.or(warp::patch().and(patch_validators))
601-
.or(warp::delete().and(delete_std_keystores)),
660+
.or(warp::delete().and(delete_std_keystores.or(delete_std_remotekeys))),
602661
)
603662
// The auth route is the only route that is allowed to be accessed without the API token.
604663
.or(warp::get().and(get_auth))

0 commit comments

Comments
 (0)