1#![forbid(unsafe_code)]
17#![warn(clippy::cast_possible_truncation)]
18
19#[macro_use]
20extern crate tracing;
21
22pub use ledger_authority as authority;
23pub use ledger_block as block;
24pub use ledger_committee as committee;
25pub use ledger_narwhal as narwhal;
26pub use ledger_puzzle as puzzle;
27pub use ledger_query as query;
28pub use ledger_store as store;
29
30pub use crate::block::*;
31
32#[cfg(feature = "test-helpers")]
33pub use ledger_test_helpers;
34
35mod helpers;
36pub use helpers::*;
37
38mod advance;
39mod check_next_block;
40mod check_transaction_basic;
41mod contains;
42mod find;
43mod get;
44mod is_solution_limit_reached;
45mod iterators;
46
47#[cfg(test)]
48mod tests;
49
50use console::{
51 account::{Address, GraphKey, PrivateKey, ViewKey},
52 network::prelude::*,
53 program::{Ciphertext, Entry, Identifier, Literal, Plaintext, ProgramID, Record, StatePath, Value},
54 types::{Field, Group},
55};
56use ledger_authority::Authority;
57use ledger_committee::Committee;
58use ledger_narwhal::{BatchCertificate, Subdag, Transmission, TransmissionID};
59use ledger_puzzle::{Puzzle, PuzzleSolutions, Solution, SolutionID};
60use ledger_query::QueryTrait;
61use ledger_store::{ConsensusStorage, ConsensusStore};
62use synthesizer::{
63 program::{FinalizeGlobalState, Program},
64 vm::VM,
65};
66
67use aleo_std::{
68 StorageMode,
69 prelude::{finish, lap, timer},
70};
71use anyhow::Result;
72use core::ops::Range;
73use indexmap::IndexMap;
74#[cfg(feature = "locktick")]
75use locktick::parking_lot::{Mutex, RwLock};
76use lru::LruCache;
77#[cfg(not(feature = "locktick"))]
78use parking_lot::{Mutex, RwLock};
79use rand::{prelude::IteratorRandom, rngs::OsRng};
80use std::{borrow::Cow, collections::HashSet, sync::Arc};
81use time::OffsetDateTime;
82
83#[cfg(not(feature = "serial"))]
84use rayon::prelude::*;
85
86pub type RecordMap<N> = IndexMap<Field<N>, Record<N, Plaintext<N>>>;
87
88const COMMITTEE_CACHE_SIZE: usize = 16;
90
91#[derive(Copy, Clone, Debug)]
92pub enum RecordsFilter<N: Network> {
93 All,
95 Spent,
97 Unspent,
99 SlowSpent(PrivateKey<N>),
101 SlowUnspent(PrivateKey<N>),
103}
104
105#[derive(Clone)]
113pub struct Ledger<N: Network, C: ConsensusStorage<N>> {
114 vm: VM<N, C>,
116 genesis_block: Block<N>,
118 current_epoch_hash: Arc<RwLock<Option<N::BlockHash>>>,
120 current_committee: Arc<RwLock<Option<Committee<N>>>>,
136 current_block: Arc<RwLock<Block<N>>>,
138 committee_cache: Arc<Mutex<LruCache<u64, Committee<N>>>>,
146 epoch_provers_cache: Arc<RwLock<IndexMap<Address<N>, u32>>>,
148}
149
150impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
151 pub fn load(genesis_block: Block<N>, storage_mode: StorageMode) -> Result<Self> {
153 let timer = timer!("Ledger::load");
154
155 let genesis_hash = genesis_block.hash();
157 let ledger = Self::load_unchecked(genesis_block, storage_mode)?;
159
160 if !ledger.contains_block_hash(&genesis_hash)? {
162 bail!("Incorrect genesis block (run 'snarkos clean' and try again)")
163 }
164
165 const NUM_BLOCKS: usize = 10;
167 let latest_height = ledger.current_block.read().height();
169 debug_assert_eq!(latest_height, ledger.vm.block_store().max_height().unwrap(), "Mismatch in latest height");
170 let block_heights: Vec<u32> =
172 (0..=latest_height).choose_multiple(&mut OsRng, (latest_height as usize).min(NUM_BLOCKS));
173 cfg_into_iter!(block_heights).try_for_each(|height| {
174 ledger.get_block(height)?;
175 Ok::<_, Error>(())
176 })?;
177 lap!(timer, "Check existence of {NUM_BLOCKS} random blocks");
178
179 finish!(timer);
180 Ok(ledger)
181 }
182
183 pub fn load_unchecked(genesis_block: Block<N>, storage_mode: StorageMode) -> Result<Self> {
185 let timer = timer!("Ledger::load_unchecked");
186
187 info!("Loading the ledger from storage...");
188 let store = match ConsensusStore::<N, C>::open(storage_mode) {
190 Ok(store) => store,
191 Err(e) => bail!("Failed to load ledger (run 'snarkos clean' and try again)\n\n{e}\n"),
192 };
193 lap!(timer, "Load consensus store");
194
195 let vm = VM::from(store)?;
197 lap!(timer, "Initialize a new VM");
198
199 let current_committee = vm.finalize_store().committee_store().current_committee().ok();
201
202 let committee_cache = Arc::new(Mutex::new(LruCache::new(COMMITTEE_CACHE_SIZE.try_into().unwrap())));
204
205 let mut ledger = Self {
207 vm,
208 genesis_block: genesis_block.clone(),
209 current_epoch_hash: Default::default(),
210 current_committee: Arc::new(RwLock::new(current_committee)),
211 current_block: Arc::new(RwLock::new(genesis_block.clone())),
212 committee_cache,
213 epoch_provers_cache: Default::default(),
214 };
215
216 if ledger.vm.block_store().max_height().is_none() {
218 ledger.advance_to_next_block(&genesis_block)?;
220 }
221 lap!(timer, "Initialize genesis");
222
223 let latest_height =
225 ledger.vm.block_store().max_height().ok_or_else(|| anyhow!("Failed to load blocks from the ledger"))?;
226 let block = ledger
228 .get_block(latest_height)
229 .map_err(|_| anyhow!("Failed to load block {latest_height} from the ledger"))?;
230
231 ledger.current_block = Arc::new(RwLock::new(block));
233 ledger.current_committee = Arc::new(RwLock::new(Some(ledger.latest_committee()?)));
235 ledger.current_epoch_hash = Arc::new(RwLock::new(Some(ledger.get_epoch_hash(latest_height)?)));
237 ledger.epoch_provers_cache = Arc::new(RwLock::new(ledger.load_epoch_provers()));
239
240 finish!(timer, "Initialize ledger");
241 Ok(ledger)
242 }
243
244 pub fn load_epoch_provers(&self) -> IndexMap<Address<N>, u32> {
246 let current_block_height = self.vm().block_store().current_block_height();
248 let start_of_epoch = current_block_height.saturating_sub(current_block_height % N::NUM_BLOCKS_PER_EPOCH);
249 let existing_epoch_blocks: Vec<_> = (start_of_epoch..=current_block_height).collect();
250
251 let solution_addresses = cfg_iter!(existing_epoch_blocks)
253 .flat_map(|height| match self.get_solutions(*height).as_deref() {
254 Ok(Some(solutions)) => solutions.iter().map(|(_, s)| s.address()).collect::<Vec<_>>(),
255 _ => vec![],
256 })
257 .collect::<Vec<_>>();
258
259 let mut epoch_provers = IndexMap::new();
261 for address in solution_addresses {
262 epoch_provers.entry(address).and_modify(|e| *e += 1).or_insert(1);
263 }
264 epoch_provers
265 }
266
267 pub const fn vm(&self) -> &VM<N, C> {
269 &self.vm
270 }
271
272 pub const fn puzzle(&self) -> &Puzzle<N> {
274 self.vm.puzzle()
275 }
276
277 pub fn epoch_provers(&self) -> Arc<RwLock<IndexMap<Address<N>, u32>>> {
279 self.epoch_provers_cache.clone()
280 }
281
282 pub fn latest_committee(&self) -> Result<Committee<N>> {
285 match self.current_committee.read().as_ref() {
286 Some(committee) => Ok(committee.clone()),
287 None => self.vm.finalize_store().committee_store().current_committee(),
288 }
289 }
290
291 pub fn latest_state_root(&self) -> N::StateRoot {
293 self.vm.block_store().current_state_root()
294 }
295
296 pub fn latest_epoch_number(&self) -> u32 {
298 self.current_block.read().height() / N::NUM_BLOCKS_PER_EPOCH
299 }
300
301 pub fn latest_epoch_hash(&self) -> Result<N::BlockHash> {
303 match self.current_epoch_hash.read().as_ref() {
304 Some(epoch_hash) => Ok(*epoch_hash),
305 None => self.get_epoch_hash(self.latest_height()),
306 }
307 }
308
309 pub fn latest_block(&self) -> Block<N> {
311 self.current_block.read().clone()
312 }
313
314 pub fn latest_round(&self) -> u64 {
316 self.current_block.read().round()
317 }
318
319 pub fn latest_height(&self) -> u32 {
321 self.current_block.read().height()
322 }
323
324 pub fn latest_hash(&self) -> N::BlockHash {
326 self.current_block.read().hash()
327 }
328
329 pub fn latest_header(&self) -> Header<N> {
331 *self.current_block.read().header()
332 }
333
334 pub fn latest_cumulative_weight(&self) -> u128 {
336 self.current_block.read().cumulative_weight()
337 }
338
339 pub fn latest_cumulative_proof_target(&self) -> u128 {
341 self.current_block.read().cumulative_proof_target()
342 }
343
344 pub fn latest_solutions_root(&self) -> Field<N> {
346 self.current_block.read().header().solutions_root()
347 }
348
349 pub fn latest_coinbase_target(&self) -> u64 {
351 self.current_block.read().coinbase_target()
352 }
353
354 pub fn latest_proof_target(&self) -> u64 {
356 self.current_block.read().proof_target()
357 }
358
359 pub fn last_coinbase_target(&self) -> u64 {
361 self.current_block.read().last_coinbase_target()
362 }
363
364 pub fn last_coinbase_timestamp(&self) -> i64 {
366 self.current_block.read().last_coinbase_timestamp()
367 }
368
369 pub fn latest_timestamp(&self) -> i64 {
371 self.current_block.read().timestamp()
372 }
373
374 pub fn latest_transactions(&self) -> Transactions<N> {
376 self.current_block.read().transactions().clone()
377 }
378}
379
380impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
381 pub fn find_unspent_credits_records(&self, view_key: &ViewKey<N>) -> Result<RecordMap<N>> {
383 let microcredits = Identifier::from_str("microcredits")?;
384 Ok(self
385 .find_records(view_key, RecordsFilter::Unspent)?
386 .filter(|(_, record)| {
387 match record.data().get(µcredits) {
389 Some(Entry::Private(Plaintext::Literal(Literal::U64(amount), _))) => !amount.is_zero(),
390 _ => false,
391 }
392 })
393 .collect::<IndexMap<_, _>>())
394 }
395
396 pub fn create_deploy<R: Rng + CryptoRng>(
400 &self,
401 private_key: &PrivateKey<N>,
402 program: &Program<N>,
403 priority_fee_in_microcredits: u64,
404 query: Option<&dyn QueryTrait<N>>,
405 rng: &mut R,
406 ) -> Result<Transaction<N>> {
407 let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
409 ensure!(!records.len().is_zero(), "The Aleo account has no records to spend.");
410 let mut records = records.values();
411
412 let fee_record = Some(records.next().unwrap().clone());
414
415 self.vm.deploy(private_key, program, fee_record, priority_fee_in_microcredits, query, rng)
417 }
418
419 pub fn create_transfer<R: Rng + CryptoRng>(
423 &self,
424 private_key: &PrivateKey<N>,
425 to: Address<N>,
426 amount_in_microcredits: u64,
427 priority_fee_in_microcredits: u64,
428 query: Option<&dyn QueryTrait<N>>,
429 rng: &mut R,
430 ) -> Result<Transaction<N>> {
431 let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
433 ensure!(!records.len().is_zero(), "The Aleo account has no records to spend.");
434 let mut records = records.values();
435
436 let inputs = [
438 Value::Record(records.next().unwrap().clone()),
439 Value::from_str(&format!("{to}"))?,
440 Value::from_str(&format!("{amount_in_microcredits}u64"))?,
441 ];
442
443 let fee_record = Some(records.next().unwrap().clone());
445
446 self.vm.execute(
448 private_key,
449 ("credits.aleo", "transfer_private"),
450 inputs.iter(),
451 fee_record,
452 priority_fee_in_microcredits,
453 query,
454 rng,
455 )
456 }
457}
458
459#[cfg(test)]
460pub(crate) mod test_helpers {
461 use crate::Ledger;
462 use aleo_std::StorageMode;
463 use console::{
464 account::{Address, PrivateKey, ViewKey},
465 network::MainnetV0,
466 prelude::*,
467 };
468 use ledger_store::ConsensusStore;
469 use snarkvm_circuit::network::AleoV0;
470 use synthesizer::vm::VM;
471
472 pub(crate) type CurrentNetwork = MainnetV0;
473 pub(crate) type CurrentAleo = AleoV0;
474
475 #[cfg(not(feature = "rocks"))]
476 pub(crate) type CurrentLedger =
477 Ledger<CurrentNetwork, ledger_store::helpers::memory::ConsensusMemory<CurrentNetwork>>;
478 #[cfg(feature = "rocks")]
479 pub(crate) type CurrentLedger = Ledger<CurrentNetwork, ledger_store::helpers::rocksdb::ConsensusDB<CurrentNetwork>>;
480
481 #[cfg(not(feature = "rocks"))]
482 pub(crate) type CurrentConsensusStore =
483 ConsensusStore<CurrentNetwork, ledger_store::helpers::memory::ConsensusMemory<CurrentNetwork>>;
484 #[cfg(feature = "rocks")]
485 pub(crate) type CurrentConsensusStore =
486 ConsensusStore<CurrentNetwork, ledger_store::helpers::rocksdb::ConsensusDB<CurrentNetwork>>;
487
488 #[allow(dead_code)]
489 pub(crate) struct TestEnv {
490 pub ledger: CurrentLedger,
491 pub private_key: PrivateKey<CurrentNetwork>,
492 pub view_key: ViewKey<CurrentNetwork>,
493 pub address: Address<CurrentNetwork>,
494 }
495
496 pub(crate) fn sample_test_env(rng: &mut (impl Rng + CryptoRng)) -> TestEnv {
497 let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
499 let view_key = ViewKey::try_from(&private_key).unwrap();
500 let address = Address::try_from(&private_key).unwrap();
501 let ledger = sample_ledger(private_key, rng);
503 TestEnv { ledger, private_key, view_key, address }
505 }
506
507 pub(crate) fn sample_ledger(
508 private_key: PrivateKey<CurrentNetwork>,
509 rng: &mut (impl Rng + CryptoRng),
510 ) -> CurrentLedger {
511 let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
513 let genesis = VM::from(store).unwrap().genesis_beacon(&private_key, rng).unwrap();
515 let ledger = CurrentLedger::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
517 assert_eq!(genesis, ledger.get_block(0).unwrap());
519 ledger
521 }
522}