Standard gas limit api (#3450)

## Issue Addressed

Resolves https://github.com/sigp/lighthouse/issues/3403

## Proposed Changes

Implements https://ethereum.github.io/keymanager-APIs/#/Gas%20Limit

## Additional Info

N/A

Co-authored-by: realbigsean <sean@sigmaprime.io>
This commit is contained in:
realbigsean
2022-08-15 01:30:58 +00:00
parent 92d597ad23
commit dd93aa8701
8 changed files with 430 additions and 6 deletions

View File

@@ -12,7 +12,7 @@ use account_utils::{
pub use api_secret::ApiSecret;
use create_validator::{create_validators_mnemonic, create_validators_web3signer};
use eth2::lighthouse_vc::{
std_types::{AuthResponse, GetFeeRecipientResponse},
std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse},
types::{self as api_types, GenericResponse, PublicKey, PublicKeyBytes},
};
use lighthouse_version::version_with_platform;
@@ -626,8 +626,8 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
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::body::json())
.and(warp::path::end())
.and(validator_store_filter.clone())
.and(signer.clone())
@@ -700,6 +700,115 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
)
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NO_CONTENT));
// GET /eth/v1/validator/{pubkey}/gas_limit
let get_gas_limit = eth_v1
.and(warp::path("validator"))
.and(warp::path::param::<PublicKey>())
.and(warp::path("gas_limit"))
.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
)));
}
Ok(GenericResponse::from(GetGasLimitResponse {
pubkey: PublicKeyBytes::from(validator_pubkey.clone()),
gas_limit: validator_store
.get_gas_limit(&PublicKeyBytes::from(&validator_pubkey)),
}))
})
},
);
// POST /eth/v1/validator/{pubkey}/gas_limit
let post_gas_limit = eth_v1
.and(warp::path("validator"))
.and(warp::path::param::<PublicKey>())
.and(warp::path("gas_limit"))
.and(warp::body::json())
.and(warp::path::end())
.and(validator_store_filter.clone())
.and(signer.clone())
.and_then(
|validator_pubkey: PublicKey,
request: api_types::UpdateGasLimitRequest,
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_gas_limit(&validator_pubkey, request.gas_limit)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"Error persisting gas limit: {:?}",
e
))
})
})
},
)
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::ACCEPTED));
// DELETE /eth/v1/validator/{pubkey}/gas_limit
let delete_gas_limit = eth_v1
.and(warp::path("validator"))
.and(warp::path::param::<PublicKey>())
.and(warp::path("gas_limit"))
.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_gas_limit(&validator_pubkey)
.map_err(|e| {
warp_utils::reject::custom_server_error(format!(
"Error persisting gas limit 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())
@@ -786,6 +895,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
.or(get_lighthouse_validators)
.or(get_lighthouse_validators_pubkey)
.or(get_fee_recipient)
.or(get_gas_limit)
.or(get_std_keystores)
.or(get_std_remotekeys),
)
@@ -795,12 +905,14 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
.or(post_validators_mnemonic)
.or(post_validators_web3signer)
.or(post_fee_recipient)
.or(post_gas_limit)
.or(post_std_keystores)
.or(post_std_remotekeys),
))
.or(warp::patch().and(patch_validators))
.or(warp::delete().and(
delete_fee_recipient
.or(delete_gas_limit)
.or(delete_std_keystores)
.or(delete_std_remotekeys),
)),

View File

@@ -1,3 +1,4 @@
use super::super::super::validator_store::DEFAULT_GAS_LIMIT;
use super::*;
use account_utils::random_password_string;
use bls::PublicKeyBytes;
@@ -769,6 +770,181 @@ fn check_get_set_fee_recipient() {
})
}
#[test]
fn check_get_set_gas_limit() {
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 gas limit should be set to DEFAULT_GAS_LIMIT
for pubkey in &all_pubkeys {
let get_res = tester
.client
.get_gas_limit(pubkey)
.await
.expect("should get gas limit");
assert_eq!(
get_res,
GetGasLimitResponse {
pubkey: pubkey.clone(),
gas_limit: DEFAULT_GAS_LIMIT,
}
);
}
let gas_limit_public_key_1 = 40_000_000;
let gas_limit_public_key_2 = 42;
let gas_limit_override = 100;
// set the gas limit for pubkey[1] using the API
tester
.client
.post_gas_limit(
&all_pubkeys[1],
&UpdateGasLimitRequest {
gas_limit: gas_limit_public_key_1,
},
)
.await
.expect("should update gas limit");
// now everything but pubkey[1] should be DEFAULT_GAS_LIMIT
for (i, pubkey) in all_pubkeys.iter().enumerate() {
let get_res = tester
.client
.get_gas_limit(pubkey)
.await
.expect("should get gas limit");
let expected = if i == 1 {
gas_limit_public_key_1.clone()
} else {
DEFAULT_GAS_LIMIT
};
assert_eq!(
get_res,
GetGasLimitResponse {
pubkey: pubkey.clone(),
gas_limit: expected,
}
);
}
// set the gas limit for pubkey[2] using the API
tester
.client
.post_gas_limit(
&all_pubkeys[2],
&UpdateGasLimitRequest {
gas_limit: gas_limit_public_key_2,
},
)
.await
.expect("should update gas limit");
// now everything but pubkey[1] & pubkey[2] should be DEFAULT_GAS_LIMIT
for (i, pubkey) in all_pubkeys.iter().enumerate() {
let get_res = tester
.client
.get_gas_limit(pubkey)
.await
.expect("should get gas limit");
let expected = if i == 1 {
gas_limit_public_key_1
} else if i == 2 {
gas_limit_public_key_2
} else {
DEFAULT_GAS_LIMIT
};
assert_eq!(
get_res,
GetGasLimitResponse {
pubkey: pubkey.clone(),
gas_limit: expected,
}
);
}
// should be able to override previous gas_limit
tester
.client
.post_gas_limit(
&all_pubkeys[1],
&UpdateGasLimitRequest {
gas_limit: gas_limit_override,
},
)
.await
.expect("should update gas limit");
for (i, pubkey) in all_pubkeys.iter().enumerate() {
let get_res = tester
.client
.get_gas_limit(pubkey)
.await
.expect("should get gas limit");
let expected = if i == 1 {
gas_limit_override
} else if i == 2 {
gas_limit_public_key_2
} else {
DEFAULT_GAS_LIMIT
};
assert_eq!(
get_res,
GetGasLimitResponse {
pubkey: pubkey.clone(),
gas_limit: expected,
}
);
}
// delete gas limit for pubkey[1] using the API
tester
.client
.delete_gas_limit(&all_pubkeys[1])
.await
.expect("should delete gas limit");
// now everything but pubkey[2] should be DEFAULT_GAS_LIMIT
for (i, pubkey) in all_pubkeys.iter().enumerate() {
let get_res = tester
.client
.get_gas_limit(pubkey)
.await
.expect("should get gas limit");
let expected = if i == 2 {
gas_limit_public_key_2
} else {
DEFAULT_GAS_LIMIT
};
assert_eq!(
get_res,
GetGasLimitResponse {
pubkey: pubkey.clone(),
gas_limit: expected,
}
);
}
})
}
fn all_indices(count: usize) -> Vec<usize> {
(0..count).collect()
}

