mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-09 11:41:51 +00:00
Implement feerecipient API for keymanager (#3213)
## Issue Addressed * #3173 ## Proposed Changes Moved all `fee_recipient_file` related logic inside the `ValidatorStore` as it makes more sense to have this all together there. I tested this with the validators I have on `mainnet-shadow-fork-5` and everything appeared to work well. Only technicality is that I can't get the method to return `401` when the authorization header is not specified (it returns `400` instead). Fixing this is probably quite difficult given that none of `warp`'s rejections have code `401`.. I don't really think this matters too much though as long as it fails.
This commit is contained in:
@@ -9,10 +9,11 @@ use account_utils::{
|
||||
mnemonic_from_phrase,
|
||||
validator_definitions::{SigningDefinition, ValidatorDefinition},
|
||||
};
|
||||
pub use api_secret::ApiSecret;
|
||||
use create_validator::{create_validators_mnemonic, create_validators_web3signer};
|
||||
use eth2::lighthouse_vc::{
|
||||
std_types::AuthResponse,
|
||||
types::{self as api_types, PublicKey, PublicKeyBytes},
|
||||
std_types::{AuthResponse, GetFeeRecipientResponse},
|
||||
types::{self as api_types, GenericResponse, PublicKey, PublicKeyBytes},
|
||||
};
|
||||
use lighthouse_version::version_with_platform;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -35,8 +36,6 @@ use warp::{
|
||||
Filter,
|
||||
};
|
||||
|
||||
pub use api_secret::ApiSecret;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Warp(warp::Error),
|
||||
@@ -562,6 +561,123 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
let std_keystores = eth_v1.and(warp::path("keystores")).and(warp::path::end());
|
||||
let std_remotekeys = eth_v1.and(warp::path("remotekeys")).and(warp::path::end());
|
||||
|
||||
// GET /eth/v1/validator/{pubkey}/feerecipient
|
||||
let get_fee_recipient = eth_v1
|
||||
.and(warp::path("validator"))
|
||||
.and(warp::path::param::<PublicKey>())
|
||||
.and(warp::path("feerecipient"))
|
||||
.and(warp::path::end())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and_then(
|
||||
|validator_pubkey: PublicKey, validator_store: Arc<ValidatorStore<T, E>>, signer| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
if validator_store
|
||||
.initialized_validators()
|
||||
.read()
|
||||
.is_enabled(&validator_pubkey)
|
||||
.is_none()
|
||||
{
|
||||
return Err(warp_utils::reject::custom_not_found(format!(
|
||||
"no validator found with pubkey {:?}",
|
||||
validator_pubkey
|
||||
)));
|
||||
}
|
||||
validator_store
|
||||
.get_fee_recipient(&PublicKeyBytes::from(&validator_pubkey))
|
||||
.map(|fee_recipient| {
|
||||
GenericResponse::from(GetFeeRecipientResponse {
|
||||
pubkey: PublicKeyBytes::from(validator_pubkey.clone()),
|
||||
ethaddress: fee_recipient,
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
warp_utils::reject::custom_server_error(
|
||||
"no fee recipient set".to_string(),
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// POST /eth/v1/validator/{pubkey}/feerecipient
|
||||
let post_fee_recipient = eth_v1
|
||||
.and(warp::path("validator"))
|
||||
.and(warp::path::param::<PublicKey>())
|
||||
.and(warp::body::json())
|
||||
.and(warp::path("feerecipient"))
|
||||
.and(warp::path::end())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and_then(
|
||||
|validator_pubkey: PublicKey,
|
||||
request: api_types::UpdateFeeRecipientRequest,
|
||||
validator_store: Arc<ValidatorStore<T, E>>,
|
||||
signer| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
if validator_store
|
||||
.initialized_validators()
|
||||
.read()
|
||||
.is_enabled(&validator_pubkey)
|
||||
.is_none()
|
||||
{
|
||||
return Err(warp_utils::reject::custom_not_found(format!(
|
||||
"no validator found with pubkey {:?}",
|
||||
validator_pubkey
|
||||
)));
|
||||
}
|
||||
validator_store
|
||||
.initialized_validators()
|
||||
.write()
|
||||
.set_validator_fee_recipient(&validator_pubkey, request.ethaddress)
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"Error persisting fee recipient: {:?}",
|
||||
e
|
||||
))
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::ACCEPTED));
|
||||
|
||||
// DELETE /eth/v1/validator/{pubkey}/feerecipient
|
||||
let delete_fee_recipient = eth_v1
|
||||
.and(warp::path("validator"))
|
||||
.and(warp::path::param::<PublicKey>())
|
||||
.and(warp::path("feerecipient"))
|
||||
.and(warp::path::end())
|
||||
.and(validator_store_filter.clone())
|
||||
.and(signer.clone())
|
||||
.and_then(
|
||||
|validator_pubkey: PublicKey, validator_store: Arc<ValidatorStore<T, E>>, signer| {
|
||||
blocking_signed_json_task(signer, move || {
|
||||
if validator_store
|
||||
.initialized_validators()
|
||||
.read()
|
||||
.is_enabled(&validator_pubkey)
|
||||
.is_none()
|
||||
{
|
||||
return Err(warp_utils::reject::custom_not_found(format!(
|
||||
"no validator found with pubkey {:?}",
|
||||
validator_pubkey
|
||||
)));
|
||||
}
|
||||
validator_store
|
||||
.initialized_validators()
|
||||
.write()
|
||||
.delete_validator_fee_recipient(&validator_pubkey)
|
||||
.map_err(|e| {
|
||||
warp_utils::reject::custom_server_error(format!(
|
||||
"Error persisting fee recipient removal: {:?}",
|
||||
e
|
||||
))
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
.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())
|
||||
@@ -647,6 +763,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.or(get_lighthouse_spec)
|
||||
.or(get_lighthouse_validators)
|
||||
.or(get_lighthouse_validators_pubkey)
|
||||
.or(get_fee_recipient)
|
||||
.or(get_std_keystores)
|
||||
.or(get_std_remotekeys),
|
||||
)
|
||||
@@ -655,11 +772,16 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.or(post_validators_keystore)
|
||||
.or(post_validators_mnemonic)
|
||||
.or(post_validators_web3signer)
|
||||
.or(post_fee_recipient)
|
||||
.or(post_std_keystores)
|
||||
.or(post_std_remotekeys),
|
||||
))
|
||||
.or(warp::patch().and(patch_validators))
|
||||
.or(warp::delete().and(delete_std_keystores.or(delete_std_remotekeys))),
|
||||
.or(warp::delete().and(
|
||||
delete_fee_recipient
|
||||
.or(delete_std_keystores)
|
||||
.or(delete_std_remotekeys),
|
||||
)),
|
||||
)
|
||||
// The auth route is the only route that is allowed to be accessed without the API token.
|
||||
.or(warp::get().and(get_auth))
|
||||
|
||||
@@ -36,6 +36,7 @@ use tokio::runtime::Runtime;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
const PASSWORD_BYTES: &[u8] = &[42, 50, 37];
|
||||
pub const TEST_DEFAULT_FEE_RECIPIENT: Address = Address::repeat_byte(42);
|
||||
|
||||
type E = MainnetEthSpec;
|
||||
|
||||
@@ -102,6 +103,7 @@ impl ApiTester {
|
||||
spec,
|
||||
Some(Arc::new(DoppelgangerService::new(log.clone()))),
|
||||
slot_clock,
|
||||
Some(TEST_DEFAULT_FEE_RECIPIENT),
|
||||
executor.clone(),
|
||||
log.clone(),
|
||||
));
|
||||
@@ -185,7 +187,7 @@ impl ApiTester {
|
||||
missing_token_client.send_authorization_header(false);
|
||||
match func(missing_token_client).await {
|
||||
Err(ApiError::ServerMessage(ApiErrorMessage {
|
||||
code: 400, message, ..
|
||||
code: 401, message, ..
|
||||
})) if message.contains("missing Authorization header") => (),
|
||||
Err(other) => panic!("expected missing header error, got {:?}", other),
|
||||
Ok(_) => panic!("expected missing header error, got Ok"),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::*;
|
||||
use account_utils::random_password_string;
|
||||
use bls::PublicKeyBytes;
|
||||
use eth2::lighthouse_vc::types::UpdateFeeRecipientRequest;
|
||||
use eth2::lighthouse_vc::{
|
||||
http_client::ValidatorClientHttpClient as HttpClient,
|
||||
std_types::{KeystoreJsonStr as Keystore, *},
|
||||
@@ -9,6 +11,7 @@ use itertools::Itertools;
|
||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
use slashing_protection::interchange::{Interchange, InterchangeMetadata};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
use types::Address;
|
||||
|
||||
fn new_keystore(password: ZeroizeString) -> Keystore {
|
||||
let keypair = Keypair::random();
|
||||
@@ -585,6 +588,185 @@ fn import_invalid_slashing_protection() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_get_set_fee_recipient() {
|
||||
run_test(|tester: ApiTester| async move {
|
||||
let _ = &tester;
|
||||
let password = random_password_string();
|
||||
let keystores = (0..3)
|
||||
.map(|_| new_keystore(password.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let all_pubkeys = keystores.iter().map(keystore_pubkey).collect::<Vec<_>>();
|
||||
|
||||
let import_res = tester
|
||||
.client
|
||||
.post_keystores(&ImportKeystoresRequest {
|
||||
keystores: keystores.clone(),
|
||||
passwords: vec![password.clone(); keystores.len()],
|
||||
slashing_protection: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// All keystores should be imported.
|
||||
check_keystore_import_response(&import_res, all_imported(keystores.len()));
|
||||
|
||||
// Check that GET lists all the imported keystores.
|
||||
let get_res = tester.client.get_keystores().await.unwrap();
|
||||
check_keystore_get_response(&get_res, &keystores);
|
||||
|
||||
// Before setting anything, every fee recipient should be set to TEST_DEFAULT_FEE_RECIPIENT
|
||||
for pubkey in &all_pubkeys {
|
||||
let get_res = tester
|
||||
.client
|
||||
.get_fee_recipient(pubkey)
|
||||
.await
|
||||
.expect("should get fee recipient");
|
||||
assert_eq!(
|
||||
get_res,
|
||||
GetFeeRecipientResponse {
|
||||
pubkey: pubkey.clone(),
|
||||
ethaddress: TEST_DEFAULT_FEE_RECIPIENT,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
use std::str::FromStr;
|
||||
let fee_recipient_public_key_1 =
|
||||
Address::from_str("0x25c4a76E7d118705e7Ea2e9b7d8C59930d8aCD3b").unwrap();
|
||||
let fee_recipient_public_key_2 =
|
||||
Address::from_str("0x0000000000000000000000000000000000000001").unwrap();
|
||||
let fee_recipient_override =
|
||||
Address::from_str("0x0123456789abcdef0123456789abcdef01234567").unwrap();
|
||||
|
||||
// set the fee recipient for pubkey[1] using the API
|
||||
tester
|
||||
.client
|
||||
.post_fee_recipient(
|
||||
&all_pubkeys[1],
|
||||
&UpdateFeeRecipientRequest {
|
||||
ethaddress: fee_recipient_public_key_1.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("should update fee recipient");
|
||||
// now everything but pubkey[1] should be TEST_DEFAULT_FEE_RECIPIENT
|
||||
for (i, pubkey) in all_pubkeys.iter().enumerate() {
|
||||
let get_res = tester
|
||||
.client
|
||||
.get_fee_recipient(pubkey)
|
||||
.await
|
||||
.expect("should get fee recipient");
|
||||
let expected = if i == 1 {
|
||||
fee_recipient_public_key_1.clone()
|
||||
} else {
|
||||
TEST_DEFAULT_FEE_RECIPIENT
|
||||
};
|
||||
assert_eq!(
|
||||
get_res,
|
||||
GetFeeRecipientResponse {
|
||||
pubkey: pubkey.clone(),
|
||||
ethaddress: expected,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// set the fee recipient for pubkey[2] using the API
|
||||
tester
|
||||
.client
|
||||
.post_fee_recipient(
|
||||
&all_pubkeys[2],
|
||||
&UpdateFeeRecipientRequest {
|
||||
ethaddress: fee_recipient_public_key_2.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("should update fee recipient");
|
||||
// now everything but pubkey[1] & pubkey[2] should be fee_recipient_file_default
|
||||
for (i, pubkey) in all_pubkeys.iter().enumerate() {
|
||||
let get_res = tester
|
||||
.client
|
||||
.get_fee_recipient(pubkey)
|
||||
.await
|
||||
.expect("should get fee recipient");
|
||||
let expected = if i == 1 {
|
||||
fee_recipient_public_key_1.clone()
|
||||
} else if i == 2 {
|
||||
fee_recipient_public_key_2.clone()
|
||||
} else {
|
||||
TEST_DEFAULT_FEE_RECIPIENT
|
||||
};
|
||||
assert_eq!(
|
||||
get_res,
|
||||
GetFeeRecipientResponse {
|
||||
pubkey: pubkey.clone(),
|
||||
ethaddress: expected,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// should be able to override previous fee_recipient
|
||||
tester
|
||||
.client
|
||||
.post_fee_recipient(
|
||||
&all_pubkeys[1],
|
||||
&UpdateFeeRecipientRequest {
|
||||
ethaddress: fee_recipient_override.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("should update fee recipient");
|
||||
for (i, pubkey) in all_pubkeys.iter().enumerate() {
|
||||
let get_res = tester
|
||||
.client
|
||||
.get_fee_recipient(pubkey)
|
||||
.await
|
||||
.expect("should get fee recipient");
|
||||
let expected = if i == 1 {
|
||||
fee_recipient_override.clone()
|
||||
} else if i == 2 {
|
||||
fee_recipient_public_key_2.clone()
|
||||
} else {
|
||||
TEST_DEFAULT_FEE_RECIPIENT
|
||||
};
|
||||
assert_eq!(
|
||||
get_res,
|
||||
GetFeeRecipientResponse {
|
||||
pubkey: pubkey.clone(),
|
||||
ethaddress: expected,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// delete fee recipient for pubkey[1] using the API
|
||||
tester
|
||||
.client
|
||||
.delete_fee_recipient(&all_pubkeys[1])
|
||||
.await
|
||||
.expect("should delete fee recipient");
|
||||
// now everything but pubkey[2] should be TEST_DEFAULT_FEE_RECIPIENT
|
||||
for (i, pubkey) in all_pubkeys.iter().enumerate() {
|
||||
let get_res = tester
|
||||
.client
|
||||
.get_fee_recipient(pubkey)
|
||||
.await
|
||||
.expect("should get fee recipient");
|
||||
let expected = if i == 2 {
|
||||
fee_recipient_public_key_2.clone()
|
||||
} else {
|
||||
TEST_DEFAULT_FEE_RECIPIENT
|
||||
};
|
||||
assert_eq!(
|
||||
get_res,
|
||||
GetFeeRecipientResponse {
|
||||
pubkey: pubkey.clone(),
|
||||
ethaddress: expected,
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn all_indices(count: usize) -> Vec<usize> {
|
||||
(0..count).collect()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user