1#![forbid(unsafe_code)]
16#![warn(clippy::cast_possible_truncation)]
17
18#[macro_use]
19extern crate tracing;
20
21pub use ledger_authority as authority;
22pub use ledger_block as block;
23pub use ledger_coinbase as coinbase;
24pub use ledger_committee as committee;
25pub use ledger_narwhal_debug as narwhal;
26pub use ledger_query as query;
27pub use ledger_store as store;
28
29pub use crate::block::*;
30
31#[cfg(feature = "test-helpers")]
32pub use ledger_test_helpers;
33
34mod helpers;
35pub use helpers::*;
36
37mod advance;
38mod check_next_block;
39mod check_transaction_basic;
40mod contains;
41mod find;
42mod get;
43mod iterators;
44
45#[cfg(test)]
46mod tests;
47
48use console::{
49 account::{Address, GraphKey, PrivateKey, ViewKey},
50 network::prelude::*,
51 program::{Ciphertext, Entry, Identifier, Literal, Plaintext, ProgramID, Record, StatePath, Value},
52 types::{Field, Group},
53};
54use ledger_authority::Authority;
55use ledger_block::{Block, ConfirmedTransaction, Header, Metadata, Ratify, Transaction, Transactions};
56use ledger_coinbase::{CoinbasePuzzle, CoinbaseSolution, EpochChallenge, ProverSolution, PuzzleCommitment};
57use ledger_committee::Committee;
58use ledger_narwhal_debug::{BatchCertificate, Subdag, Transmission, TransmissionID};
59use ledger_query::Query;
60use ledger_store::{ConsensusStorage, ConsensusStore};
61use synthesizer_debug::{
62 program::{FinalizeGlobalState, Program},
63 vm::VM,
64};
65
66use aleo_std::prelude::{finish, lap, timer};
67use anyhow::Result;
68use core::ops::Range;
69use indexmap::IndexMap;
70use parking_lot::RwLock;
71use rand::{prelude::IteratorRandom, rngs::OsRng};
72use std::{borrow::Cow, sync::Arc};
73use time::OffsetDateTime;
74
75#[cfg(not(feature = "serial"))]
76use rayon::prelude::*;
77
78pub type RecordMap<N> = IndexMap<Field<N>, Record<N, Plaintext<N>>>;
79
80#[derive(Copy, Clone, Debug)]
81pub enum RecordsFilter<N: Network> {
82 All,
84 Spent,
86 Unspent,
88 SlowSpent(PrivateKey<N>),
90 SlowUnspent(PrivateKey<N>),
92}
93
94#[derive(Clone)]
95pub struct Ledger<N: Network, C: ConsensusStorage<N>> {
96 vm: VM<N, C>,
98 genesis_block: Block<N>,
100 coinbase_puzzle: CoinbasePuzzle<N>,
102 current_epoch_challenge: Arc<RwLock<Option<EpochChallenge<N>>>>,
104 current_committee: Arc<RwLock<Option<Committee<N>>>>,
106 current_block: Arc<RwLock<Block<N>>>,
108}
109
110impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
111 pub fn load(genesis_block: Block<N>, dev: Option<u16>) -> Result<Self> {
113 let timer = timer!("Ledger::load");
114
115 let genesis_hash = genesis_block.hash();
117 let ledger = Self::load_unchecked(genesis_block, dev)?;
119
120 if !ledger.contains_block_hash(&genesis_hash)? {
122 bail!("Incorrect genesis block (run 'snarkos clean' and try again)")
123 }
124
125 const NUM_BLOCKS: usize = 10;
127 let latest_height = ledger.current_block.read().height();
129 debug_assert_eq!(latest_height, *ledger.vm.block_store().heights().max().unwrap(), "Mismatch in latest height");
130 let block_heights: Vec<u32> =
132 (0..=latest_height).choose_multiple(&mut OsRng, (latest_height as usize).min(NUM_BLOCKS));
133 cfg_into_iter!(block_heights).try_for_each(|height| {
134 ledger.get_block(height)?;
135 Ok::<_, Error>(())
136 })?;
137 lap!(timer, "Check existence of {NUM_BLOCKS} random blocks");
138
139 finish!(timer);
140 Ok(ledger)
141 }
142
143 pub fn load_unchecked(genesis_block: Block<N>, dev: Option<u16>) -> Result<Self> {
145 let timer = timer!("Ledger::load_unchecked");
146
147 info!("Loading the ledger from storage...");
148 let store = match ConsensusStore::<N, C>::open(dev) {
150 Ok(store) => store,
151 Err(e) => bail!("Failed to load ledger (run 'snarkos clean' and try again)\n\n{e}\n"),
152 };
153 lap!(timer, "Load consensus store");
154
155 let vm = VM::from(store)?;
157 lap!(timer, "Initialize a new VM");
158
159 let current_committee = vm.finalize_store().committee_store().current_committee().ok();
161
162 let mut ledger = Self {
164 vm,
165 genesis_block: genesis_block.clone(),
166 coinbase_puzzle: CoinbasePuzzle::<N>::load()?,
167 current_epoch_challenge: Default::default(),
168 current_committee: Arc::new(RwLock::new(current_committee)),
169 current_block: Arc::new(RwLock::new(genesis_block.clone())),
170 };
171
172 if ledger.vm.block_store().heights().max().is_none() {
174 ledger.advance_to_next_block(&genesis_block)?;
176 }
177 lap!(timer, "Initialize genesis");
178
179 let latest_height =
181 *ledger.vm.block_store().heights().max().ok_or_else(|| anyhow!("Failed to load blocks from the ledger"))?;
182 let block = ledger
184 .get_block(latest_height)
185 .map_err(|_| anyhow!("Failed to load block {latest_height} from the ledger"))?;
186
187 ledger.current_block = Arc::new(RwLock::new(block));
189 ledger.current_committee = Arc::new(RwLock::new(Some(ledger.latest_committee()?)));
191 ledger.current_epoch_challenge = Arc::new(RwLock::new(Some(ledger.get_epoch_challenge(latest_height)?)));
193
194 finish!(timer, "Initialize ledger");
195 Ok(ledger)
196 }
197
198 pub const fn vm(&self) -> &VM<N, C> {
200 &self.vm
201 }
202
203 pub const fn coinbase_puzzle(&self) -> &CoinbasePuzzle<N> {
205 &self.coinbase_puzzle
206 }
207
208 pub fn latest_committee(&self) -> Result<Committee<N>> {
210 match self.current_committee.read().as_ref() {
211 Some(committee) => Ok(committee.clone()),
212 None => self.vm.finalize_store().committee_store().current_committee(),
213 }
214 }
215
216 pub fn latest_state_root(&self) -> N::StateRoot {
218 self.vm.block_store().current_state_root()
219 }
220
221 pub fn latest_epoch_number(&self) -> u32 {
223 self.current_block.read().height() / N::NUM_BLOCKS_PER_EPOCH
224 }
225
226 pub fn latest_epoch_challenge(&self) -> Result<EpochChallenge<N>> {
228 match self.current_epoch_challenge.read().as_ref() {
229 Some(challenge) => Ok(challenge.clone()),
230 None => self.get_epoch_challenge(self.latest_height()),
231 }
232 }
233
234 pub fn latest_block(&self) -> Block<N> {
236 self.current_block.read().clone()
237 }
238
239 pub fn latest_round(&self) -> u64 {
241 self.current_block.read().round()
242 }
243
244 pub fn latest_height(&self) -> u32 {
246 self.current_block.read().height()
247 }
248
249 pub fn latest_hash(&self) -> N::BlockHash {
251 self.current_block.read().hash()
252 }
253
254 pub fn latest_header(&self) -> Header<N> {
256 *self.current_block.read().header()
257 }
258
259 pub fn latest_cumulative_weight(&self) -> u128 {
261 self.current_block.read().cumulative_weight()
262 }
263
264 pub fn latest_cumulative_proof_target(&self) -> u128 {
266 self.current_block.read().cumulative_proof_target()
267 }
268
269 pub fn latest_solutions_root(&self) -> Field<N> {
271 self.current_block.read().header().solutions_root()
272 }
273
274 pub fn latest_coinbase_target(&self) -> u64 {
276 self.current_block.read().coinbase_target()
277 }
278
279 pub fn latest_proof_target(&self) -> u64 {
281 self.current_block.read().proof_target()
282 }
283
284 pub fn last_coinbase_target(&self) -> u64 {
286 self.current_block.read().last_coinbase_target()
287 }
288
289 pub fn last_coinbase_timestamp(&self) -> i64 {
291 self.current_block.read().last_coinbase_timestamp()
292 }
293
294 pub fn latest_timestamp(&self) -> i64 {
296 self.current_block.read().timestamp()
297 }
298
299 pub fn latest_transactions(&self) -> Transactions<N> {
301 self.current_block.read().transactions().clone()
302 }
303}
304
305impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
306 pub fn find_unspent_credits_records(&self, view_key: &ViewKey<N>) -> Result<RecordMap<N>> {
308 let microcredits = Identifier::from_str("microcredits")?;
309 Ok(self
310 .find_records(view_key, RecordsFilter::Unspent)?
311 .filter(|(_, record)| {
312 match record.data().get(µcredits) {
314 Some(Entry::Private(Plaintext::Literal(Literal::U64(amount), _))) => !amount.is_zero(),
315 _ => false,
316 }
317 })
318 .collect::<IndexMap<_, _>>())
319 }
320
321 pub fn create_deploy<R: Rng + CryptoRng>(
325 &self,
326 private_key: &PrivateKey<N>,
327 program: &Program<N>,
328 priority_fee_in_microcredits: u64,
329 query: Option<Query<N, C::BlockStorage>>,
330 rng: &mut R,
331 ) -> Result<Transaction<N>> {
332 let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
334 ensure!(!records.len().is_zero(), "The Aleo account has no records to spend.");
335 let mut records = records.values();
336
337 let fee_record = Some(records.next().unwrap().clone());
339
340 self.vm.deploy(private_key, program, fee_record, priority_fee_in_microcredits, query, rng)
342 }
343
344 pub fn create_transfer<R: Rng + CryptoRng>(
348 &self,
349 private_key: &PrivateKey<N>,
350 to: Address<N>,
351 amount_in_microcredits: u64,
352 priority_fee_in_microcredits: u64,
353 query: Option<Query<N, C::BlockStorage>>,
354 rng: &mut R,
355 ) -> Result<Transaction<N>> {
356 let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
358 ensure!(!records.len().is_zero(), "The Aleo account has no records to spend.");
359 let mut records = records.values();
360
361 let inputs = [
363 Value::Record(records.next().unwrap().clone()),
364 Value::from_str(&format!("{to}"))?,
365 Value::from_str(&format!("{amount_in_microcredits}u64"))?,
366 ];
367
368 let fee_record = Some(records.next().unwrap().clone());
370
371 self.vm.execute(
373 private_key,
374 ("credits.aleo", "transfer_private"),
375 inputs.iter(),
376 fee_record,
377 priority_fee_in_microcredits,
378 query,
379 rng,
380 )
381 }
382}
383
384#[cfg(test)]
385pub(crate) mod test_helpers {
386 use crate::Ledger;
387 use console::{
388 account::{Address, PrivateKey, ViewKey},
389 network::Testnet3,
390 prelude::*,
391 };
392 use ledger_block::Block;
393 use ledger_store::ConsensusStore;
394 use synthesizer::vm::VM;
395
396 pub(crate) type CurrentNetwork = Testnet3;
397
398 #[cfg(not(feature = "rocks"))]
399 pub(crate) type CurrentLedger =
400 Ledger<CurrentNetwork, ledger_store::helpers::memory::ConsensusMemory<CurrentNetwork>>;
401 #[cfg(feature = "rocks")]
402 pub(crate) type CurrentLedger = Ledger<CurrentNetwork, ledger_store::helpers::rocksdb::ConsensusDB<CurrentNetwork>>;
403
404 #[cfg(not(feature = "rocks"))]
405 pub(crate) type CurrentConsensusStore =
406 ConsensusStore<CurrentNetwork, ledger_store::helpers::memory::ConsensusMemory<CurrentNetwork>>;
407 #[cfg(feature = "rocks")]
408 pub(crate) type CurrentConsensusStore =
409 ConsensusStore<CurrentNetwork, ledger_store::helpers::rocksdb::ConsensusDB<CurrentNetwork>>;
410
411 #[allow(dead_code)]
412 pub(crate) struct TestEnv {
413 pub ledger: CurrentLedger,
414 pub private_key: PrivateKey<CurrentNetwork>,
415 pub view_key: ViewKey<CurrentNetwork>,
416 pub address: Address<CurrentNetwork>,
417 }
418
419 pub(crate) fn sample_test_env(rng: &mut (impl Rng + CryptoRng)) -> TestEnv {
420 let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
422 let view_key = ViewKey::try_from(&private_key).unwrap();
423 let address = Address::try_from(&private_key).unwrap();
424 let ledger = sample_ledger(private_key, rng);
426 TestEnv { ledger, private_key, view_key, address }
428 }
429
430 pub(crate) fn sample_genesis_block() -> Block<CurrentNetwork> {
431 Block::<CurrentNetwork>::from_bytes_le(CurrentNetwork::genesis_bytes()).unwrap()
432 }
433
434 pub(crate) fn sample_ledger(
435 private_key: PrivateKey<CurrentNetwork>,
436 rng: &mut (impl Rng + CryptoRng),
437 ) -> CurrentLedger {
438 let store = CurrentConsensusStore::open(None).unwrap();
440 let genesis = VM::from(store).unwrap().genesis_beacon(&private_key, rng).unwrap();
442 let ledger = CurrentLedger::load(genesis.clone(), None).unwrap();
444 assert_eq!(genesis, ledger.get_block(0).unwrap());
446 ledger
448 }
449}