snarkvm_ledger_store/transaction/
execution.rsuse crate::{
FeeStorage,
FeeStore,
TransitionStore,
atomic_batch_scope,
cow_to_cloned,
cow_to_copied,
helpers::{Map, MapRead},
};
use console::network::prelude::*;
use ledger_block::{Execution, Transaction, Transition};
use synthesizer_snark::Proof;
use aleo_std_storage::StorageMode;
use anyhow::Result;
use core::marker::PhantomData;
use std::borrow::Cow;
pub trait ExecutionStorage<N: Network>: Clone + Send + Sync {
type IDMap: for<'a> Map<'a, N::TransactionID, (Vec<N::TransitionID>, bool)>;
type ReverseIDMap: for<'a> Map<'a, N::TransitionID, N::TransactionID>;
type InclusionMap: for<'a> Map<'a, N::TransactionID, (N::StateRoot, Option<Proof<N>>)>;
type FeeStorage: FeeStorage<N>;
fn open(fee_store: FeeStore<N, Self::FeeStorage>) -> Result<Self>;
fn id_map(&self) -> &Self::IDMap;
fn reverse_id_map(&self) -> &Self::ReverseIDMap;
fn inclusion_map(&self) -> &Self::InclusionMap;
fn fee_store(&self) -> &FeeStore<N, Self::FeeStorage>;
fn transition_store(&self) -> &TransitionStore<N, <Self::FeeStorage as FeeStorage<N>>::TransitionStorage> {
self.fee_store().transition_store()
}
fn storage_mode(&self) -> &StorageMode {
self.transition_store().storage_mode()
}
fn start_atomic(&self) {
self.id_map().start_atomic();
self.reverse_id_map().start_atomic();
self.inclusion_map().start_atomic();
self.fee_store().start_atomic();
}
fn is_atomic_in_progress(&self) -> bool {
self.id_map().is_atomic_in_progress()
|| self.reverse_id_map().is_atomic_in_progress()
|| self.inclusion_map().is_atomic_in_progress()
|| self.fee_store().is_atomic_in_progress()
}
fn atomic_checkpoint(&self) {
self.id_map().atomic_checkpoint();
self.reverse_id_map().atomic_checkpoint();
self.inclusion_map().atomic_checkpoint();
self.fee_store().atomic_checkpoint();
}
fn clear_latest_checkpoint(&self) {
self.id_map().clear_latest_checkpoint();
self.reverse_id_map().clear_latest_checkpoint();
self.inclusion_map().clear_latest_checkpoint();
self.fee_store().clear_latest_checkpoint();
}
fn atomic_rewind(&self) {
self.id_map().atomic_rewind();
self.reverse_id_map().atomic_rewind();
self.inclusion_map().atomic_rewind();
self.fee_store().atomic_rewind();
}
fn abort_atomic(&self) {
self.id_map().abort_atomic();
self.reverse_id_map().abort_atomic();
self.inclusion_map().abort_atomic();
self.fee_store().abort_atomic();
}
fn finish_atomic(&self) -> Result<()> {
self.id_map().finish_atomic()?;
self.reverse_id_map().finish_atomic()?;
self.inclusion_map().finish_atomic()?;
self.fee_store().finish_atomic()
}
fn insert(&self, transaction: &Transaction<N>) -> Result<()> {
let (transaction_id, execution, fee) = match transaction {
Transaction::Deploy(..) => bail!("Attempted to insert a deploy transaction into execution storage."),
Transaction::Execute(transaction_id, execution, fee) => (transaction_id, execution, fee),
Transaction::Fee(..) => bail!("Attempted to insert a fee transaction into execution storage."),
};
let transitions = execution.transitions();
let transition_ids = execution.transitions().map(Transition::id).copied().collect();
let global_state_root = execution.global_state_root();
let proof = execution.proof().cloned();
atomic_batch_scope!(self, {
self.id_map().insert(*transaction_id, (transition_ids, fee.is_some()))?;
for transition in transitions {
self.reverse_id_map().insert(*transition.id(), *transaction_id)?;
self.transition_store().insert(transition)?;
}
self.inclusion_map().insert(*transaction_id, (global_state_root, proof))?;
if let Some(fee) = fee {
self.fee_store().insert(*transaction_id, fee)?;
}
Ok(())
})
}
fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
let (transition_ids, has_fee) = match self.id_map().get_confirmed(transaction_id)? {
Some(ids) => cow_to_cloned!(ids),
None => bail!("Failed to get the transition IDs for the transaction '{transaction_id}'"),
};
atomic_batch_scope!(self, {
self.id_map().remove(transaction_id)?;
for transition_id in transition_ids {
self.reverse_id_map().remove(&transition_id)?;
self.transition_store().remove(&transition_id)?;
}
self.inclusion_map().remove(transaction_id)?;
if has_fee {
self.fee_store().remove(transaction_id)?;
}
Ok(())
})
}
fn find_transaction_id_from_transition_id(
&self,
transition_id: &N::TransitionID,
) -> Result<Option<N::TransactionID>> {
if let Some(transaction_id) = self.fee_store().find_transaction_id_from_transition_id(transition_id)? {
return Ok(Some(transaction_id));
}
match self.reverse_id_map().get_confirmed(transition_id)? {
Some(transaction_id) => Ok(Some(cow_to_copied!(transaction_id))),
None => Ok(None),
}
}
fn get_execution(&self, transaction_id: &N::TransactionID) -> Result<Option<Execution<N>>> {
let (transition_ids, _) = match self.id_map().get_confirmed(transaction_id)? {
Some(ids) => cow_to_cloned!(ids),
None => return Ok(None),
};
let (global_state_root, proof) = match self.inclusion_map().get_confirmed(transaction_id)? {
Some(inclusion) => cow_to_cloned!(inclusion),
None => bail!("Failed to get the proof for the transaction '{transaction_id}'"),
};
let mut transitions = Vec::new();
for transition_id in &transition_ids {
match self.transition_store().get_transition(transition_id)? {
Some(transition) => transitions.push(transition),
None => bail!("Failed to get transition '{transition_id}' for transaction '{transaction_id}'"),
};
}
Ok(Some(Execution::from(transitions.into_iter(), global_state_root, proof)?))
}
fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
let (transition_ids, has_fee) = match self.id_map().get_confirmed(transaction_id)? {
Some(ids) => cow_to_cloned!(ids),
None => return Ok(None),
};
let (global_state_root, proof) = match self.inclusion_map().get_confirmed(transaction_id)? {
Some(inclusion) => cow_to_cloned!(inclusion),
None => bail!("Failed to get the proof for the transaction '{transaction_id}'"),
};
let mut transitions = Vec::new();
for transition_id in &transition_ids {
match self.transition_store().get_transition(transition_id)? {
Some(transition) => transitions.push(transition),
None => bail!("Failed to get transition '{transition_id}' for transaction '{transaction_id}'"),
};
}
let execution = Execution::from(transitions.into_iter(), global_state_root, proof)?;
let transaction = match has_fee {
true => match self.fee_store().get_fee(transaction_id)? {
Some(fee) => Transaction::from_execution(execution, Some(fee))?,
None => bail!("Failed to get the fee for transaction '{transaction_id}'"),
},
false => Transaction::from_execution(execution, None)?,
};
match *transaction_id == transaction.id() {
true => Ok(Some(transaction)),
false => bail!("Mismatching transaction ID for transaction '{transaction_id}'"),
}
}
}
#[derive(Clone)]
pub struct ExecutionStore<N: Network, E: ExecutionStorage<N>> {
storage: E,
_phantom: PhantomData<N>,
}
impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
pub fn open(fee_store: FeeStore<N, E::FeeStorage>) -> Result<Self> {
let storage = E::open(fee_store)?;
Ok(Self { storage, _phantom: PhantomData })
}
pub fn from(storage: E) -> Self {
Self { storage, _phantom: PhantomData }
}
pub fn insert(&self, transaction: &Transaction<N>) -> Result<()> {
self.storage.insert(transaction)
}
pub fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
self.storage.remove(transaction_id)
}
pub fn start_atomic(&self) {
self.storage.start_atomic();
}
pub fn is_atomic_in_progress(&self) -> bool {
self.storage.is_atomic_in_progress()
}
pub fn atomic_checkpoint(&self) {
self.storage.atomic_checkpoint();
}
pub fn clear_latest_checkpoint(&self) {
self.storage.clear_latest_checkpoint();
}
pub fn atomic_rewind(&self) {
self.storage.atomic_rewind();
}
pub fn abort_atomic(&self) {
self.storage.abort_atomic();
}
pub fn finish_atomic(&self) -> Result<()> {
self.storage.finish_atomic()
}
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}
}
impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
self.storage.get_transaction(transaction_id)
}
pub fn get_execution(&self, transaction_id: &N::TransactionID) -> Result<Option<Execution<N>>> {
self.storage.get_execution(transaction_id)
}
}
impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
pub fn find_transaction_id_from_transition_id(
&self,
transition_id: &N::TransitionID,
) -> Result<Option<N::TransactionID>> {
self.storage.find_transaction_id_from_transition_id(transition_id)
}
}
impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
pub fn execution_transaction_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, N::TransactionID>> {
self.storage.id_map().keys_confirmed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{TransitionStore, helpers::memory::ExecutionMemory};
type CurrentNetwork = console::network::MainnetV0;
fn insert_get_remove(transaction: Transaction<CurrentNetwork>) -> Result<()> {
let transaction_id = transaction.id();
let transition_store = TransitionStore::open(None)?;
let fee_store = FeeStore::open(transition_store).unwrap();
let execution_store = ExecutionMemory::open(fee_store)?;
let candidate = execution_store.get_transaction(&transaction_id)?;
assert_eq!(None, candidate);
execution_store.insert(&transaction)?;
let candidate = execution_store.get_transaction(&transaction_id)?;
assert_eq!(Some(transaction), candidate);
execution_store.remove(&transaction_id)?;
let candidate = execution_store.get_transaction(&transaction_id)?;
assert_eq!(None, candidate);
Ok(())
}
fn find_transaction_id(transaction: Transaction<CurrentNetwork>) -> Result<()> {
let transaction_id = transaction.id();
if matches!(transaction, Transaction::Deploy(..)) {
bail!("Invalid transaction type");
}
let transition_store = TransitionStore::open(None)?;
let fee_store = FeeStore::open(transition_store).unwrap();
let execution_store = ExecutionMemory::open(fee_store)?;
let candidate = execution_store.get_transaction(&transaction_id)?;
assert_eq!(None, candidate);
for transition_id in transaction.transition_ids() {
let candidate = execution_store.find_transaction_id_from_transition_id(transition_id).unwrap();
assert_eq!(None, candidate);
execution_store.insert(&transaction)?;
let candidate = execution_store.find_transaction_id_from_transition_id(transition_id).unwrap();
assert_eq!(Some(transaction_id), candidate);
execution_store.remove(&transaction_id)?;
let candidate = execution_store.find_transaction_id_from_transition_id(transition_id).unwrap();
assert_eq!(None, candidate);
}
Ok(())
}
#[test]
fn test_insert_get_remove() {
let rng = &mut TestRng::default();
let transaction = ledger_test_helpers::sample_execution_transaction_with_fee(true, rng);
insert_get_remove(transaction).unwrap();
let transaction = ledger_test_helpers::sample_execution_transaction_with_fee(false, rng);
insert_get_remove(transaction).unwrap();
}
#[test]
fn test_find_transaction_id() {
let rng = &mut TestRng::default();
let transaction = ledger_test_helpers::sample_execution_transaction_with_fee(true, rng);
find_transaction_id(transaction).unwrap();
let transaction = ledger_test_helpers::sample_execution_transaction_with_fee(false, rng);
find_transaction_id(transaction).unwrap();
}
}