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:
ethDreamer
2022-07-06 03:51:08 +00:00
parent 3dc323b035
commit d5e2d98970
17 changed files with 583 additions and 374 deletions

View File

@@ -136,14 +136,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.value_name("FEE-RECIPIENT")
.takes_value(true)
)
.arg(
Arg::with_name("suggested-fee-recipient-file")
.long("suggested-fee-recipient-file")
.help("The fallback address provided to the BN if nothing suitable is found \
in the validator definitions.")
.value_name("FEE-RECIPIENT-FILE")
.takes_value(true)
)
/* REST API related arguments */
.arg(
Arg::with_name("http")

View File

@@ -1,4 +1,3 @@
use crate::fee_recipient_file::FeeRecipientFile;
use crate::graffiti_file::GraffitiFile;
use crate::{http_api, http_metrics};
use clap::ArgMatches;
@@ -44,8 +43,6 @@ pub struct Config {
pub graffiti_file: Option<GraffitiFile>,
/// Fallback fallback address.
pub fee_recipient: Option<Address>,
/// Fee recipient file to load per validator suggested-fee-recipients.
pub fee_recipient_file: Option<FeeRecipientFile>,
/// Configuration for the HTTP REST API.
pub http_api: http_api::Config,
/// Configuration for the HTTP REST API.
@@ -86,7 +83,6 @@ impl Default for Config {
graffiti: None,
graffiti_file: None,
fee_recipient: None,
fee_recipient_file: None,
http_api: <_>::default(),
http_metrics: <_>::default(),
monitoring_api: None,
@@ -206,19 +202,6 @@ impl Config {
}
}
if let Some(fee_recipient_file_path) = cli_args.value_of("suggested-fee-recipient-file") {
let mut fee_recipient_file = FeeRecipientFile::new(fee_recipient_file_path.into());
fee_recipient_file
.read_fee_recipient_file()
.map_err(|e| format!("Error reading suggested-fee-recipient file: {:?}", e))?;
config.fee_recipient_file = Some(fee_recipient_file);
info!(
log,
"Successfully loaded suggested-fee-recipient file";
"path" => fee_recipient_file_path
);
}
if let Some(input_fee_recipient) =
parse_optional::<Address>(cli_args, "suggested-fee-recipient")?
{

View File

@@ -1,184 +0,0 @@
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::File;
use std::io::{prelude::*, BufReader};
use std::path::PathBuf;
use std::str::FromStr;
use bls::PublicKeyBytes;
use types::Address;
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
pub enum Error {
InvalidFile(std::io::Error),
InvalidLine(String),
InvalidPublicKey(String),
InvalidFeeRecipient(String),
}
/// Struct to load validator fee-recipients from file.
/// The fee-recipient file is expected to have the following structure
///
/// default: 0x00000000219ab540356cbb839cbe05303d7705fa
/// public_key1: fee-recipient1
/// public_key2: fee-recipient2
/// ...
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeRecipientFile {
fee_recipient_path: PathBuf,
fee_recipients: HashMap<PublicKeyBytes, Address>,
default: Option<Address>,
}
impl FeeRecipientFile {
pub fn new(fee_recipient_path: PathBuf) -> Self {
Self {
fee_recipient_path,
fee_recipients: HashMap::new(),
default: None,
}
}
/// Returns the fee-recipient corresponding to the given public key if present, else returns the
/// default fee-recipient.
///
/// Returns an error if loading from the fee-recipient file fails.
pub fn get_fee_recipient(&self, public_key: &PublicKeyBytes) -> Result<Option<Address>, Error> {
Ok(self
.fee_recipients
.get(public_key)
.copied()
.or(self.default))
}
/// Loads the fee-recipient file and populates the default fee-recipient and `fee_recipients` hashmap.
/// Returns the fee-recipient corresponding to the given public key if present, else returns the
/// default fee-recipient.
///
/// Returns an error if loading from the fee-recipient file fails.
pub fn load_fee_recipient(
&mut self,
public_key: &PublicKeyBytes,
) -> Result<Option<Address>, Error> {
self.read_fee_recipient_file()?;
Ok(self
.fee_recipients
.get(public_key)
.copied()
.or(self.default))
}
/// Reads from a fee-recipient file with the specified format and populates the default value
/// and the hashmap.
///
/// Returns an error if the file does not exist, or if the format is invalid.
pub fn read_fee_recipient_file(&mut self) -> Result<(), Error> {
let file = File::open(self.fee_recipient_path.as_path()).map_err(Error::InvalidFile)?;
let reader = BufReader::new(file);
let lines = reader.lines();
self.default = None;
self.fee_recipients.clear();
for line in lines {
let line = line.map_err(|e| Error::InvalidLine(e.to_string()))?;
let (pk_opt, fee_recipient) = read_line(&line)?;
match pk_opt {
Some(pk) => {
self.fee_recipients.insert(pk, fee_recipient);
}
None => self.default = Some(fee_recipient),
}
}
Ok(())
}
}
/// Parses a line from the fee-recipient file.
///
/// `Ok((None, fee_recipient))` represents the fee-recipient for the default key.
/// `Ok((Some(pk), fee_recipient))` represents fee-recipient for the public key `pk`.
/// Returns an error if the line is in the wrong format or does not contain a valid public key or fee-recipient.
fn read_line(line: &str) -> Result<(Option<PublicKeyBytes>, Address), Error> {
if let Some(i) = line.find(':') {
let (key, value) = line.split_at(i);
// Note: `value.len() >=1` so `value[1..]` is safe
let fee_recipient = Address::from_str(value[1..].trim())
.map_err(|e| Error::InvalidFeeRecipient(e.to_string()))?;
if key == "default" {
Ok((None, fee_recipient))
} else {
let pk = PublicKeyBytes::from_str(key).map_err(Error::InvalidPublicKey)?;
Ok((Some(pk), fee_recipient))
}
} else {
Err(Error::InvalidLine(format!("Missing delimiter: {}", line)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use bls::Keypair;
use std::io::LineWriter;
use tempfile::TempDir;
const DEFAULT_FEE_RECIPIENT: &str = "0x00000000219ab540356cbb839cbe05303d7705fa";
const CUSTOM_FEE_RECIPIENT1: &str = "0x4242424242424242424242424242424242424242";
const CUSTOM_FEE_RECIPIENT2: &str = "0x0000000000000000000000000000000000000001";
const PK1: &str = "0x800012708dc03f611751aad7a43a082142832b5c1aceed07ff9b543cf836381861352aa923c70eeb02018b638aa306aa";
const PK2: &str = "0x80001866ce324de7d80ec73be15e2d064dcf121adf1b34a0d679f2b9ecbab40ce021e03bb877e1a2fe72eaaf475e6e21";
// Create a fee-recipient file in the required format and return a path to the file.
fn create_fee_recipient_file() -> PathBuf {
let temp = TempDir::new().unwrap();
let pk1 = PublicKeyBytes::deserialize(&hex::decode(&PK1[2..]).unwrap()).unwrap();
let pk2 = PublicKeyBytes::deserialize(&hex::decode(&PK2[2..]).unwrap()).unwrap();
let file_name = temp.into_path().join("fee_recipient.txt");
let file = File::create(&file_name).unwrap();
let mut fee_recipient_file = LineWriter::new(file);
fee_recipient_file
.write_all(format!("default: {}\n", DEFAULT_FEE_RECIPIENT).as_bytes())
.unwrap();
fee_recipient_file
.write_all(format!("{}: {}\n", pk1.as_hex_string(), CUSTOM_FEE_RECIPIENT1).as_bytes())
.unwrap();
fee_recipient_file
.write_all(format!("{}: {}\n", pk2.as_hex_string(), CUSTOM_FEE_RECIPIENT2).as_bytes())
.unwrap();
fee_recipient_file.flush().unwrap();
file_name
}
#[test]
fn test_load_fee_recipient() {
let fee_recipient_file_path = create_fee_recipient_file();
let mut gf = FeeRecipientFile::new(fee_recipient_file_path);
let pk1 = PublicKeyBytes::deserialize(&hex::decode(&PK1[2..]).unwrap()).unwrap();
let pk2 = PublicKeyBytes::deserialize(&hex::decode(&PK2[2..]).unwrap()).unwrap();
// Read once
gf.read_fee_recipient_file().unwrap();
assert_eq!(
gf.load_fee_recipient(&pk1).unwrap().unwrap(),
Address::from_str(CUSTOM_FEE_RECIPIENT1).unwrap()
);
assert_eq!(
gf.load_fee_recipient(&pk2).unwrap().unwrap(),
Address::from_str(CUSTOM_FEE_RECIPIENT2).unwrap()
);
// Random pk should return the default fee-recipient
let random_pk = Keypair::random().pk.compress();
assert_eq!(
gf.load_fee_recipient(&random_pk).unwrap().unwrap(),
Address::from_str(DEFAULT_FEE_RECIPIENT).unwrap()
);
}
}

View File

@@ -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))

View File

@@ -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"),

View File

@@ -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()
}

View File

@@ -617,6 +617,78 @@ impl InitializedValidators {
Ok(())
}
/// Sets the `InitializedValidator` and `ValidatorDefinition` `suggested_fee_recipient` values.
///
/// ## Notes
///
/// Setting a validator `fee_recipient` 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_validator_fee_recipient(
&mut self,
voting_public_key: &PublicKey,
fee_recipient: Address,
) -> Result<(), Error> {
if let Some(def) = self
.definitions
.as_mut_slice()
.iter_mut()
.find(|def| def.voting_public_key == *voting_public_key)
{
def.suggested_fee_recipient = Some(fee_recipient);
}
if let Some(val) = self
.validators
.get_mut(&PublicKeyBytes::from(voting_public_key))
{
val.suggested_fee_recipient = Some(fee_recipient);
}
self.definitions
.save(&self.validators_dir)
.map_err(Error::UnableToSaveDefinitions)?;
Ok(())
}
/// Removes the `InitializedValidator` and `ValidatorDefinition` `suggested_fee_recipient` values.
///
/// ## Notes
///
/// Removing a validator `fee_recipient` will cause `self.definitions` to be updated and saved to
/// disk. The fee_recipient 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_validator_fee_recipient(
&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.suggested_fee_recipient = None;
}
if let Some(val) = self
.validators
.get_mut(&PublicKeyBytes::from(voting_public_key))
{
val.suggested_fee_recipient = None;
}
self.definitions
.save(&self.validators_dir)
.map_err(Error::UnableToSaveDefinitions)?;
Ok(())
}
/// Tries to decrypt the key cache.
///
/// Returns the decrypted cache if decryption was successful, or an error if a required password

