mod helpers;
mod authorize;
mod deploy;
mod execute;
mod execute_fee;
mod finalize;
mod verify;
pub use finalize::FinalizeMode;
use crate::{
atomic_finalize,
block::{Block, ConfirmedTransaction, Deployment, Execution, Fee, Header, Transaction, Transactions, Transition},
cast_ref,
process,
process::{Authorization, Inclusion, InclusionAssignment, Process, Query},
program::Program,
store::{BlockStore, ConsensusStorage, ConsensusStore, FinalizeStore, TransactionStore, TransitionStore},
CallMetrics,
};
use console::{
account::{Address, PrivateKey},
network::prelude::*,
program::{Entry, Identifier, Literal, Plaintext, ProgramID, ProgramOwner, Record, Response, Value},
types::Field,
};
use aleo_std::prelude::{finish, lap, timer};
use parking_lot::RwLock;
use std::sync::Arc;
#[derive(Clone)]
pub struct VM<N: Network, C: ConsensusStorage<N>> {
process: Arc<RwLock<Process<N>>>,
store: ConsensusStore<N, C>,
}
impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
#[inline]
pub fn from(store: ConsensusStore<N, C>) -> Result<Self> {
let mut process = Process::load()?;
let credits = Program::<N>::credits()?;
for mapping in credits.mappings().values() {
if !store.finalize_store().contains_mapping_confirmed(credits.id(), mapping.name())? {
store.finalize_store().initialize_mapping(credits.id(), mapping.name())?;
}
}
let transaction_store = store.transaction_store();
for transaction_id in transaction_store.deployment_transaction_ids() {
match transaction_store.get_deployment(&transaction_id)? {
Some(deployment) => process.load_deployment(&deployment)?,
None => bail!("Deployment transaction '{transaction_id}' is not found in storage."),
};
}
Ok(Self { process: Arc::new(RwLock::new(process)), store })
}
#[inline]
pub fn contains_program(&self, program_id: &ProgramID<N>) -> bool {
self.process.read().contains_program(program_id)
}
#[inline]
pub fn process(&self) -> Arc<RwLock<Process<N>>> {
self.process.clone()
}
}
impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
#[inline]
pub fn finalize_store(&self) -> &FinalizeStore<N, C::FinalizeStorage> {
self.store.finalize_store()
}
#[inline]
pub fn block_store(&self) -> &BlockStore<N, C::BlockStorage> {
self.store.block_store()
}
#[inline]
pub fn transaction_store(&self) -> &TransactionStore<N, C::TransactionStorage> {
self.store.transaction_store()
}
#[inline]
pub fn transition_store(&self) -> &TransitionStore<N, C::TransitionStorage> {
self.store.transition_store()
}
}
impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
pub fn genesis<R: Rng + CryptoRng>(&self, private_key: &PrivateKey<N>, rng: &mut R) -> Result<Block<N>> {
let caller = Address::try_from(private_key)?;
let locator = ("credits.aleo", "mint");
let amount = N::STARTING_SUPPLY.saturating_div(Block::<N>::NUM_GENESIS_TRANSACTIONS as u64);
let inputs = [caller.to_string(), format!("{amount}_u64")];
let transactions = (0u32..Block::<N>::NUM_GENESIS_TRANSACTIONS as u32)
.map(|index| {
let transaction = self.execute(private_key, locator, inputs.iter(), None, None, rng)?;
ConfirmedTransaction::accepted_execute(index, transaction, vec![])
})
.collect::<Result<Transactions<_>>>()?;
let header = Header::genesis(&transactions)?;
let previous_hash = N::BlockHash::default();
let coinbase_solution = None; let block = Block::new(private_key, previous_hash, header, transactions, coinbase_solution, rng)?;
match block.is_genesis() {
true => Ok(block),
false => bail!("Failed to initialize a genesis block"),
}
}
#[inline]
pub fn add_next_block(&self, block: &Block<N>) -> Result<()> {
self.block_store().insert(block)?;
match self.finalize(block.transactions()) {
Ok(_) => {
Ok(())
}
Err(error) => {
self.block_store().remove_last_n(1)?;
Err(error)
}
}
}
}
#[cfg(test)]
pub(crate) mod test_helpers {
use super::*;
use crate::{
program::Program,
store::helpers::memory::ConsensusMemory,
Block,
Fee,
Header,
Inclusion,
Metadata,
Transition,
};
use console::{
account::{Address, ViewKey},
network::Testnet3,
program::Value,
};
use indexmap::IndexMap;
use once_cell::sync::OnceCell;
use std::borrow::Borrow;
pub(crate) type CurrentNetwork = Testnet3;
pub(crate) fn sample_vm() -> VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>> {
VM::from(ConsensusStore::open(None).unwrap()).unwrap()
}
pub(crate) fn sample_genesis_private_key(rng: &mut TestRng) -> PrivateKey<CurrentNetwork> {
static INSTANCE: OnceCell<PrivateKey<CurrentNetwork>> = OnceCell::new();
*INSTANCE.get_or_init(|| {
PrivateKey::<CurrentNetwork>::new(rng).unwrap()
})
}
pub(crate) fn sample_genesis_block(rng: &mut TestRng) -> Block<CurrentNetwork> {
static INSTANCE: OnceCell<Block<CurrentNetwork>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
let vm = crate::vm::test_helpers::sample_vm();
let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
vm.genesis(&caller_private_key, rng).unwrap()
})
.clone()
}
pub(crate) fn sample_vm_with_genesis_block(
rng: &mut TestRng,
) -> VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>> {
let vm = crate::vm::test_helpers::sample_vm();
let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
vm.add_next_block(&genesis).unwrap();
vm
}
pub(crate) fn sample_program() -> Program<CurrentNetwork> {
static INSTANCE: OnceCell<Program<CurrentNetwork>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
Program::<CurrentNetwork>::from_str(
r"
program testing.aleo;
struct message:
amount as u128;
mapping account:
key owner as address.public;
value amount as u64.public;
record token:
owner as address.private;
amount as u64.private;
function mint:
input r0 as address.private;
input r1 as u64.private;
cast r0 r1 into r2 as token.record;
output r2 as token.record;
function compute:
input r0 as message.private;
input r1 as message.public;
input r2 as message.private;
input r3 as token.record;
add r0.amount r1.amount into r4;
cast r3.owner r3.amount into r5 as token.record;
output r4 as u128.public;
output r5 as token.record;",
)
.unwrap()
})
.clone()
}
pub(crate) fn sample_deployment_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
static INSTANCE: OnceCell<Transaction<CurrentNetwork>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
let program = sample_program();
let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
let records =
genesis.transitions().cloned().flat_map(Transition::into_records).collect::<IndexMap<_, _>>();
trace!("Unspent Records:\n{:#?}", records);
let credits = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
let fee = (credits, 10);
let vm = sample_vm();
vm.add_next_block(&genesis).unwrap();
let transaction = vm.deploy(&caller_private_key, &program, fee, None, rng).unwrap();
assert!(vm.verify_transaction(&transaction));
transaction
})
.clone()
}
pub(crate) fn sample_execution_transaction_without_fee(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
static INSTANCE: OnceCell<Transaction<CurrentNetwork>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
let address = Address::try_from(&caller_private_key).unwrap();
let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
let records =
genesis.transitions().cloned().flat_map(Transition::into_records).collect::<IndexMap<_, _>>();
trace!("Unspent Records:\n{:#?}", records);
let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
let vm = sample_vm();
vm.add_next_block(&genesis).unwrap();
let inputs = [
Value::<CurrentNetwork>::Record(record),
Value::<CurrentNetwork>::from_str(&address.to_string()).unwrap(),
Value::<CurrentNetwork>::from_str("1u64").unwrap(),
]
.into_iter();
let authorization = vm.authorize(&caller_private_key, "credits.aleo", "transfer", inputs, rng).unwrap();
assert_eq!(authorization.len(), 1);
let transaction = vm.execute_authorization(authorization, None, None, rng).unwrap();
assert!(!vm.verify_transaction(&transaction));
transaction
})
.clone()
}
pub(crate) fn sample_execution_transaction_with_fee(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
static INSTANCE: OnceCell<Transaction<CurrentNetwork>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
let address = Address::try_from(&caller_private_key).unwrap();
let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
let records =
genesis.transitions().cloned().flat_map(Transition::into_records).collect::<IndexMap<_, _>>();
trace!("Unspent Records:\n{:#?}", records);
let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
let vm = sample_vm();
vm.add_next_block(&genesis).unwrap();
let inputs = [
Value::<CurrentNetwork>::from_str(&address.to_string()).unwrap(),
Value::<CurrentNetwork>::from_str("1u64").unwrap(),
]
.into_iter();
let authorization = vm.authorize(&caller_private_key, "credits.aleo", "mint", inputs, rng).unwrap();
assert_eq!(authorization.len(), 1);
let fee = vm.execute_fee_raw(&caller_private_key, record, 100, None, rng).unwrap().1;
let transaction = vm.execute_authorization(authorization, Some(fee), None, rng).unwrap();
assert!(vm.verify_transaction(&transaction));
transaction
})
.clone()
}
pub(crate) fn sample_fee(rng: &mut TestRng) -> Fee<CurrentNetwork> {
static INSTANCE: OnceCell<Fee<CurrentNetwork>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
let records =
genesis.transitions().cloned().flat_map(Transition::into_records).collect::<IndexMap<_, _>>();
trace!("Unspent Records:\n{:#?}", records);
let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
let vm = sample_vm();
vm.add_next_block(&genesis).unwrap();
let (_response, fee, _metrics) =
vm.execute_fee_raw(&caller_private_key, record, 1u64, None, rng).unwrap();
assert!(vm.verify_fee(&fee));
assert!(Inclusion::verify_fee(&fee).is_ok());
fee
})
.clone()
}
pub(crate) fn sample_fee_transaction(rng: &mut TestRng) -> Transaction<CurrentNetwork> {
static INSTANCE: OnceCell<Transaction<CurrentNetwork>> = OnceCell::new();
INSTANCE
.get_or_init(|| {
let fee = crate::vm::test_helpers::sample_fee(rng);
Transaction::from_fee(fee).unwrap()
})
.clone()
}
pub fn sample_next_block<R: Rng + CryptoRng>(
vm: &VM<Testnet3, ConsensusMemory<Testnet3>>,
private_key: &PrivateKey<Testnet3>,
transactions: &[Transaction<Testnet3>],
rng: &mut R,
) -> Result<Block<Testnet3>> {
let block_hash =
vm.block_store().get_block_hash(*vm.block_store().heights().max().unwrap().borrow()).unwrap().unwrap();
let previous_block = vm.block_store().get_block(&block_hash).unwrap().unwrap();
let transactions = vm.speculate(transactions.iter())?;
let metadata = Metadata::new(
Testnet3::ID,
previous_block.round() + 1,
previous_block.height() + 1,
Testnet3::STARTING_SUPPLY,
0,
Testnet3::GENESIS_COINBASE_TARGET,
Testnet3::GENESIS_PROOF_TARGET,
previous_block.last_coinbase_target(),
previous_block.last_coinbase_timestamp(),
Testnet3::GENESIS_TIMESTAMP + 1,
)?;
let header = Header::from(
*vm.block_store().current_state_root(),
transactions.to_root().unwrap(),
Field::zero(),
Field::zero(),
metadata,
)?;
Block::new(private_key, previous_block.hash(), header, transactions, None, rng)
}
#[test]
fn test_multiple_deployments_and_multiple_executions() {
let rng = &mut TestRng::default();
let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
let caller_view_key = ViewKey::try_from(&caller_private_key).unwrap();
let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::<IndexMap<_, _>>();
trace!("Unspent Records:\n{:#?}", records);
let record = records.values().next().unwrap().decrypt(&caller_view_key).unwrap();
let vm = sample_vm();
vm.add_next_block(&genesis).unwrap();
let transaction = vm
.execute(
&caller_private_key,
("credits.aleo", "split"),
[Value::Record(record), Value::from_str("1000000000u64").unwrap()].iter(), None,
None,
rng,
)
.unwrap();
let records = transaction.records().collect_vec();
let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap();
let second_record = records[1].1.clone().decrypt(&caller_view_key).unwrap();
let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap();
vm.add_next_block(&block).unwrap();
let mut transactions = Vec::new();
let transaction = vm
.execute(
&caller_private_key,
("credits.aleo", "split"),
[Value::Record(first_record), Value::from_str("100000000u64").unwrap()].iter(), None,
None,
rng,
)
.unwrap();
let records = transaction.records().collect_vec();
let first_record = records[0].1.clone().decrypt(&caller_view_key).unwrap();
let third_record = records[1].1.clone().decrypt(&caller_view_key).unwrap();
transactions.push(transaction);
let transaction = vm
.execute(
&caller_private_key,
("credits.aleo", "split"),
[Value::Record(second_record), Value::from_str("100000000u64").unwrap()].iter(), None,
None,
rng,
)
.unwrap();
let records = transaction.records().collect_vec();
let second_record = records[0].1.clone().decrypt(&caller_view_key).unwrap();
let fourth_record = records[1].1.clone().decrypt(&caller_view_key).unwrap();
transactions.push(transaction);
let fee_block = sample_next_block(&vm, &caller_private_key, &transactions, rng).unwrap();
vm.add_next_block(&fee_block).unwrap();
let first_program = r"
program test_1.aleo;
mapping map_0:
key left as field.public;
value right as field.public;
function init:
finalize;
finalize init:
set 0field into map_0[0field];
function getter:
finalize;
finalize getter:
get map_0[0field] into r0;
";
let second_program = r"
program test_2.aleo;
mapping map_0:
key left as field.public;
value right as field.public;
function init:
finalize;
finalize init:
set 0field into map_0[0field];
function getter:
finalize;
finalize getter:
get map_0[0field] into r0;
";
let first_deployment = vm
.deploy(&caller_private_key, &Program::from_str(first_program).unwrap(), (first_record, 1), None, rng)
.unwrap();
let second_deployment = vm
.deploy(&caller_private_key, &Program::from_str(second_program).unwrap(), (second_record, 1), None, rng)
.unwrap();
let deployment_block =
sample_next_block(&vm, &caller_private_key, &[first_deployment, second_deployment], rng).unwrap();
vm.add_next_block(&deployment_block).unwrap();
let first_execution = vm
.execute(
&caller_private_key,
("test_1.aleo", "init"),
Vec::<Value<Testnet3>>::new().iter(),
Some((third_record, 1)),
None,
rng,
)
.unwrap();
let second_execution = vm
.execute(
&caller_private_key,
("test_2.aleo", "init"),
Vec::<Value<Testnet3>>::new().iter(),
Some((fourth_record, 1)),
None,
rng,
)
.unwrap();
let execution_block =
sample_next_block(&vm, &caller_private_key, &[first_execution, second_execution], rng).unwrap();
vm.add_next_block(&execution_block).unwrap();
}
}