2020-03-05 06:19:35 +00:00
|
|
|
use crate::errors::BeaconChainError;
|
2021-03-04 01:25:12 +00:00
|
|
|
use crate::{BeaconChainTypes, BeaconStore};
|
2022-05-17 04:54:39 +00:00
|
|
|
use ssz::{Decode, Encode};
|
2020-04-06 04:13:19 +00:00
|
|
|
use std::collections::HashMap;
|
2020-03-05 06:19:35 +00:00
|
|
|
use std::convert::TryInto;
|
2022-11-30 05:22:58 +00:00
|
|
|
use std::marker::PhantomData;
|
|
|
|
use store::{DBColumn, Error as StoreError, KeyValueStore, KeyValueStoreOp, StoreItem};
|
2021-03-04 01:25:12 +00:00
|
|
|
use types::{BeaconState, Hash256, PublicKey, PublicKeyBytes};
|
2020-03-05 06:19:35 +00:00
|
|
|
|
|
|
|
/// Provides a mapping of `validator_index -> validator_publickey`.
|
|
|
|
///
|
|
|
|
/// This cache exists for two reasons:
|
|
|
|
///
|
|
|
|
/// 1. To avoid reading a `BeaconState` from disk each time we need a public key.
|
|
|
|
/// 2. To reduce the amount of public key _decompression_ required. A `BeaconState` stores public
|
|
|
|
/// keys in compressed form and they are needed in decompressed form for signature verification.
|
|
|
|
/// Decompression is expensive when many keys are involved.
|
2021-03-04 01:25:12 +00:00
|
|
|
pub struct ValidatorPubkeyCache<T: BeaconChainTypes> {
|
2020-03-05 06:19:35 +00:00
|
|
|
pubkeys: Vec<PublicKey>,
|
2020-04-06 04:13:19 +00:00
|
|
|
indices: HashMap<PublicKeyBytes, usize>,
|
2021-03-17 05:09:57 +00:00
|
|
|
pubkey_bytes: Vec<PublicKeyBytes>,
|
2022-11-30 05:22:58 +00:00
|
|
|
_phantom: PhantomData<T>,
|
2021-03-04 01:25:12 +00:00
|
|
|
}
|
2020-03-05 06:19:35 +00:00
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
|
2020-03-05 06:19:35 +00:00
|
|
|
/// Create a new public key cache using the keys in `state.validators`.
|
|
|
|
///
|
2022-11-30 05:22:58 +00:00
|
|
|
/// The new cache will be updated with the keys from `state` and immediately written to disk.
|
2021-03-04 01:25:12 +00:00
|
|
|
pub fn new(
|
|
|
|
state: &BeaconState<T::EthSpec>,
|
|
|
|
store: BeaconStore<T>,
|
2020-03-05 06:19:35 +00:00
|
|
|
) -> Result<Self, BeaconChainError> {
|
|
|
|
let mut cache = Self {
|
|
|
|
pubkeys: vec![],
|
2020-04-06 04:13:19 +00:00
|
|
|
indices: HashMap::new(),
|
2021-03-17 05:09:57 +00:00
|
|
|
pubkey_bytes: vec![],
|
2022-11-30 05:22:58 +00:00
|
|
|
_phantom: PhantomData,
|
2020-03-05 06:19:35 +00:00
|
|
|
};
|
|
|
|
|
2022-11-30 05:22:58 +00:00
|
|
|
let store_ops = cache.import_new_pubkeys(state)?;
|
|
|
|
store.hot_db.do_atomically(store_ops)?;
|
2020-03-05 06:19:35 +00:00
|
|
|
|
|
|
|
Ok(cache)
|
|
|
|
}
|
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
/// Load the pubkey cache from the given on-disk database.
|
|
|
|
pub fn load_from_store(store: BeaconStore<T>) -> Result<Self, BeaconChainError> {
|
|
|
|
let mut pubkeys = vec![];
|
|
|
|
let mut indices = HashMap::new();
|
2021-03-17 05:09:57 +00:00
|
|
|
let mut pubkey_bytes = vec![];
|
2021-03-04 01:25:12 +00:00
|
|
|
|
|
|
|
for validator_index in 0.. {
|
|
|
|
if let Some(DatabasePubkey(pubkey)) =
|
|
|
|
store.get_item(&DatabasePubkey::key_for_index(validator_index))?
|
|
|
|
{
|
2022-05-17 04:54:39 +00:00
|
|
|
pubkeys.push((&pubkey).try_into().map_err(|e| {
|
|
|
|
BeaconChainError::ValidatorPubkeyCacheError(format!("{:?}", e))
|
|
|
|
})?);
|
2021-03-17 05:09:57 +00:00
|
|
|
pubkey_bytes.push(pubkey);
|
2021-03-04 01:25:12 +00:00
|
|
|
indices.insert(pubkey, validator_index);
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(ValidatorPubkeyCache {
|
|
|
|
pubkeys,
|
|
|
|
indices,
|
2021-03-17 05:09:57 +00:00
|
|
|
pubkey_bytes,
|
2022-11-30 05:22:58 +00:00
|
|
|
_phantom: PhantomData,
|
2021-03-04 01:25:12 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-03-05 06:19:35 +00:00
|
|
|
/// Scan the given `state` and add any new validator public keys.
|
|
|
|
///
|
|
|
|
/// Does not delete any keys from `self` if they don't appear in `state`.
|
2022-11-30 05:22:58 +00:00
|
|
|
///
|
|
|
|
/// NOTE: The caller *must* commit the returned I/O batch as part of the block import process.
|
2021-03-04 01:25:12 +00:00
|
|
|
pub fn import_new_pubkeys(
|
2020-03-05 06:19:35 +00:00
|
|
|
&mut self,
|
2021-03-04 01:25:12 +00:00
|
|
|
state: &BeaconState<T::EthSpec>,
|
2022-11-30 05:22:58 +00:00
|
|
|
) -> Result<Vec<KeyValueStoreOp>, BeaconChainError> {
|
2021-07-09 06:15:32 +00:00
|
|
|
if state.validators().len() > self.pubkeys.len() {
|
2021-03-04 01:25:12 +00:00
|
|
|
self.import(
|
2021-07-09 06:15:32 +00:00
|
|
|
state.validators()[self.pubkeys.len()..]
|
2021-03-04 01:25:12 +00:00
|
|
|
.iter()
|
|
|
|
.map(|v| v.pubkey),
|
|
|
|
)
|
2020-04-06 04:13:19 +00:00
|
|
|
} else {
|
2022-11-30 05:22:58 +00:00
|
|
|
Ok(vec![])
|
2020-04-06 04:13:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds zero or more validators to `self`.
|
2022-11-30 05:22:58 +00:00
|
|
|
fn import<I>(&mut self, validator_keys: I) -> Result<Vec<KeyValueStoreOp>, BeaconChainError>
|
2021-03-04 01:25:12 +00:00
|
|
|
where
|
|
|
|
I: Iterator<Item = PublicKeyBytes> + ExactSizeIterator,
|
|
|
|
{
|
2021-03-17 05:09:57 +00:00
|
|
|
self.pubkey_bytes.reserve(validator_keys.len());
|
2021-03-04 01:25:12 +00:00
|
|
|
self.pubkeys.reserve(validator_keys.len());
|
|
|
|
self.indices.reserve(validator_keys.len());
|
|
|
|
|
2022-11-30 05:22:58 +00:00
|
|
|
let mut store_ops = Vec::with_capacity(validator_keys.len());
|
2021-03-04 01:25:12 +00:00
|
|
|
for pubkey in validator_keys {
|
2020-04-06 04:13:19 +00:00
|
|
|
let i = self.pubkeys.len();
|
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
if self.indices.contains_key(&pubkey) {
|
2020-04-06 04:13:19 +00:00
|
|
|
return Err(BeaconChainError::DuplicateValidatorPublicKey);
|
|
|
|
}
|
|
|
|
|
2022-11-30 05:22:58 +00:00
|
|
|
// Stage the new validator key for writing to disk.
|
|
|
|
// It will be committed atomically when the block that introduced it is written to disk.
|
|
|
|
// Notably it is NOT written while the write lock on the cache is held.
|
|
|
|
// See: https://github.com/sigp/lighthouse/issues/2327
|
|
|
|
store_ops.push(DatabasePubkey(pubkey).as_kv_store_op(DatabasePubkey::key_for_index(i)));
|
2020-04-06 04:13:19 +00:00
|
|
|
|
|
|
|
self.pubkeys.push(
|
2021-03-04 01:25:12 +00:00
|
|
|
(&pubkey)
|
2020-04-06 04:13:19 +00:00
|
|
|
.try_into()
|
|
|
|
.map_err(BeaconChainError::InvalidValidatorPubkeyBytes)?,
|
|
|
|
);
|
2021-03-17 05:09:57 +00:00
|
|
|
self.pubkey_bytes.push(pubkey);
|
2020-03-05 06:19:35 +00:00
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
self.indices.insert(pubkey, i);
|
2020-04-06 04:13:19 +00:00
|
|
|
}
|
|
|
|
|
2022-11-30 05:22:58 +00:00
|
|
|
Ok(store_ops)
|
2020-03-05 06:19:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the public key for a validator with index `i`.
|
|
|
|
pub fn get(&self, i: usize) -> Option<&PublicKey> {
|
|
|
|
self.pubkeys.get(i)
|
|
|
|
}
|
2020-04-06 04:13:19 +00:00
|
|
|
|
2021-07-15 00:52:02 +00:00
|
|
|
/// Get the `PublicKey` for a validator with `PublicKeyBytes`.
|
|
|
|
pub fn get_pubkey_from_pubkey_bytes(&self, pubkey: &PublicKeyBytes) -> Option<&PublicKey> {
|
2022-02-25 00:10:17 +00:00
|
|
|
self.get_index(pubkey).and_then(|index| self.get(index))
|
2021-07-15 00:52:02 +00:00
|
|
|
}
|
|
|
|
|
2021-03-17 05:09:57 +00:00
|
|
|
/// Get the public key (in bytes form) for a validator with index `i`.
|
|
|
|
pub fn get_pubkey_bytes(&self, i: usize) -> Option<&PublicKeyBytes> {
|
|
|
|
self.pubkey_bytes.get(i)
|
|
|
|
}
|
|
|
|
|
2020-04-06 04:13:19 +00:00
|
|
|
/// Get the index of a validator with `pubkey`.
|
|
|
|
pub fn get_index(&self, pubkey: &PublicKeyBytes) -> Option<usize> {
|
|
|
|
self.indices.get(pubkey).copied()
|
|
|
|
}
|
2020-05-06 11:42:56 +00:00
|
|
|
|
|
|
|
/// Returns the number of validators in the cache.
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.indices.len()
|
|
|
|
}
|
2022-08-09 06:05:13 +00:00
|
|
|
|
|
|
|
/// Returns `true` if there are no validators in the cache.
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.indices.is_empty()
|
|
|
|
}
|
2020-03-05 06:19:35 +00:00
|
|
|
}
|
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
/// Wrapper for a public key stored in the database.
|
|
|
|
///
|
|
|
|
/// Keyed by the validator index as `Hash256::from_low_u64_be(index)`.
|
|
|
|
struct DatabasePubkey(PublicKeyBytes);
|
|
|
|
|
|
|
|
impl StoreItem for DatabasePubkey {
|
|
|
|
fn db_column() -> DBColumn {
|
|
|
|
DBColumn::PubkeyCache
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_store_bytes(&self) -> Vec<u8> {
|
|
|
|
self.0.as_ssz_bytes()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn from_store_bytes(bytes: &[u8]) -> Result<Self, StoreError> {
|
|
|
|
Ok(Self(PublicKeyBytes::from_ssz_bytes(bytes)?))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DatabasePubkey {
|
|
|
|
fn key_for_index(index: usize) -> Hash256 {
|
|
|
|
Hash256::from_low_u64_be(index as u64)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-05 06:19:35 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2021-10-06 00:46:07 +00:00
|
|
|
use crate::test_utils::{BeaconChainHarness, EphemeralHarnessType};
|
|
|
|
use logging::test_logger;
|
2021-03-04 01:25:12 +00:00
|
|
|
use std::sync::Arc;
|
2021-10-14 02:58:10 +00:00
|
|
|
use store::HotColdDB;
|
2022-05-17 04:54:39 +00:00
|
|
|
use types::{BeaconState, EthSpec, Keypair, MainnetEthSpec};
|
2020-03-05 06:19:35 +00:00
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
type E = MainnetEthSpec;
|
|
|
|
type T = EphemeralHarnessType<E>;
|
|
|
|
|
|
|
|
fn get_state(validator_count: usize) -> (BeaconState<E>, Vec<Keypair>) {
|
2021-10-14 02:58:10 +00:00
|
|
|
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
|
|
|
.default_spec()
|
|
|
|
.deterministic_keypairs(validator_count)
|
|
|
|
.fresh_ephemeral_store()
|
|
|
|
.build();
|
2021-07-09 06:15:32 +00:00
|
|
|
|
|
|
|
harness.advance_slot();
|
|
|
|
|
|
|
|
(harness.get_current_state(), harness.validator_keypairs)
|
2020-03-05 06:19:35 +00:00
|
|
|
}
|
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
fn get_store() -> BeaconStore<T> {
|
|
|
|
Arc::new(
|
|
|
|
HotColdDB::open_ephemeral(<_>::default(), E::default_spec(), test_logger()).unwrap(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-01-28 23:31:06 +00:00
|
|
|
#[allow(clippy::needless_range_loop)]
|
2021-03-04 01:25:12 +00:00
|
|
|
fn check_cache_get(cache: &ValidatorPubkeyCache<T>, keypairs: &[Keypair]) {
|
2020-03-05 06:19:35 +00:00
|
|
|
let validator_count = keypairs.len();
|
|
|
|
|
|
|
|
for i in 0..validator_count + 1 {
|
|
|
|
if i < validator_count {
|
|
|
|
let pubkey = cache.get(i).expect("pubkey should be present");
|
|
|
|
assert_eq!(pubkey, &keypairs[i].pk, "pubkey should match cache");
|
2020-04-06 04:13:19 +00:00
|
|
|
|
|
|
|
let pubkey_bytes: PublicKeyBytes = pubkey.clone().into();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
i,
|
|
|
|
cache
|
|
|
|
.get_index(&pubkey_bytes)
|
|
|
|
.expect("should resolve index"),
|
|
|
|
"index should match cache"
|
|
|
|
);
|
2020-03-05 06:19:35 +00:00
|
|
|
} else {
|
|
|
|
assert_eq!(
|
|
|
|
cache.get(i),
|
|
|
|
None,
|
|
|
|
"should not get pubkey for out of bounds index",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn basic_operation() {
|
|
|
|
let (state, keypairs) = get_state(8);
|
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
let store = get_store();
|
2020-03-05 06:19:35 +00:00
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
let mut cache = ValidatorPubkeyCache::new(&state, store).expect("should create cache");
|
2020-03-05 06:19:35 +00:00
|
|
|
|
|
|
|
check_cache_get(&cache, &keypairs[..]);
|
|
|
|
|
|
|
|
// Try adding a state with the same number of keypairs.
|
|
|
|
let (state, keypairs) = get_state(8);
|
|
|
|
cache
|
|
|
|
.import_new_pubkeys(&state)
|
|
|
|
.expect("should import pubkeys");
|
|
|
|
check_cache_get(&cache, &keypairs[..]);
|
|
|
|
|
|
|
|
// Try adding a state with less keypairs.
|
|
|
|
let (state, _) = get_state(1);
|
|
|
|
cache
|
|
|
|
.import_new_pubkeys(&state)
|
|
|
|
.expect("should import pubkeys");
|
|
|
|
check_cache_get(&cache, &keypairs[..]);
|
|
|
|
|
|
|
|
// Try adding a state with more keypairs.
|
|
|
|
let (state, keypairs) = get_state(12);
|
|
|
|
cache
|
|
|
|
.import_new_pubkeys(&state)
|
|
|
|
.expect("should import pubkeys");
|
|
|
|
check_cache_get(&cache, &keypairs[..]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn persistence() {
|
|
|
|
let (state, keypairs) = get_state(8);
|
|
|
|
|
2021-03-04 01:25:12 +00:00
|
|
|
let store = get_store();
|
2020-03-05 06:19:35 +00:00
|
|
|
|
|
|
|
// Create a new cache.
|
2021-03-04 01:25:12 +00:00
|
|
|
let cache = ValidatorPubkeyCache::new(&state, store.clone()).expect("should create cache");
|
2020-03-05 06:19:35 +00:00
|
|
|
check_cache_get(&cache, &keypairs[..]);
|
|
|
|
drop(cache);
|
|
|
|
|
2022-05-17 04:54:39 +00:00
|
|
|
// Re-init the cache from the store.
|
2021-03-04 01:25:12 +00:00
|
|
|
let mut cache =
|
|
|
|
ValidatorPubkeyCache::load_from_store(store.clone()).expect("should open cache");
|
2020-03-05 06:19:35 +00:00
|
|
|
check_cache_get(&cache, &keypairs[..]);
|
|
|
|
|
|
|
|
// Add some more keypairs.
|
|
|
|
let (state, keypairs) = get_state(12);
|
2022-11-30 05:22:58 +00:00
|
|
|
let ops = cache
|
2020-03-05 06:19:35 +00:00
|
|
|
.import_new_pubkeys(&state)
|
|
|
|
.expect("should import pubkeys");
|
2022-11-30 05:22:58 +00:00
|
|
|
store.hot_db.do_atomically(ops).unwrap();
|
2020-03-05 06:19:35 +00:00
|
|
|
check_cache_get(&cache, &keypairs[..]);
|
|
|
|
drop(cache);
|
|
|
|
|
2022-05-17 04:54:39 +00:00
|
|
|
// Re-init the cache from the store.
|
2021-03-04 01:25:12 +00:00
|
|
|
let cache = ValidatorPubkeyCache::load_from_store(store).expect("should open cache");
|
2020-03-05 06:19:35 +00:00
|
|
|
check_cache_get(&cache, &keypairs[..]);
|
|
|
|
}
|
|
|
|
}
|