View File

@@ -5,7 +5,6 @@ mod check_synced;
mod cli;
mod config;
mod duties_service;
mod fee_recipient_file;
mod graffiti_file;
mod http_metrics;
mod key_cache;
@@ -360,6 +359,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
context.eth2_config.spec.clone(),
doppelganger_service.clone(),
slot_clock.clone(),
config.fee_recipient,
context.executor.clone(),
log.clone(),
));
@@ -426,8 +426,6 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
.validator_store(validator_store.clone())
.beacon_nodes(beacon_nodes.clone())
.runtime_context(context.service_context("preparation".into()))
.fee_recipient(config.fee_recipient)
.fee_recipient_file(config.fee_recipient_file.clone())
.build()?;
let sync_committee_service = SyncCommitteeService::new(

View File

@@ -1,8 +1,5 @@
use crate::beacon_node_fallback::{BeaconNodeFallback, RequireSynced};
use crate::{
fee_recipient_file::FeeRecipientFile,
validator_store::{DoppelgangerStatus, ValidatorStore},
};
use crate::validator_store::{DoppelgangerStatus, ValidatorStore};
use bls::PublicKeyBytes;
use environment::RuntimeContext;
use parking_lot::RwLock;
@@ -31,8 +28,6 @@ pub struct PreparationServiceBuilder<T: SlotClock + 'static, E: EthSpec> {
slot_clock: Option<T>,
beacon_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
context: Option<RuntimeContext<E>>,
fee_recipient: Option<Address>,
fee_recipient_file: Option<FeeRecipientFile>,
}
impl<T: SlotClock + 'static, E: EthSpec> PreparationServiceBuilder<T, E> {
@@ -42,8 +37,6 @@ impl<T: SlotClock + 'static, E: EthSpec> PreparationServiceBuilder<T, E> {
slot_clock: None,
beacon_nodes: None,
context: None,
fee_recipient: None,
fee_recipient_file: None,
}
}
@@ -67,16 +60,6 @@ impl<T: SlotClock + 'static, E: EthSpec> PreparationServiceBuilder<T, E> {
self
}
pub fn fee_recipient(mut self, fee_recipient: Option<Address>) -> Self {
self.fee_recipient = fee_recipient;
self
}
pub fn fee_recipient_file(mut self, fee_recipient_file: Option<FeeRecipientFile>) -> Self {
self.fee_recipient_file = fee_recipient_file;
self
}
pub fn build(self) -> Result<PreparationService<T, E>, String> {
Ok(PreparationService {
inner: Arc::new(Inner {
@@ -92,8 +75,6 @@ impl<T: SlotClock + 'static, E: EthSpec> PreparationServiceBuilder<T, E> {
context: self
.context
.ok_or("Cannot build PreparationService without runtime_context")?,
fee_recipient: self.fee_recipient,
fee_recipient_file: self.fee_recipient_file,
validator_registration_cache: RwLock::new(HashMap::new()),
}),
})
@@ -106,8 +87,6 @@ pub struct Inner<T, E: EthSpec> {
slot_clock: T,
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
context: RuntimeContext<E>,
fee_recipient: Option<Address>,
fee_recipient_file: Option<FeeRecipientFile>,
// Used to track unpublished validator registration changes.
validator_registration_cache:
RwLock<HashMap<ValidatorRegistrationKey, SignedValidatorRegistrationData>>,
@@ -301,23 +280,6 @@ impl<T: SlotClock + 'static, E: EthSpec> PreparationService<T, E> {
{
let log = self.context.log();
let fee_recipient_file = self
.fee_recipient_file
.clone()
.map(|mut fee_recipient_file| {
fee_recipient_file
.read_fee_recipient_file()
.map_err(|e| {
error!(
log,
"Error loading fee-recipient file";
"error" => ?e
);
})
.unwrap_or(());
fee_recipient_file
});
let all_pubkeys: Vec<_> = self
.validator_store
.voting_pubkeys(DoppelgangerStatus::ignored);
@@ -327,22 +289,7 @@ impl<T: SlotClock + 'static, E: EthSpec> PreparationService<T, E> {
.filter_map(|pubkey| {
// Ignore fee recipients for keys without indices, they are inactive.
let validator_index = self.validator_store.validator_index(&pubkey)?;
// If there is a `suggested_fee_recipient` in the validator definitions yaml
// file, use that value.
let fee_recipient = self
.validator_store
.suggested_fee_recipient(&pubkey)
.or_else(|| {
// If there's nothing in the validator defs file, check the fee
// recipient file.
fee_recipient_file
.as_ref()?
.get_fee_recipient(&pubkey)
.ok()?
})
// If there's nothing in the file, try the process-level default value.
.or(self.fee_recipient);
let fee_recipient = self.validator_store.get_fee_recipient(&pubkey);
if let Some(fee_recipient) = fee_recipient {
Some(map_fn(pubkey, validator_index, fee_recipient))

View File

@@ -86,6 +86,7 @@ pub struct ValidatorStore<T, E: EthSpec> {
log: Logger,
doppelganger_service: Option<Arc<DoppelgangerService>>,
slot_clock: T,
fee_recipient_process: Option<Address>,
task_executor: TaskExecutor,
_phantom: PhantomData<E>,
}
@@ -101,6 +102,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
spec: ChainSpec,
doppelganger_service: Option<Arc<DoppelgangerService>>,
slot_clock: T,
fee_recipient_process: Option<Address>,
task_executor: TaskExecutor,
log: Logger,
) -> Self {
@@ -113,6 +115,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
log,
doppelganger_service,
slot_clock,
fee_recipient_process,
task_executor,
_phantom: PhantomData,
}
@@ -356,7 +359,21 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
self.validators.read().graffiti(validator_pubkey)
}
pub fn suggested_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option<Address> {
/// Returns the fee recipient for the given public key. The priority order for fetching
/// the fee recipient is:
/// 1. validator_definitions.yml
/// 2. process level fee recipient
pub fn get_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option<Address> {
// If there is a `suggested_fee_recipient` in the validator definitions yaml
// file, use that value.
self.suggested_fee_recipient(validator_pubkey)
// If there's nothing in the file, try the process-level default value.
.or(self.fee_recipient_process)
}
/// Returns the suggested_fee_recipient from `validator_definitions.yml` if any.
/// This has been pulled into a private function so the read lock is dropped easily
fn suggested_fee_recipient(&self, validator_pubkey: &PublicKeyBytes) -> Option<Address> {
self.validators
.read()
.suggested_fee_recipient(validator_pubkey)