Implement standard keystore API (#2736)

## Issue Addressed

Implements the standard key manager API from https://ethereum.github.io/keymanager-APIs/, formerly https://github.com/ethereum/beacon-APIs/pull/151
Related to https://github.com/sigp/lighthouse/issues/2557

## Proposed Changes

- [x] Add all of the new endpoints from the standard API: GET, POST and DELETE.
- [x] Add a `validators.enabled` column to the slashing protection database to support atomic disable + export.
- [x] Add tests for all the common sequential accesses of the API
- [x] Add tests for interactions with remote signer validators
- [x] Add end-to-end tests for migration of validators from one VC to another
- [x] Implement the authentication scheme from the standard (token bearer auth)

## Additional Info

The `enabled` column in the validators SQL database is necessary to prevent a race condition when exporting slashing protection data. Without the slashing protection database having a way of knowing that a key has been disabled, a concurrent request to sign a message could insert a new record into the database. The `delete_concurrent_with_signing` test exercises this code path, and was indeed failing before the `enabled` column was added.

The validator client authentication has been modified from basic auth to bearer auth, with basic auth preserved for backwards compatibility.
This commit is contained in:
Michael Sproul
2022-01-30 23:22:04 +00:00
parent ee000d5219
commit e961ff60b4
32 changed files with 2284 additions and 127 deletions

View File

@@ -6,7 +6,9 @@ use crate::{
};
use account_utils::{validator_definitions::ValidatorDefinition, ZeroizeString};
use parking_lot::{Mutex, RwLock};
use slashing_protection::{NotSafe, Safe, SlashingDatabase};
use slashing_protection::{
interchange::Interchange, InterchangeError, NotSafe, Safe, SlashingDatabase,
};
use slog::{crit, error, info, warn, Logger};
use slot_clock::SlotClock;
use std::iter::FromIterator;
@@ -183,7 +185,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
self.validators
.write()
.add_definition(validator_def.clone())
.add_definition_replace_disabled(validator_def.clone())
.await
.map_err(|e| format!("Unable to add definition: {:?}", e))?;
@@ -693,6 +695,48 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
Ok(SignedContributionAndProof { message, signature })
}
pub fn import_slashing_protection(
&self,
interchange: Interchange,
) -> Result<(), InterchangeError> {
self.slashing_protection
.import_interchange_info(interchange, self.genesis_validators_root)?;
Ok(())
}
/// Export slashing protection data while also disabling the given keys in the database.
///
/// If any key is unknown to the slashing protection database it will be silently omitted
/// from the result. It is the caller's responsibility to check whether all keys provided
/// had data returned for them.
pub fn export_slashing_protection_for_keys(
&self,
pubkeys: &[PublicKeyBytes],
) -> Result<Interchange, InterchangeError> {
self.slashing_protection.with_transaction(|txn| {
let known_pubkeys = pubkeys
.iter()
.filter_map(|pubkey| {
let validator_id = self
.slashing_protection
.get_validator_id_ignoring_status(txn, pubkey)
.ok()?;
Some(
self.slashing_protection
.update_validator_status(txn, validator_id, false)
.map(|()| *pubkey),
)
})
.collect::<Result<Vec<PublicKeyBytes>, _>>()?;
self.slashing_protection.export_interchange_info_in_txn(
self.genesis_validators_root,
Some(&known_pubkeys),
txn,
)
})
}
/// Prune the slashing protection database so that it remains performant.
///
/// This function will only do actual pruning periodically, so it should usually be