Skip to main content

snarkvm_ledger/
lib.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![forbid(unsafe_code)]
17#![warn(clippy::cast_possible_truncation)]
18
19extern crate snarkvm_console as console;
20
21#[macro_use]
22extern crate tracing;
23
24pub use snarkvm_ledger_authority as authority;
25pub use snarkvm_ledger_block as block;
26pub use snarkvm_ledger_committee as committee;
27pub use snarkvm_ledger_narwhal as narwhal;
28pub use snarkvm_ledger_puzzle as puzzle;
29pub use snarkvm_ledger_query as query;
30pub use snarkvm_ledger_store as store;
31
32#[cfg(any(test, feature = "test-helpers"))]
33pub mod test_helpers;
34
35mod error;
36pub use error::*;
37
38mod helpers;
39pub use helpers::*;
40
41pub use crate::block::*;
42
43mod check_next_block;
44pub use check_next_block::{CheckBlockError, PendingBlock};
45
46mod advance;
47mod check_transaction_basic;
48mod contains;
49mod find;
50mod get;
51mod is_solution_limit_reached;
52mod iterators;
53
54#[cfg(test)]
55mod tests;
56
57use console::{
58    account::{Address, GraphKey, PrivateKey, ViewKey},
59    network::prelude::*,
60    program::{Ciphertext, Entry, Identifier, Literal, Plaintext, ProgramID, Record, StatePath, Value},
61    types::{Field, Group},
62};
63use snarkvm_ledger_authority::Authority;
64use snarkvm_ledger_committee::Committee;
65use snarkvm_ledger_narwhal::{BatchCertificate, Subdag, Transmission, TransmissionID};
66use snarkvm_ledger_puzzle::{Puzzle, PuzzleSolutions, Solution, SolutionID};
67use snarkvm_ledger_query::QueryTrait;
68use snarkvm_ledger_store::{ConsensusStorage, ConsensusStore};
69use snarkvm_synthesizer::{
70    program::{FinalizeGlobalState, Program},
71    vm::VM,
72};
73
74use aleo_std::{
75    StorageMode,
76    prelude::{finish, lap, timer},
77};
78use anyhow::{Context, Result};
79use cfg_if::cfg_if;
80use core::ops::Range;
81use indexmap::IndexMap;
82#[cfg(feature = "locktick")]
83use locktick::parking_lot::{Mutex, RwLock};
84use lru::LruCache;
85#[cfg(not(feature = "locktick"))]
86use parking_lot::{Mutex, RwLock};
87use rand::prelude::IteratorRandom;
88use std::{borrow::Cow, collections::HashSet, sync::Arc};
89use time::OffsetDateTime;
90
91#[cfg(not(feature = "serial"))]
92use rayon::prelude::*;
93
94pub type RecordMap<N> = IndexMap<Field<N>, Record<N, Plaintext<N>>>;
95
96/// The capacity of the LRU cache holding the recently queried committees.
97const COMMITTEE_CACHE_SIZE: usize = 16;
98
99/// Options describing the deterministic dev committee to install on a [`Ledger`].
100///
101/// Installs an override that is returned by [`Ledger::get_committee_for_round`]
102/// and [`Ledger::get_committee_lookback_for_round`] for any round at or after
103/// the resolved start round.
104///
105/// This is used by devnet deployments that hot-swap the on-chain mainnet
106/// committee with a deterministic dev committee on top of a mainnet snapshot.
107/// Without it, validators that fall behind enough to enter `BlockSync` (and
108/// therefore go through `check_block_subdag_quorum`) would look up the mainnet
109/// committee at the lookback round and reject the dev-signed certificates.
110#[cfg(feature = "dev-committee")]
111#[derive(Clone, Copy, Debug)]
112pub struct DevCommitteeOptions {
113    /// The round the committee was swapped.
114    /// If none is set, the latest round in the ledger is chosen.
115    pub start_round: Option<u64>,
116    /// Number of members in the dev committee.
117    pub dev_num_validators: u16,
118    /// Seed used to deterministically derive the dev committee's keys.
119    ///
120    /// Every node in a devnet deployment must use the same value, or their
121    /// committees won't match. Callers can pass a project-wide constant such
122    /// as `snarkos_utilities::DEVELOPMENT_MODE_RNG_SEED`.
123    pub seed: u64,
124}
125
126/// Deterministically builds the dev committee from the given parameters.
127///
128/// Every member is given equal stake (`MIN_VALIDATOR_STAKE`). Two callers that
129/// pass the same `(start_round, dev_num_validators, seed)` get bitwise-
130/// identical committees.
131#[cfg(feature = "dev-committee")]
132pub fn build_dev_committee<N: Network>(start_round: u64, dev_num_validators: u16, seed: u64) -> Result<Committee<N>> {
133    use rand::SeedableRng;
134    let mut rng = rand_chacha::ChaChaRng::seed_from_u64(seed);
135    let dev_keys = (0..dev_num_validators).map(|_| PrivateKey::<N>::new(&mut rng)).collect::<Result<Vec<_>>>()?;
136    let members = dev_keys
137        .iter()
138        .map(Address::<N>::try_from)
139        .collect::<Result<Vec<_>>>()?
140        .into_iter()
141        .map(|address| (address, (snarkvm_ledger_committee::MIN_VALIDATOR_STAKE, true, 0)))
142        .collect::<IndexMap<_, _>>();
143    Committee::new(start_round, members)
144}
145
146#[derive(Copy, Clone, Debug)]
147pub enum RecordsFilter<N: Network> {
148    /// Returns all records associated with the account.
149    All,
150    /// Returns only records associated with the account that are **spent** with the graph key.
151    Spent,
152    /// Returns only records associated with the account that are **not spent** with the graph key.
153    Unspent,
154    /// Returns all records associated with the account that are **spent** with the given private key.
155    SlowSpent(PrivateKey<N>),
156    /// Returns all records associated with the account that are **not spent** with the given private key.
157    SlowUnspent(PrivateKey<N>),
158}
159
160/// State of the entire chain.
161///
162/// All stored state is held in the `VM`, while Ledger holds the `VM` and relevant cache data.
163///
164/// The constructor is [`Ledger::load`],
165/// which loads the ledger from storage,
166/// or initializes it with the genesis block if the storage is empty
167#[derive(Clone)]
168pub struct Ledger<N: Network, C: ConsensusStorage<N>>(Arc<InnerLedger<N, C>>);
169
170impl<N: Network, C: ConsensusStorage<N>> Deref for Ledger<N, C> {
171    type Target = InnerLedger<N, C>;
172
173    fn deref(&self) -> &Self::Target {
174        &self.0
175    }
176}
177
178#[doc(hidden)]
179pub struct InnerLedger<N: Network, C: ConsensusStorage<N>> {
180    /// The VM state.
181    vm: VM<N, C>,
182    /// The genesis block.
183    genesis_block: Block<N>,
184
185    /// The current epoch hash.
186    current_epoch_hash: RwLock<Option<N::BlockHash>>,
187
188    /// The committee resulting from all the on-chain staking activity.
189    ///
190    /// This includes any bonding and unbonding transactions in the latest block.
191    /// The starting point, in the genesis block, is the genesis committee.
192    /// If the latest block has round `R`, `current_committee` is
193    /// the committee bonded for rounds `R+1`, `R+2`, and perhaps others
194    /// (unless a block at round `R+2` changes the committee).
195    /// Note that this committee is not active (i.e. in charge of running consensus)
196    /// until round `R + 1 + L`, where `L` is the lookback round distance.
197    ///
198    /// This committee is always well-defined
199    /// (in particular, it is the genesis committee when the `Ledger` is empty, or only has the genesis block).
200    /// So the `Option` should always be `Some`,
201    /// but there are cases in which it is `None`,
202    /// probably only temporarily when loading/initializing the ledger,
203    current_committee: RwLock<Option<Committee<N>>>,
204
205    /// The latest block that was added to the ledger.
206    ///
207    /// This lock is also to ensure *atomicity* of calls to `[Ledger::advance`], i.e., to guarantee that
208    /// there cannot be multiple concurrent ledger advancements and that ledger state cannot be read while advancement happens.
209    current_block: RwLock<Block<N>>,
210
211    /// The recent committees of interest paired with their applicable rounds.
212    ///
213    /// Each entry consisting of a round `R` and a committee `C`,
214    /// says that `C` is the bonded committee at round `R`,
215    /// i.e. resulting from all the bonding and unbonding transactions before `R`.
216    /// If `L` is the lookback round distance, `C` is the active committee at round `R + L`
217    /// (i.e. the committee in charge of running consensus at round `R + L`).
218    committee_cache: Mutex<LruCache<u64, Committee<N>>>,
219    /// The cache that holds the provers and the number of solutions they have submitted for the current epoch.
220    epoch_provers_cache: Arc<RwLock<IndexMap<Address<N>, u32>>>,
221
222    /// Optional dev committee, returned for any round `>= committee.starting_round()`.
223    ///
224    /// See [`DevCommitteeOptions`] for details. This is only consulted by the
225    /// round-based committee lookups; the height-based lookups and the
226    /// on-chain `current_committee` continue to read from the finalize store.
227    #[cfg(feature = "dev-committee")]
228    dev_committee: Option<Committee<N>>,
229}
230
231impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
232    /// Loads the ledger from storage.
233    pub fn load(genesis_block: Block<N>, storage_mode: StorageMode) -> Result<Self> {
234        let timer = timer!("Ledger::load");
235
236        // Retrieve the genesis hash.
237        let genesis_hash = genesis_block.hash();
238        // Initialize the ledger.
239        let ledger = Self::load_unchecked(genesis_block, storage_mode)?;
240
241        // Ensure the ledger contains the correct genesis block.
242        if !ledger.contains_block_hash(&genesis_hash)? {
243            bail!("Incorrect genesis block (run 'snarkos clean' and try again)")
244        }
245
246        // Spot check the integrity of `NUM_BLOCKS` random blocks upon bootup.
247        const NUM_BLOCKS: usize = 10;
248        // Retrieve the latest height.
249        let latest_height = ledger.current_block.read().height();
250        debug_assert_eq!(latest_height, ledger.vm.block_store().max_height().unwrap(), "Mismatch in latest height");
251        // Sample random block heights.
252        let block_heights: Vec<u32> =
253            (0..=latest_height).sample(&mut rand::rng(), (latest_height as usize).min(NUM_BLOCKS));
254        cfg_into_iter!(block_heights).try_for_each(|height| {
255            ledger.get_block(height)?;
256            Ok::<_, Error>(())
257        })?;
258        lap!(timer, "Check existence of {NUM_BLOCKS} random blocks");
259
260        finish!(timer);
261        Ok(ledger)
262    }
263
264    /// Loads the ledger from storage, without performing integrity checks.
265    pub fn load_unchecked(genesis_block: Block<N>, storage_mode: StorageMode) -> Result<Self> {
266        cfg_if! {
267            if #[cfg(feature="dev-committee")] {
268                Self::load_unchecked_inner(genesis_block, storage_mode, None)
269            } else {
270                Self::load_unchecked_inner(genesis_block, storage_mode)
271            }
272        }
273    }
274
275    /// Like [`Self::load`], but installs a dev committee override that is
276    /// returned by [`Self::get_committee_for_round`] and
277    /// [`Self::get_committee_lookback_for_round`] for any round at or after
278    /// `dev_committee.start_round`. See [`DevCommitteeOptions`].
279    #[cfg(feature = "dev-committee")]
280    pub fn load_with_dev_committee(
281        genesis_block: Block<N>,
282        storage_mode: StorageMode,
283        dev_committee_opts: DevCommitteeOptions,
284    ) -> Result<Self> {
285        let timer = timer!("Ledger::load_with_dev_committee");
286
287        // Retrieve the genesis hash.
288        let genesis_hash = genesis_block.hash();
289        // Initialize the ledger.
290        let ledger = Self::load_unchecked_with_dev_committee(genesis_block, storage_mode, dev_committee_opts)?;
291
292        // Ensure the ledger contains the correct genesis block.
293        if !ledger.contains_block_hash(&genesis_hash)? {
294            bail!("Incorrect genesis block (run 'snarkos clean' and try again)")
295        }
296
297        // Spot check the integrity of `NUM_BLOCKS` random blocks upon bootup.
298        const NUM_BLOCKS: usize = 10;
299        let latest_height = ledger.current_block.read().height();
300        debug_assert_eq!(latest_height, ledger.vm.block_store().max_height().unwrap(), "Mismatch in latest height");
301        let block_heights: Vec<u32> =
302            (0..=latest_height).sample(&mut rand::rng(), (latest_height as usize).min(NUM_BLOCKS));
303        cfg_into_iter!(block_heights).try_for_each(|height| {
304            ledger.get_block(height)?;
305            Ok::<_, Error>(())
306        })?;
307        lap!(timer, "Check existence of {NUM_BLOCKS} random blocks");
308
309        finish!(timer);
310        Ok(ledger)
311    }
312
313    /// Like [`Self::load_unchecked`], but installs a dev committee override.
314    #[cfg(feature = "dev-committee")]
315    pub fn load_unchecked_with_dev_committee(
316        genesis_block: Block<N>,
317        storage_mode: StorageMode,
318        dev_committee_opts: DevCommitteeOptions,
319    ) -> Result<Self> {
320        Self::load_unchecked_inner(genesis_block, storage_mode, Some(dev_committee_opts))
321    }
322
323    fn load_unchecked_inner(
324        genesis_block: Block<N>,
325        storage_mode: StorageMode,
326        #[cfg(feature = "dev-committee")] dev_committee_opts: Option<DevCommitteeOptions>,
327    ) -> Result<Self> {
328        let timer = timer!("Ledger::load_unchecked");
329
330        info!("Loading the ledger from storage...");
331        // Initialize the consensus store.
332        let store = match ConsensusStore::<N, C>::open(storage_mode) {
333            Ok(store) => store,
334            Err(e) => bail!("Failed to load ledger (run 'snarkos clean' and try again)\n\n{e}\n"),
335        };
336        lap!(timer, "Load consensus store");
337
338        // Initialize a new VM.
339        let vm = VM::from(store)?;
340        lap!(timer, "Initialize a new VM");
341
342        // Retrieve the current committee.
343        let current_committee = vm.finalize_store().committee_store().current_committee().ok();
344        // Create a committee cache.
345        let committee_cache = Mutex::new(LruCache::new(COMMITTEE_CACHE_SIZE.try_into().unwrap()));
346
347        // If a dev committee override was requested, resolve its start round
348        // (defaulting to the round of the latest block in storage, or the
349        // genesis round when storage is empty) and build the committee now,
350        // before the `InnerLedger` is constructed.
351        #[cfg(feature = "dev-committee")]
352        let dev_committee = match dev_committee_opts {
353            Some(opts) => {
354                let start_round = if let Some(round) = opts.start_round {
355                    round
356                } else {
357                    match vm.block_store().max_height() {
358                        Some(h) => {
359                            let hash = vm
360                                .block_store()
361                                .get_block_hash(h)?
362                                .ok_or_else(|| anyhow!("Missing block hash at height {h}"))?;
363                            let block = vm
364                                .block_store()
365                                .get_block(&hash)?
366                                .ok_or_else(|| anyhow!("Missing block at height {h}"))?;
367                            block.round()
368                        }
369                        None => genesis_block.round(),
370                    }
371                };
372
373                Some(build_dev_committee::<N>(start_round, opts.dev_num_validators, opts.seed)?)
374            }
375            None => None,
376        };
377
378        // Initialize the ledger.
379        let ledger = Self(Arc::new(InnerLedger {
380            vm,
381            genesis_block: genesis_block.clone(),
382            current_epoch_hash: Default::default(),
383            current_committee: RwLock::new(current_committee),
384            current_block: RwLock::new(genesis_block.clone()),
385            committee_cache,
386            epoch_provers_cache: Default::default(),
387            #[cfg(feature = "dev-committee")]
388            dev_committee,
389        }));
390
391        // Attempt to obtain the maximum height from the storage.
392        let max_stored_height = ledger.vm.block_store().max_height();
393
394        // If the block store is empty, add the genesis block.
395        let latest_height = if let Some(max_height) = max_stored_height {
396            max_height
397        } else {
398            ledger.advance_to_next_block(&genesis_block)?;
399            0
400        };
401        lap!(timer, "Initialize genesis");
402
403        // Ensure that the greatest stored height matches that of the block tree.
404        let tree_derived_block_height = ledger.vm().block_store().current_block_height();
405        ensure!(
406            latest_height == tree_derived_block_height,
407            "The stored height ({latest_height}) is different than the one in \
408            the block tree ({tree_derived_block_height}); please ensure that \
409            the cached block tree is valid or delete the 'block_tree' file from \
410            the ledger folder"
411        );
412
413        // Verify that the root of the cached block tree matches the one in the storage.
414        let tree_root = <N::StateRoot>::from(ledger.vm().block_store().get_block_tree_root());
415        let state_root = ledger
416            .vm()
417            .block_store()
418            .get_state_root(latest_height)?
419            .ok_or_else(|| anyhow!("Missing state root in the storage"))?;
420        ensure!(
421            tree_root == state_root,
422            "The stored state root is different than the one in the block tree;
423            please ensure that the cached block tree is valid or delete the \
424            'block_tree' file from the ledger folder"
425        );
426
427        // Fetch the latest block.
428        let block = ledger
429            .get_block(latest_height)
430            .with_context(|| format!("Failed to load block {latest_height} from the ledger"))?;
431
432        // Set the current block.
433        *ledger.current_block.write() = block;
434        // Set the current committee (and ensures the latest committee exists).
435        *ledger.current_committee.write() = Some(ledger.latest_committee()?);
436        // Set the current epoch hash.
437        *ledger.current_epoch_hash.write() = Some(ledger.get_epoch_hash(latest_height)?);
438        // Set the epoch prover cache.
439        *ledger.epoch_provers_cache.write() = ledger.load_epoch_provers();
440
441        finish!(timer, "Initialize ledger");
442        Ok(ledger)
443    }
444}
445
446impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
447    /// Returns the installed dev committee, if any.
448    ///
449    /// The committee is determined at load time by
450    /// [`Ledger::load_with_dev_committee`] (or its `unchecked` variant) and is
451    /// immutable thereafter.
452    #[cfg(feature = "dev-committee")]
453    pub fn dev_committee(&self) -> Option<&Committee<N>> {
454        self.dev_committee.as_ref()
455    }
456
457    /// Creates a rocksdb checkpoint in the specified directory, which needs to not exist at the
458    /// moment of calling. The checkpoints are based on hard links, which means they can both be
459    /// incremental (i.e. they aren't full physical copies), and used as full rollback points
460    /// (a checkpoint can be used to completely replace the original ledger).
461    #[cfg(feature = "rocks")]
462    pub fn backup_database<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
463        self.vm.block_store().backup_database(path).map_err(|err| anyhow!(err))
464    }
465
466    #[cfg(feature = "rocks")]
467    pub fn cache_block_tree(&self) -> Result<()> {
468        self.vm.block_store().cache_block_tree()
469    }
470
471    /// Loads the provers and the number of solutions they have submitted for the current epoch.
472    pub fn load_epoch_provers(&self) -> IndexMap<Address<N>, u32> {
473        // Fetch the current block height.
474        let current_block_height = self.vm().block_store().current_block_height();
475
476        // Determine the first block to start checking.
477        // Note that the epoch boundary (where current_block_height % N::NUM_BLOCKS_PER_EPOCH == 0) can contain solutions
478        // for the previous epoch X. The subsequent block is the first block to contain solutions for the current epoch X+1.
479        let next_block_height = current_block_height.saturating_add(1);
480        let start = next_block_height.saturating_sub(current_block_height % N::NUM_BLOCKS_PER_EPOCH);
481
482        // If the epoch contains no blocks that have solutions for the epoch.
483        if start > current_block_height {
484            return IndexMap::new();
485        }
486
487        // Collect the addresses of the solutions submitted in the current epoch.
488        let existing_epoch_blocks: Vec<_> = (start..=current_block_height).collect();
489        let solution_addresses = cfg_iter!(existing_epoch_blocks)
490            .flat_map(|height| match self.get_solutions(*height).as_deref() {
491                Ok(Some(solutions)) => solutions.iter().map(|(_, s)| s.address()).collect::<Vec<_>>(),
492                _ => vec![],
493            })
494            .collect::<Vec<_>>();
495
496        // Count the number of occurrences of each address in the epoch blocks.
497        let mut epoch_provers = IndexMap::new();
498        for address in solution_addresses {
499            epoch_provers.entry(address).and_modify(|e| *e += 1).or_insert(1);
500        }
501        epoch_provers
502    }
503
504    /// Returns the VM.
505    pub fn vm(&self) -> &VM<N, C> {
506        &self.vm
507    }
508
509    /// Returns the puzzle.
510    pub fn puzzle(&self) -> &Puzzle<N> {
511        self.vm.puzzle()
512    }
513
514    /// Returns the size of the block cache (or `None` if the block cache is not enabled).
515    pub fn block_cache_size(&self) -> Option<u32> {
516        self.vm.block_store().cache_size()
517    }
518
519    /// Returns the provers and the number of solutions they have submitted for the current epoch.
520    pub fn epoch_provers(&self) -> Arc<RwLock<IndexMap<Address<N>, u32>>> {
521        self.epoch_provers_cache.clone()
522    }
523
524    /// Returns the latest committee,
525    /// i.e. the committee resulting from all the on-chain staking activity.
526    pub fn latest_committee(&self) -> Result<Committee<N>> {
527        match self.current_committee.read().as_ref() {
528            Some(committee) => Ok(committee.clone()),
529            None => self.vm.finalize_store().committee_store().current_committee(),
530        }
531    }
532
533    /// Returns the latest state root.
534    pub fn latest_state_root(&self) -> N::StateRoot {
535        self.vm.block_store().current_state_root()
536    }
537
538    /// Returns the latest epoch number.
539    pub fn latest_epoch_number(&self) -> u32 {
540        self.current_block.read().height() / N::NUM_BLOCKS_PER_EPOCH
541    }
542
543    /// Returns the latest epoch hash.
544    pub fn latest_epoch_hash(&self) -> Result<N::BlockHash> {
545        match self.current_epoch_hash.read().as_ref() {
546            Some(epoch_hash) => Ok(*epoch_hash),
547            None => self.get_epoch_hash(self.latest_height()),
548        }
549    }
550
551    /// Returns the latest block.
552    pub fn latest_block(&self) -> Block<N> {
553        self.current_block.read().clone()
554    }
555
556    /// Returns the latest round number.
557    pub fn latest_round(&self) -> u64 {
558        self.current_block.read().round()
559    }
560
561    /// Returns the latest block height.
562    pub fn latest_height(&self) -> u32 {
563        self.current_block.read().height()
564    }
565
566    /// Returns the latest block hash.
567    pub fn latest_hash(&self) -> N::BlockHash {
568        self.current_block.read().hash()
569    }
570
571    /// Returns the latest block header.
572    pub fn latest_header(&self) -> Header<N> {
573        *self.current_block.read().header()
574    }
575
576    /// Returns the latest block cumulative weight.
577    pub fn latest_cumulative_weight(&self) -> u128 {
578        self.current_block.read().cumulative_weight()
579    }
580
581    /// Returns the latest block cumulative proof target.
582    pub fn latest_cumulative_proof_target(&self) -> u128 {
583        self.current_block.read().cumulative_proof_target()
584    }
585
586    /// Returns the latest block solutions root.
587    pub fn latest_solutions_root(&self) -> Field<N> {
588        self.current_block.read().header().solutions_root()
589    }
590
591    /// Returns the latest block coinbase target.
592    pub fn latest_coinbase_target(&self) -> u64 {
593        self.current_block.read().coinbase_target()
594    }
595
596    /// Returns the latest block proof target.
597    pub fn latest_proof_target(&self) -> u64 {
598        self.current_block.read().proof_target()
599    }
600
601    /// Returns the last coinbase target.
602    pub fn last_coinbase_target(&self) -> u64 {
603        self.current_block.read().last_coinbase_target()
604    }
605
606    /// Returns the last coinbase timestamp.
607    pub fn last_coinbase_timestamp(&self) -> i64 {
608        self.current_block.read().last_coinbase_timestamp()
609    }
610
611    /// Returns the latest block timestamp.
612    pub fn latest_timestamp(&self) -> i64 {
613        self.current_block.read().timestamp()
614    }
615
616    /// Returns the latest block transactions.
617    pub fn latest_transactions(&self) -> Transactions<N> {
618        self.current_block.read().transactions().clone()
619    }
620}
621
622impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
623    /// Returns the unspent `credits.aleo` records.
624    pub fn find_unspent_credits_records(&self, view_key: &ViewKey<N>) -> Result<RecordMap<N>> {
625        let microcredits = Identifier::from_str("microcredits")?;
626        Ok(self
627            .find_records(view_key, RecordsFilter::Unspent)?
628            .filter(|(_, record)| {
629                // TODO (raychu86): Find cleaner approach and check that the record is associated with the `credits.aleo` program
630                match record.data().get(&microcredits) {
631                    Some(Entry::Private(Plaintext::Literal(Literal::U64(amount), _))) => !amount.is_zero(),
632                    _ => false,
633                }
634            })
635            .collect::<IndexMap<_, _>>())
636    }
637
638    /// Creates a deploy transaction.
639    ///
640    /// The `priority_fee_in_microcredits` is an additional fee **on top** of the deployment fee.
641    pub fn create_deploy<R: Rng + CryptoRng>(
642        &self,
643        private_key: &PrivateKey<N>,
644        program: &Program<N>,
645        priority_fee_in_microcredits: u64,
646        query: Option<&dyn QueryTrait<N>>,
647        rng: &mut R,
648    ) -> Result<Transaction<N>, CreateDeployError> {
649        // Fetch the unspent records.
650        let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
651        if records.len().is_zero() {
652            return Err(anyhow!("The Aleo account has no records to spend.").into());
653        }
654        let mut records = records.values();
655
656        // Prepare the fee record.
657        let fee_record = Some(records.next().unwrap().clone());
658
659        // Create a new deploy transaction.
660        Ok(self.vm.deploy(private_key, program, fee_record, priority_fee_in_microcredits, query, rng)?)
661    }
662
663    /// Creates a transfer transaction.
664    ///
665    /// The `priority_fee_in_microcredits` is an additional fee **on top** of the execution fee.
666    pub fn create_transfer<R: Rng + CryptoRng>(
667        &self,
668        private_key: &PrivateKey<N>,
669        to: Address<N>,
670        amount_in_microcredits: u64,
671        priority_fee_in_microcredits: u64,
672        query: Option<&dyn QueryTrait<N>>,
673        rng: &mut R,
674    ) -> Result<Transaction<N>, CreateTransferError> {
675        // Fetch the unspent records.
676        let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
677        if records.len() < 2 {
678            return Err(anyhow!("The Aleo account does not have enough records to spend.").into());
679        }
680        let mut records = records.values();
681
682        // Prepare the inputs.
683        let inputs = [
684            Value::Record(records.next().unwrap().clone()),
685            Value::from_str(&format!("{to}"))?,
686            Value::from_str(&format!("{amount_in_microcredits}u64"))?,
687        ];
688
689        // Prepare the fee.
690        let fee_record = Some(records.next().unwrap().clone());
691
692        // Create a new execute transaction.
693        Ok(self.vm.execute(
694            private_key,
695            ("credits.aleo", "transfer_private"),
696            inputs.iter(),
697            fee_record,
698            priority_fee_in_microcredits,
699            query,
700            rng,
701        )?)
702    }
703}
704
705#[cfg(feature = "rocks")]
706impl<N: Network, C: ConsensusStorage<N>> Drop for InnerLedger<N, C> {
707    fn drop(&mut self) {
708        // Cache the block tree in order to speed up the next startup; this operation
709        // is guaranteed to conclude as long as the destructors are allowed to run
710        // (a clean shutdown, panic = "unwind", an explicit call to `drop`, etc.).
711        // At the moment this code is executed, the Ledger is guaranteed to be owned
712        // exclusively by this method, so no other activity may interrupt it.
713        if let Err(e) = self.vm.block_store().cache_block_tree() {
714            error!("Couldn't cache the block tree: {e}");
715        }
716    }
717}
718
719pub mod prelude {
720    pub use crate::{Ledger, authority, block, block::*, committee, helpers::*, narwhal, puzzle, query, store};
721}