View File

@@ -795,6 +795,78 @@ impl InitializedValidators {
Ok(())
}
/// Sets the `InitializedValidator` and `ValidatorDefinition` `gas_limit` values.
///
/// ## Notes
///
/// Setting a validator `gas_limit` 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_gas_limit(
&mut self,
voting_public_key: &PublicKey,
gas_limit: u64,
) -> Result<(), Error> {
if let Some(def) = self
.definitions
.as_mut_slice()
.iter_mut()
.find(|def| def.voting_public_key == *voting_public_key)
{
def.gas_limit = Some(gas_limit);
}
if let Some(val) = self
.validators
.get_mut(&PublicKeyBytes::from(voting_public_key))
{
val.gas_limit = Some(gas_limit);
}
self.definitions
.save(&self.validators_dir)
.map_err(Error::UnableToSaveDefinitions)?;
Ok(())
}
/// Removes the `InitializedValidator` and `ValidatorDefinition` `gas_limit` values.
///
/// ## Notes
///
/// Removing a validator `gas_limit` will cause `self.definitions` to be updated and saved to
/// disk. The gas_limit 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_gas_limit(
&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.gas_limit = None;
}
if let Some(val) = self
.validators
.get_mut(&PublicKeyBytes::from(voting_public_key))
{
val.gas_limit = 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

@@ -57,7 +57,7 @@ const SLASHING_PROTECTION_HISTORY_EPOCHS: u64 = 512;
/// Currently used as the default gas limit in execution clients.
///
/// https://github.com/ethereum/builder-specs/issues/17
const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000;
struct LocalValidator {
validator_dir: ValidatorDir,