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 iterators;
45
46#[cfg(test)]
47mod tests;
48
49use console::{
50 account::{Address, GraphKey, PrivateKey, ViewKey},
51 network::prelude::*,
52 program::{Ciphertext, Entry, Identifier, Literal, Plaintext, ProgramID, Record, StatePath, Value},
53 types::{Field, Group},
54};
55use ledger_authority::Authority;
56use ledger_committee::Committee;
57use ledger_narwhal::{BatchCertificate, Subdag, Transmission, TransmissionID};
58use ledger_puzzle::{Puzzle, PuzzleSolutions, Solution, SolutionID};
59use ledger_query::Query;
60use ledger_store::{ConsensusStorage, ConsensusStore};
61use synthesizer::{
62 program::{FinalizeGlobalState, Program},
63 vm::VM,
64};
65
66use aleo_std::{
67 StorageMode,
68 prelude::{finish, lap, timer},
69};
70use anyhow::Result;
71use core::ops::Range;
72use indexmap::IndexMap;
73#[cfg(feature = "locktick")]
74use locktick::parking_lot::{Mutex, RwLock};
75use lru::LruCache;
76#[cfg(not(feature = "locktick"))]
77use parking_lot::{Mutex, RwLock};
78use rand::{prelude::IteratorRandom, rngs::OsRng};
79use std::{borrow::Cow, collections::HashSet, sync::Arc};
80use time::OffsetDateTime;
81
82#[cfg(not(feature = "serial"))]
83use rayon::prelude::*;
84
85pub type RecordMap<N> = IndexMap<Field<N>, Record<N, Plaintext<N>>>;
86
87const COMMITTEE_CACHE_SIZE: usize = 16;
89
90#[derive(Copy, Clone, Debug)]
91pub enum RecordsFilter<N: Network> {
92 All,
94 Spent,
96 Unspent,
98 SlowSpent(PrivateKey<N>),
100 SlowUnspent(PrivateKey<N>),
102}
103
104#[derive(Clone)]
112pub struct Ledger<N: Network, C: ConsensusStorage<N>> {
113 vm: VM<N, C>,
115 genesis_block: Block<N>,
117 current_epoch_hash: Arc<RwLock<Option<N::BlockHash>>>,
119 current_committee: Arc<RwLock<Option<Committee<N>>>>,
135 current_block: Arc<RwLock<Block<N>>>,
137 committee_cache: Arc<Mutex<LruCache<u64, Committee<N>>>>,
145}
146
147impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
148 pub fn load(genesis_block: Block<N>, storage_mode: StorageMode) -> Result<Self> {
150 let timer = timer!("Ledger::load");
151
152 let genesis_hash = genesis_block.hash();
154 let ledger = Self::load_unchecked(genesis_block, storage_mode)?;
156
157 if !ledger.contains_block_hash(&genesis_hash)? {
159 bail!("Incorrect genesis block (run 'snarkos clean' and try again)")
160 }
161
162 const NUM_BLOCKS: usize = 10;
164 let latest_height = ledger.current_block.read().height();
166 debug_assert_eq!(latest_height, ledger.vm.block_store().max_height().unwrap(), "Mismatch in latest height");
167 let block_heights: Vec<u32> =
169 (0..=latest_height).choose_multiple(&mut OsRng, (latest_height as usize).min(NUM_BLOCKS));
170 cfg_into_iter!(block_heights).try_for_each(|height| {
171 ledger.get_block(height)?;
172 Ok::<_, Error>(())
173 })?;
174 lap!(timer, "Check existence of {NUM_BLOCKS} random blocks");
175
176 finish!(timer);
177 Ok(ledger)
178 }
179
180 pub fn load_unchecked(genesis_block: Block<N>, storage_mode: StorageMode) -> Result<Self> {
182 let timer = timer!("Ledger::load_unchecked");
183
184 info!("Loading the ledger from storage...");
185 let store = match ConsensusStore::<N, C>::open(storage_mode) {
187 Ok(store) => store,
188 Err(e) => bail!("Failed to load ledger (run 'snarkos clean' and try again)\n\n{e}\n"),
189 };
190 lap!(timer, "Load consensus store");
191
192 let vm = VM::from(store)?;
194 lap!(timer, "Initialize a new VM");
195
196 let current_committee = vm.finalize_store().committee_store().current_committee().ok();
198
199 let committee_cache = Arc::new(Mutex::new(LruCache::new(COMMITTEE_CACHE_SIZE.try_into().unwrap())));
201
202 let mut ledger = Self {
204 vm,
205 genesis_block: genesis_block.clone(),
206 current_epoch_hash: Default::default(),
207 current_committee: Arc::new(RwLock::new(current_committee)),
208 current_block: Arc::new(RwLock::new(genesis_block.clone())),
209 committee_cache,
210 };
211
212 if ledger.vm.block_store().max_height().is_none() {
214 ledger.advance_to_next_block(&genesis_block)?;
216 }
217 lap!(timer, "Initialize genesis");
218
219 let latest_height =
221 ledger.vm.block_store().max_height().ok_or_else(|| anyhow!("Failed to load blocks from the ledger"))?;
222 let block = ledger
224 .get_block(latest_height)
225 .map_err(|_| anyhow!("Failed to load block {latest_height} from the ledger"))?;
226
227 ledger.current_block = Arc::new(RwLock::new(block));
229 ledger.current_committee = Arc::new(RwLock::new(Some(ledger.latest_committee()?)));
231 ledger.current_epoch_hash = Arc::new(RwLock::new(Some(ledger.get_epoch_hash(latest_height)?)));
233
234 finish!(timer, "Initialize ledger");
235 Ok(ledger)
236 }
237
238 pub const fn vm(&self) -> &VM<N, C> {
240 &self.vm
241 }
242
243 pub const fn puzzle(&self) -> &Puzzle<N> {
245 self.vm.puzzle()
246 }
247
248 pub fn latest_committee(&self) -> Result<Committee<N>> {
251 match self.current_committee.read().as_ref() {
252 Some(committee) => Ok(committee.clone()),
253 None => self.vm.finalize_store().committee_store().current_committee(),
254 }
255 }
256
257 pub fn latest_state_root(&self) -> N::StateRoot {
259 self.vm.block_store().current_state_root()
260 }
261
262 pub fn latest_epoch_number(&self) -> u32 {
264 self.current_block.read().height() / N::NUM_BLOCKS_PER_EPOCH
265 }
266
267 pub fn latest_epoch_hash(&self) -> Result<N::BlockHash> {
269 match self.current_epoch_hash.read().as_ref() {
270 Some(epoch_hash) => Ok(*epoch_hash),
271 None => self.get_epoch_hash(self.latest_height()),
272 }
273 }
274
275 pub fn latest_block(&self) -> Block<N> {
277 self.current_block.read().clone()
278 }
279
280 pub fn latest_round(&self) -> u64 {
282 self.current_block.read().round()
283 }
284
285 pub fn latest_height(&self) -> u32 {
287 self.current_block.read().height()
288 }
289
290 pub fn latest_hash(&self) -> N::BlockHash {
292 self.current_block.read().hash()
293 }
294
295 pub fn latest_header(&self) -> Header<N> {
297 *self.current_block.read().header()
298 }
299
300 pub fn latest_cumulative_weight(&self) -> u128 {
302 self.current_block.read().cumulative_weight()
303 }
304
305 pub fn latest_cumulative_proof_target(&self) -> u128 {
307 self.current_block.read().cumulative_proof_target()
308 }
309
310 pub fn latest_solutions_root(&self) -> Field<N> {
312 self.current_block.read().header().solutions_root()
313 }
314
315 pub fn latest_coinbase_target(&self) -> u64 {
317 self.current_block.read().coinbase_target()
318 }
319
320 pub fn latest_proof_target(&self) -> u64 {
322 self.current_block.read().proof_target()
323 }
324
325 pub fn last_coinbase_target(&self) -> u64 {
327 self.current_block.read().last_coinbase_target()
328 }
329
330 pub fn last_coinbase_timestamp(&self) -> i64 {
332 self.current_block.read().last_coinbase_timestamp()
333 }
334
335 pub fn latest_timestamp(&self) -> i64 {
337 self.current_block.read().timestamp()
338 }
339
340 pub fn latest_transactions(&self) -> Transactions<N> {
342 self.current_block.read().transactions().clone()
343 }
344}
345
346impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
347 pub fn find_unspent_credits_records(&self, view_key: &ViewKey<N>) -> Result<RecordMap<N>> {
349 let microcredits = Identifier::from_str("microcredits")?;
350 Ok(self
351 .find_records(view_key, RecordsFilter::Unspent)?
352 .filter(|(_, record)| {
353 match record.data().get(µcredits) {
355 Some(Entry::Private(Plaintext::Literal(Literal::U64(amount), _))) => !amount.is_zero(),
356 _ => false,
357 }
358 })
359 .collect::<IndexMap<_, _>>())
360 }
361
362 pub fn create_deploy<R: Rng + CryptoRng>(
366 &self,
367 private_key: &PrivateKey<N>,
368 program: &Program<N>,
369 priority_fee_in_microcredits: u64,
370 query: Option<Query<N, C::BlockStorage>>,
371 rng: &mut R,
372 ) -> Result<Transaction<N>> {
373 let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
375 ensure!(!records.len().is_zero(), "The Aleo account has no records to spend.");
376 let mut records = records.values();
377
378 let fee_record = Some(records.next().unwrap().clone());
380
381 self.vm.deploy(private_key, program, fee_record, priority_fee_in_microcredits, query, rng)
383 }
384
385 pub fn create_transfer<R: Rng + CryptoRng>(
389 &self,
390 private_key: &PrivateKey<N>,
391 to: Address<N>,
392 amount_in_microcredits: u64,
393 priority_fee_in_microcredits: u64,
394 query: Option<Query<N, C::BlockStorage>>,
395 rng: &mut R,
396 ) -> Result<Transaction<N>> {
397 let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
399 ensure!(!records.len().is_zero(), "The Aleo account has no records to spend.");
400 let mut records = records.values();
401
402 let inputs = [
404 Value::Record(records.next().unwrap().clone()),
405 Value::from_str(&format!("{to}"))?,
406 Value::from_str(&format!("{amount_in_microcredits}u64"))?,
407 ];
408
409 let fee_record = Some(records.next().unwrap().clone());
411
412 self.vm.execute(
414 private_key,
415 ("credits.aleo", "transfer_private"),
416 inputs.iter(),
417 fee_record,
418 priority_fee_in_microcredits,
419 query,
420 rng,
421 )
422 }
423}
424
425#[cfg(test)]
426pub(crate) mod test_helpers {
427 use crate::Ledger;
428 use aleo_std::StorageMode;
429 use console::{
430 account::{Address, PrivateKey, ViewKey},
431 network::MainnetV0,
432 prelude::*,
433 };
434 use ledger_store::ConsensusStore;
435 use snarkvm_circuit::network::AleoV0;
436 use synthesizer::vm::VM;
437
438 pub(crate) type CurrentNetwork = MainnetV0;
439 pub(crate) type CurrentAleo = AleoV0;
440
441 #[cfg(not(feature = "rocks"))]
442 pub(crate) type CurrentLedger =
443 Ledger<CurrentNetwork, ledger_store::helpers::memory::ConsensusMemory<CurrentNetwork>>;
444 #[cfg(feature = "rocks")]
445 pub(crate) type CurrentLedger = Ledger<CurrentNetwork, ledger_store::helpers::rocksdb::ConsensusDB<CurrentNetwork>>;
446
447 #[cfg(not(feature = "rocks"))]
448 pub(crate) type CurrentConsensusStore =
449 ConsensusStore<CurrentNetwork, ledger_store::helpers::memory::ConsensusMemory<CurrentNetwork>>;
450 #[cfg(feature = "rocks")]
451 pub(crate) type CurrentConsensusStore =
452 ConsensusStore<CurrentNetwork, ledger_store::helpers::rocksdb::ConsensusDB<CurrentNetwork>>;
453
454 #[allow(dead_code)]
455 pub(crate) struct TestEnv {
456 pub ledger: CurrentLedger,
457 pub private_key: PrivateKey<CurrentNetwork>,
458 pub view_key: ViewKey<CurrentNetwork>,
459 pub address: Address<CurrentNetwork>,
460 }
461
462 pub(crate) fn sample_test_env(rng: &mut (impl Rng + CryptoRng)) -> TestEnv {
463 let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
465 let view_key = ViewKey::try_from(&private_key).unwrap();
466 let address = Address::try_from(&private_key).unwrap();
467 let ledger = sample_ledger(private_key, rng);
469 TestEnv { ledger, private_key, view_key, address }
471 }
472
473 pub(crate) fn sample_ledger(
474 private_key: PrivateKey<CurrentNetwork>,
475 rng: &mut (impl Rng + CryptoRng),
476 ) -> CurrentLedger {
477 let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
479 let genesis = VM::from(store).unwrap().genesis_beacon(&private_key, rng).unwrap();
481 let ledger = CurrentLedger::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
483 assert_eq!(genesis, ledger.get_block(0).unwrap());
485 ledger
487 }
488}