@@ -114,14 +114,24 @@ pub enum RecordsFilter<N: Network> {
114114/// which loads the ledger from storage,
115115/// or initializes it with the genesis block if the storage is empty
116116#[ derive( Clone ) ]
117- pub struct Ledger < N : Network , C : ConsensusStorage < N > > {
117+ pub struct Ledger < N : Network , C : ConsensusStorage < N > > ( Arc < InnerLedger < N , C > > ) ;
118+
119+ impl < N : Network , C : ConsensusStorage < N > > Deref for Ledger < N , C > {
120+ type Target = InnerLedger < N , C > ;
121+
122+ fn deref ( & self ) -> & Self :: Target {
123+ & self . 0
124+ }
125+ }
126+
127+ #[ doc( hidden) ]
128+ pub struct InnerLedger < N : Network , C : ConsensusStorage < N > > {
118129 /// The VM state.
119130 vm : VM < N , C > ,
120131 /// The genesis block.
121132 genesis_block : Block < N > ,
122133 /// The current epoch hash.
123- current_epoch_hash : Arc < RwLock < Option < N :: BlockHash > > > ,
124-
134+ current_epoch_hash : RwLock < Option < N :: BlockHash > > ,
125135 /// The committee resulting from all the on-chain staking activity.
126136 ///
127137 /// This includes any bonding and unbonding transactions in the latest block.
@@ -137,23 +147,21 @@ pub struct Ledger<N: Network, C: ConsensusStorage<N>> {
137147 /// So the `Option` should always be `Some`,
138148 /// but there are cases in which it is `None`,
139149 /// probably only temporarily when loading/initializing the ledger,
140- current_committee : Arc < RwLock < Option < Committee < N > > > > ,
150+ current_committee : RwLock < Option < Committee < N > > > ,
141151
142152 /// The latest block that was added to the ledger.
143153 ///
144154 /// This lock is also used as a way to prevent concurrent updates to the ledger, and to ensure that
145155 /// the ledger does not advance while certain check happen.
146- current_block : Arc < RwLock < Block < N > > > ,
147-
156+ current_block : RwLock < Block < N > > ,
148157 /// The recent committees of interest paired with their applicable rounds.
149158 ///
150159 /// Each entry consisting of a round `R` and a committee `C`,
151160 /// says that `C` is the bonded committee at round `R`,
152161 /// i.e. resulting from all the bonding and unbonding transactions before `R`.
153162 /// If `L` is the lookback round distance, `C` is the active committee at round `R + L`
154163 /// (i.e. the committee in charge of running consensus at round `R + L`).
155- committee_cache : Arc < Mutex < LruCache < u64 , Committee < N > > > > ,
156-
164+ committee_cache : Mutex < LruCache < u64 , Committee < N > > > ,
157165 /// The cache that holds the provers and the number of solutions they have submitted for the current epoch.
158166 epoch_provers_cache : Arc < RwLock < IndexMap < Address < N > , u32 > > > ,
159167}
@@ -211,47 +219,73 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
211219 let current_committee = vm. finalize_store ( ) . committee_store ( ) . current_committee ( ) . ok ( ) ;
212220
213221 // Create a committee cache.
214- let committee_cache = Arc :: new ( Mutex :: new ( LruCache :: new ( COMMITTEE_CACHE_SIZE . try_into ( ) . unwrap ( ) ) ) ) ;
222+ let committee_cache = Mutex :: new ( LruCache :: new ( COMMITTEE_CACHE_SIZE . try_into ( ) . unwrap ( ) ) ) ;
215223
216224 // Initialize the ledger.
217- let mut ledger = Self {
225+ let ledger = Self ( Arc :: new ( InnerLedger {
218226 vm,
219227 genesis_block : genesis_block. clone ( ) ,
220228 current_epoch_hash : Default :: default ( ) ,
221- current_committee : Arc :: new ( RwLock :: new ( current_committee) ) ,
222- current_block : Arc :: new ( RwLock :: new ( genesis_block. clone ( ) ) ) ,
229+ current_committee : RwLock :: new ( current_committee) ,
230+ current_block : RwLock :: new ( genesis_block. clone ( ) ) ,
223231 committee_cache,
224232 epoch_provers_cache : Default :: default ( ) ,
225- } ;
233+ } ) ) ;
234+
235+ // Attempt to obtain the maximum height from the storage.
236+ let max_stored_height = ledger. vm . block_store ( ) . max_height ( ) ;
226237
227238 // If the block store is empty, add the genesis block.
228- if ledger. vm . block_store ( ) . max_height ( ) . is_none ( ) {
229- // Add the genesis block.
239+ let latest_height = if let Some ( max_height) = max_stored_height {
240+ max_height
241+ } else {
230242 ledger. advance_to_next_block ( & genesis_block) ?;
231- }
243+ 0
244+ } ;
232245 lap ! ( timer, "Initialize genesis" ) ;
233246
234- // Retrieve the latest height.
235- let latest_height =
236- ledger. vm . block_store ( ) . max_height ( ) . with_context ( || "Failed to load blocks from the ledger" ) ?;
247+ // Ensure that the greatest stored height matches that of the block tree.
248+ ensure ! (
249+ latest_height == ledger. vm( ) . block_store( ) . current_block_height( ) ,
250+ "The stored height is different than the one in the block tree; \
251+ please ensure that the cached block tree is valid or delete the \
252+ 'block_tree' file from the ledger folder"
253+ ) ;
254+
255+ // Verify that the root of the cached block tree matches the one in the storage.
256+ let tree_root = <N :: StateRoot >:: from ( ledger. vm ( ) . block_store ( ) . get_block_tree_root ( ) ) ;
257+ let state_root = ledger
258+ . vm ( )
259+ . block_store ( )
260+ . get_state_root ( latest_height) ?
261+ . ok_or_else ( || anyhow ! ( "Missing state root in the storage" ) ) ?;
262+ ensure ! (
263+ tree_root == state_root,
264+ "The stored state root is different than the one in the block tree;
265+ please ensure that the cached block tree is valid or delete the \
266+ 'block_tree' file from the ledger folder"
267+ ) ;
268+
237269 // Fetch the latest block.
238270 let block = ledger
239271 . get_block ( latest_height)
240272 . with_context ( || format ! ( "Failed to load block {latest_height} from the ledger" ) ) ?;
241273
242274 // Set the current block.
243- ledger. current_block = Arc :: new ( RwLock :: new ( block) ) ;
275+ * ledger. current_block . write ( ) = block;
244276 // Set the current committee (and ensures the latest committee exists).
245- ledger. current_committee = Arc :: new ( RwLock :: new ( Some ( ledger. latest_committee ( ) ?) ) ) ;
277+ * ledger. current_committee . write ( ) = Some ( ledger. latest_committee ( ) ?) ;
246278 // Set the current epoch hash.
247- ledger. current_epoch_hash = Arc :: new ( RwLock :: new ( Some ( ledger. get_epoch_hash ( latest_height) ?) ) ) ;
279+ * ledger. current_epoch_hash . write ( ) = Some ( ledger. get_epoch_hash ( latest_height) ?) ;
248280 // Set the epoch prover cache.
249- ledger. epoch_provers_cache = Arc :: new ( RwLock :: new ( ledger. load_epoch_provers ( ) ) ) ;
281+ * ledger. epoch_provers_cache . write ( ) = ledger. load_epoch_provers ( ) ;
250282
251283 finish ! ( timer, "Initialize ledger" ) ;
252284 Ok ( ledger)
253285 }
286+ }
254287
288+ impl < N : Network , C : ConsensusStorage < N > > Ledger < N , C > {
255289 /// Creates a rocksdb checkpoint in the specified directory, which needs to not exist at the
256290 /// moment of calling. The checkpoints are based on hard links, which means they can both be
257291 /// incremental (i.e. they aren't full physical copies), and used as full rollback points
@@ -261,6 +295,11 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
261295 self . vm . block_store ( ) . backup_database ( path) . map_err ( |err| anyhow ! ( err) )
262296 }
263297
298+ #[ cfg( feature = "rocks" ) ]
299+ pub fn cache_block_tree ( & self ) -> Result < ( ) > {
300+ self . vm . block_store ( ) . cache_block_tree ( )
301+ }
302+
264303 /// Loads the provers and the number of solutions they have submitted for the current epoch.
265304 pub fn load_epoch_provers ( & self ) -> IndexMap < Address < N > , u32 > {
266305 // Fetch the block heights that belong to the current epoch.
@@ -285,12 +324,12 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
285324 }
286325
287326 /// Returns the VM.
288- pub const fn vm ( & self ) -> & VM < N , C > {
327+ pub fn vm ( & self ) -> & VM < N , C > {
289328 & self . vm
290329 }
291330
292331 /// Returns the puzzle.
293- pub const fn puzzle ( & self ) -> & Puzzle < N > {
332+ pub fn puzzle ( & self ) -> & Puzzle < N > {
294333 self . vm . puzzle ( )
295334 }
296335
@@ -481,6 +520,20 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
481520 }
482521}
483522
523+ #[ cfg( feature = "rocks" ) ]
524+ impl < N : Network , C : ConsensusStorage < N > > Drop for InnerLedger < N , C > {
525+ fn drop ( & mut self ) {
526+ // Cache the block tree in order to speed up the next startup; this operation
527+ // is guaranteed to conclude as long as the destructors are allowed to run
528+ // (a clean shutdown, panic = "unwind", an explicit call to `drop`, etc.).
529+ // At the moment this code is executed, the Ledger is guaranteed to be owned
530+ // exclusively by this method, so no other activity may interrupt it.
531+ if let Err ( e) = self . vm . block_store ( ) . cache_block_tree ( ) {
532+ error ! ( "Couldn't cache the block tree: {e}" ) ;
533+ }
534+ }
535+ }
536+
484537pub mod prelude {
485538 pub use crate :: { Ledger , authority, block, block:: * , committee, helpers:: * , narwhal, puzzle, query, store} ;
486539}
0 commit comments