@@ -3,7 +3,8 @@ use crate::{BeaconChainTypes, BeaconStore};
33use ssz:: { Decode , Encode } ;
44use std:: collections:: HashMap ;
55use std:: convert:: TryInto ;
6- use store:: { DBColumn , Error as StoreError , StoreItem } ;
6+ use std:: marker:: PhantomData ;
7+ use store:: { DBColumn , Error as StoreError , KeyValueStore , KeyValueStoreOp , StoreItem } ;
78use types:: { BeaconState , Hash256 , PublicKey , PublicKeyBytes } ;
89
910/// Provides a mapping of `validator_index -> validator_publickey`.
@@ -14,21 +15,17 @@ use types::{BeaconState, Hash256, PublicKey, PublicKeyBytes};
1415/// 2. To reduce the amount of public key _decompression_ required. A `BeaconState` stores public
1516/// keys in compressed form and they are needed in decompressed form for signature verification.
1617/// Decompression is expensive when many keys are involved.
17- ///
18- /// The cache has a `backing` that it uses to maintain a persistent, on-disk
19- /// copy of itself. This allows it to be restored between process invocations.
2018pub struct ValidatorPubkeyCache < T : BeaconChainTypes > {
2119 pubkeys : Vec < PublicKey > ,
2220 indices : HashMap < PublicKeyBytes , usize > ,
2321 pubkey_bytes : Vec < PublicKeyBytes > ,
24- store : BeaconStore < T > ,
22+ _phantom : PhantomData < T > ,
2523}
2624
2725impl < T : BeaconChainTypes > ValidatorPubkeyCache < T > {
2826 /// Create a new public key cache using the keys in `state.validators`.
2927 ///
30- /// Also creates a new persistence file, returning an error if there is already a file at
31- /// `persistence_path`.
28+ /// The new cache will be updated with the keys from `state` and immediately written to disk.
3229 pub fn new (
3330 state : & BeaconState < T :: EthSpec > ,
3431 store : BeaconStore < T > ,
@@ -37,10 +34,11 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
3734 pubkeys : vec ! [ ] ,
3835 indices : HashMap :: new ( ) ,
3936 pubkey_bytes : vec ! [ ] ,
40- store ,
37+ _phantom : PhantomData ,
4138 } ;
4239
43- cache. import_new_pubkeys ( state) ?;
40+ let store_ops = cache. import_new_pubkeys ( state) ?;
41+ store. hot_db . do_atomically ( store_ops) ?;
4442
4543 Ok ( cache)
4644 }
@@ -69,55 +67,52 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
6967 pubkeys,
7068 indices,
7169 pubkey_bytes,
72- store ,
70+ _phantom : PhantomData ,
7371 } )
7472 }
7573
7674 /// Scan the given `state` and add any new validator public keys.
7775 ///
7876 /// Does not delete any keys from `self` if they don't appear in `state`.
77+ ///
78+ /// NOTE: The caller *must* commit the returned I/O batch as part of the block import process.
7979 pub fn import_new_pubkeys (
8080 & mut self ,
8181 state : & BeaconState < T :: EthSpec > ,
82- ) -> Result < ( ) , BeaconChainError > {
82+ ) -> Result < Vec < KeyValueStoreOp > , BeaconChainError > {
8383 if state. validators ( ) . len ( ) > self . pubkeys . len ( ) {
8484 self . import (
8585 state. validators ( ) [ self . pubkeys . len ( ) ..]
8686 . iter ( )
8787 . map ( |v| v. pubkey ) ,
8888 )
8989 } else {
90- Ok ( ( ) )
90+ Ok ( vec ! [ ] )
9191 }
9292 }
9393
9494 /// Adds zero or more validators to `self`.
95- fn import < I > ( & mut self , validator_keys : I ) -> Result < ( ) , BeaconChainError >
95+ fn import < I > ( & mut self , validator_keys : I ) -> Result < Vec < KeyValueStoreOp > , BeaconChainError >
9696 where
9797 I : Iterator < Item = PublicKeyBytes > + ExactSizeIterator ,
9898 {
9999 self . pubkey_bytes . reserve ( validator_keys. len ( ) ) ;
100100 self . pubkeys . reserve ( validator_keys. len ( ) ) ;
101101 self . indices . reserve ( validator_keys. len ( ) ) ;
102102
103+ let mut store_ops = Vec :: with_capacity ( validator_keys. len ( ) ) ;
103104 for pubkey in validator_keys {
104105 let i = self . pubkeys . len ( ) ;
105106
106107 if self . indices . contains_key ( & pubkey) {
107108 return Err ( BeaconChainError :: DuplicateValidatorPublicKey ) ;
108109 }
109110
110- // The item is written to disk _before_ it is written into
111- // the local struct.
112- //
113- // This means that a pubkey cache read from disk will always be equivalent to or
114- // _later than_ the cache that was running in the previous instance of Lighthouse.
115- //
116- // The motivation behind this ordering is that we do not want to have states that
117- // reference a pubkey that is not in our cache. However, it's fine to have pubkeys
118- // that are never referenced in a state.
119- self . store
120- . put_item ( & DatabasePubkey :: key_for_index ( i) , & DatabasePubkey ( pubkey) ) ?;
111+ // Stage the new validator key for writing to disk.
112+ // It will be committed atomically when the block that introduced it is written to disk.
113+ // Notably it is NOT written while the write lock on the cache is held.
114+ // See: https://github.com/sigp/lighthouse/issues/2327
115+ store_ops. push ( DatabasePubkey ( pubkey) . as_kv_store_op ( DatabasePubkey :: key_for_index ( i) ) ) ;
121116
122117 self . pubkeys . push (
123118 ( & pubkey)
@@ -129,7 +124,7 @@ impl<T: BeaconChainTypes> ValidatorPubkeyCache<T> {
129124 self . indices . insert ( pubkey, i) ;
130125 }
131126
132- Ok ( ( ) )
127+ Ok ( store_ops )
133128 }
134129
135130 /// Get the public key for a validator with index `i`.
@@ -296,9 +291,10 @@ mod test {
296291
297292 // Add some more keypairs.
298293 let ( state, keypairs) = get_state ( 12 ) ;
299- cache
294+ let ops = cache
300295 . import_new_pubkeys ( & state)
301296 . expect ( "should import pubkeys" ) ;
297+ store. hot_db . do_atomically ( ops) . unwrap ( ) ;
302298 check_cache_get ( & cache, & keypairs[ ..] ) ;
303299 drop ( cache) ;
304300
0 commit comments