#![forbid(unsafe_code)]
#[macro_use]
extern crate tracing;
mod helpers;
pub use helpers::*;
mod advance;
mod check;
mod contains;
mod find;
mod get;
mod iterators;
#[cfg(test)]
mod tests;
use console::{
account::{Address, GraphKey, PrivateKey, Signature, ViewKey},
network::prelude::*,
program::{
Ciphertext,
Entry,
Identifier,
Literal,
Plaintext,
ProgramID,
Record,
StatePath,
Value,
RATIFICATIONS_DEPTH,
},
types::{Field, Group},
};
use synthesizer::{
block::{Block, ConfirmedTransaction, Header, Metadata, Ratify, Transaction, Transactions},
coinbase::{CoinbasePuzzle, CoinbaseSolution, EpochChallenge, ProverSolution, PuzzleCommitment},
process::{FinalizeGlobalState, Query},
program::Program,
store::{ConsensusStorage, ConsensusStore},
vm::VM,
};
use aleo_std::prelude::{finish, lap, timer};
use anyhow::Result;
use core::ops::Range;
use indexmap::{IndexMap, IndexSet};
use parking_lot::RwLock;
use rand::{prelude::IteratorRandom, rngs::OsRng};
use std::{borrow::Cow, sync::Arc};
use time::OffsetDateTime;
#[cfg(not(feature = "serial"))]
use rayon::prelude::*;
pub type RecordMap<N> = IndexMap<Field<N>, Record<N, Plaintext<N>>>;
#[derive(Copy, Clone, Debug)]
pub enum RecordsFilter<N: Network> {
All,
Spent,
Unspent,
SlowSpent(PrivateKey<N>),
SlowUnspent(PrivateKey<N>),
}
#[derive(Clone)]
pub struct Ledger<N: Network, C: ConsensusStorage<N>> {
vm: VM<N, C>,
genesis: Block<N>,
coinbase_puzzle: CoinbasePuzzle<N>,
current_block: Arc<RwLock<Block<N>>>,
current_epoch_challenge: Arc<RwLock<Option<EpochChallenge<N>>>>,
current_committee: Arc<RwLock<IndexSet<Address<N>>>>,
}
impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
pub fn load(genesis: Block<N>, dev: Option<u16>) -> Result<Self> {
let timer = timer!("Ledger::load");
let genesis_hash = genesis.hash();
let ledger = Self::load_unchecked(genesis, dev)?;
if !ledger.contains_block_hash(&genesis_hash)? {
bail!("Incorrect genesis block (run 'snarkos clean' and try again)")
}
let latest_height =
*ledger.vm.block_store().heights().max().ok_or_else(|| anyhow!("Failed to load blocks from the ledger"))?;
const NUM_BLOCKS: usize = 1000;
let block_heights: Vec<u32> = (0..=latest_height)
.choose_multiple(&mut OsRng::default(), core::cmp::min(NUM_BLOCKS, latest_height as usize));
cfg_into_iter!(block_heights).try_for_each(|height| {
ledger.get_block(height)?;
Ok::<_, Error>(())
})?;
lap!(timer, "Check existence of {NUM_BLOCKS} random blocks");
finish!(timer);
Ok(ledger)
}
pub fn load_unchecked(genesis: Block<N>, dev: Option<u16>) -> Result<Self> {
let timer = timer!("Ledger::load_unchecked");
let store = match ConsensusStore::<N, C>::open(dev) {
Ok(store) => store,
_ => bail!("Failed to load ledger (run 'snarkos clean' and try again)"),
};
lap!(timer, "Load consensus store");
let vm = VM::from(store)?;
lap!(timer, "Initialize a new VM");
let mut ledger = Self {
vm,
genesis: genesis.clone(),
coinbase_puzzle: CoinbasePuzzle::<N>::load()?,
current_block: Arc::new(RwLock::new(genesis.clone())),
current_epoch_challenge: Default::default(),
current_committee: Default::default(),
};
ledger.current_committee.write().insert(genesis.signature().to_address());
if ledger.vm.block_store().heights().max().is_none() {
ledger.advance_to_next_block(&genesis)?;
}
lap!(timer, "Initialize genesis");
let latest_height =
*ledger.vm.block_store().heights().max().ok_or_else(|| anyhow!("Failed to load blocks from the ledger"))?;
let block = ledger
.get_block(latest_height)
.map_err(|_| anyhow!("Failed to load block {latest_height} from the ledger"))?;
ledger.current_block = Arc::new(RwLock::new(block));
ledger.current_epoch_challenge = Arc::new(RwLock::new(Some(ledger.get_epoch_challenge(latest_height)?)));
lap!(timer, "Initialize ledger");
finish!(timer);
Ok(ledger)
}
pub const fn vm(&self) -> &VM<N, C> {
&self.vm
}
pub const fn coinbase_puzzle(&self) -> &CoinbasePuzzle<N> {
&self.coinbase_puzzle
}
pub fn latest_committee(&self) -> IndexSet<Address<N>> {
self.current_committee.read().clone()
}
pub fn latest_state_root(&self) -> N::StateRoot {
self.vm.block_store().current_state_root()
}
pub fn latest_block(&self) -> Block<N> {
self.current_block.read().clone()
}
pub fn latest_round(&self) -> u64 {
self.current_block.read().round()
}
pub fn latest_height(&self) -> u32 {
self.current_block.read().height()
}
pub fn latest_hash(&self) -> N::BlockHash {
self.current_block.read().hash()
}
pub fn latest_header(&self) -> Header<N> {
*self.current_block.read().header()
}
pub fn latest_total_supply_in_microcredits(&self) -> u64 {
self.current_block.read().header().total_supply_in_microcredits()
}
pub fn latest_cumulative_weight(&self) -> u128 {
self.current_block.read().cumulative_weight()
}
pub fn latest_coinbase_accumulator_point(&self) -> Field<N> {
self.current_block.read().header().coinbase_accumulator_point()
}
pub fn latest_coinbase_target(&self) -> u64 {
self.current_block.read().coinbase_target()
}
pub fn latest_proof_target(&self) -> u64 {
self.current_block.read().proof_target()
}
pub fn last_coinbase_target(&self) -> u64 {
self.current_block.read().last_coinbase_target()
}
pub fn last_coinbase_timestamp(&self) -> i64 {
self.current_block.read().last_coinbase_timestamp()
}
pub fn latest_timestamp(&self) -> i64 {
self.current_block.read().timestamp()
}
pub fn latest_transactions(&self) -> Transactions<N> {
self.current_block.read().transactions().clone()
}
pub fn latest_epoch_number(&self) -> u32 {
self.current_block.read().height() / N::NUM_BLOCKS_PER_EPOCH
}
pub fn latest_epoch_challenge(&self) -> Result<EpochChallenge<N>> {
match self.current_epoch_challenge.read().as_ref() {
Some(challenge) => Ok(challenge.clone()),
None => self.get_epoch_challenge(self.latest_height()),
}
}
}
impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
pub fn find_unspent_credits_records(&self, view_key: &ViewKey<N>) -> Result<RecordMap<N>> {
let microcredits = Identifier::from_str("microcredits")?;
Ok(self
.find_records(view_key, RecordsFilter::Unspent)?
.filter(|(_, record)| {
match record.data().get(µcredits) {
Some(Entry::Private(Plaintext::Literal(Literal::U64(amount), _))) => !amount.is_zero(),
_ => false,
}
})
.collect::<IndexMap<_, _>>())
}
pub fn create_deploy(
&self,
private_key: &PrivateKey<N>,
program: &Program<N>,
priority_fee_in_microcredits: u64,
query: Option<Query<N, C::BlockStorage>>,
) -> Result<Transaction<N>> {
let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
ensure!(!records.len().is_zero(), "The Aleo account has no records to spend.");
let mut records = records.values();
let rng = &mut ::rand::thread_rng();
let fee_record = records.next().unwrap().clone();
let fee = (fee_record, priority_fee_in_microcredits);
self.vm.deploy(private_key, program, fee, query, rng)
}
pub fn create_transfer(
&self,
private_key: &PrivateKey<N>,
to: Address<N>,
amount_in_microcredits: u64,
priority_fee_in_microcredits: u64,
query: Option<Query<N, C::BlockStorage>>,
) -> Result<Transaction<N>> {
let records = self.find_unspent_credits_records(&ViewKey::try_from(private_key)?)?;
ensure!(!records.len().is_zero(), "The Aleo account has no records to spend.");
let mut records = records.values();
let rng = &mut rand::thread_rng();
let inputs = [
Value::Record(records.next().unwrap().clone()),
Value::from_str(&format!("{to}"))?,
Value::from_str(&format!("{amount_in_microcredits}u64"))?,
];
let fee_record = records.next().unwrap().clone();
let fee = Some((fee_record, priority_fee_in_microcredits));
self.vm.execute(private_key, ("credits.aleo", "transfer_private"), inputs.iter(), fee, query, rng)
}
}