mod deployment;
pub use deployment::*;
mod execution;
pub use execution::*;
mod fee;
pub use fee::*;
use crate::{
TransitionStorage,
TransitionStore,
atomic_batch_scope,
cow_to_copied,
helpers::{Map, MapRead},
};
use console::{
network::prelude::*,
program::{Identifier, ProgramID},
};
use ledger_block::{Deployment, Execution, Transaction};
use synthesizer_program::Program;
use synthesizer_snark::{Certificate, VerifyingKey};
use aleo_std_storage::StorageMode;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TransactionType {
Deploy,
Execute,
Fee,
}
pub trait TransactionStorage<N: Network>: Clone + Send + Sync {
type IDMap: for<'a> Map<'a, N::TransactionID, TransactionType>;
type DeploymentStorage: DeploymentStorage<N, FeeStorage = Self::FeeStorage>;
type ExecutionStorage: ExecutionStorage<N, FeeStorage = Self::FeeStorage>;
type FeeStorage: FeeStorage<N, TransitionStorage = Self::TransitionStorage>;
type TransitionStorage: TransitionStorage<N>;
fn open(transition_store: TransitionStore<N, Self::TransitionStorage>) -> Result<Self>;
fn id_map(&self) -> &Self::IDMap;
fn deployment_store(&self) -> &DeploymentStore<N, Self::DeploymentStorage>;
fn execution_store(&self) -> &ExecutionStore<N, Self::ExecutionStorage>;
fn fee_store(&self) -> &FeeStore<N, Self::FeeStorage>;
fn transition_store(&self) -> &TransitionStore<N, Self::TransitionStorage> {
debug_assert!(self.deployment_store().storage_mode() == self.execution_store().storage_mode());
debug_assert!(self.execution_store().storage_mode() == self.fee_store().storage_mode());
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.deployment_store().start_atomic();
self.execution_store().start_atomic();
self.fee_store().start_atomic();
}
fn is_atomic_in_progress(&self) -> bool {
self.id_map().is_atomic_in_progress()
|| self.deployment_store().is_atomic_in_progress()
|| self.execution_store().is_atomic_in_progress()
|| self.fee_store().is_atomic_in_progress()
}
fn atomic_checkpoint(&self) {
self.id_map().atomic_checkpoint();
self.deployment_store().atomic_checkpoint();
self.execution_store().atomic_checkpoint();
self.fee_store().atomic_checkpoint();
}
fn clear_latest_checkpoint(&self) {
self.id_map().clear_latest_checkpoint();
self.deployment_store().clear_latest_checkpoint();
self.execution_store().clear_latest_checkpoint();
self.fee_store().clear_latest_checkpoint();
}
fn atomic_rewind(&self) {
self.id_map().atomic_rewind();
self.deployment_store().atomic_rewind();
self.execution_store().atomic_rewind();
self.fee_store().atomic_rewind();
}
fn abort_atomic(&self) {
self.id_map().abort_atomic();
self.deployment_store().abort_atomic();
self.execution_store().abort_atomic();
self.fee_store().abort_atomic();
}
fn finish_atomic(&self) -> Result<()> {
self.id_map().finish_atomic()?;
self.deployment_store().finish_atomic()?;
self.execution_store().finish_atomic()?;
self.fee_store().finish_atomic()
}
fn insert(&self, transaction: &Transaction<N>) -> Result<()> {
atomic_batch_scope!(self, {
match transaction {
Transaction::Deploy(..) => {
self.id_map().insert(transaction.id(), TransactionType::Deploy)?;
self.deployment_store().insert(transaction)?;
}
Transaction::Execute(..) => {
self.id_map().insert(transaction.id(), TransactionType::Execute)?;
self.execution_store().insert(transaction)?;
}
Transaction::Fee(_, fee) => {
self.id_map().insert(transaction.id(), TransactionType::Fee)?;
self.fee_store().insert(transaction.id(), fee)?;
}
}
Ok(())
})
}
fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
let transaction_type = match self.id_map().get_confirmed(transaction_id)? {
Some(transaction_type) => cow_to_copied!(transaction_type),
None => bail!("Failed to get the type for transaction '{transaction_id}'"),
};
atomic_batch_scope!(self, {
self.id_map().remove(transaction_id)?;
match transaction_type {
TransactionType::Deploy => self.deployment_store().remove(transaction_id)?,
TransactionType::Execute => self.execution_store().remove(transaction_id)?,
TransactionType::Fee => self.fee_store().remove(transaction_id)?,
}
Ok(())
})
}
fn find_transaction_id_from_transition_id(
&self,
transition_id: &N::TransitionID,
) -> Result<Option<N::TransactionID>> {
self.execution_store().find_transaction_id_from_transition_id(transition_id)
}
fn find_transaction_id_from_program_id(&self, program_id: &ProgramID<N>) -> Result<Option<N::TransactionID>> {
self.deployment_store().find_transaction_id_from_program_id(program_id)
}
fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
let transaction_type = match self.id_map().get_confirmed(transaction_id)? {
Some(transaction_type) => cow_to_copied!(transaction_type),
None => return Ok(None),
};
match transaction_type {
TransactionType::Deploy => self.deployment_store().get_transaction(transaction_id),
TransactionType::Execute => self.execution_store().get_transaction(transaction_id),
TransactionType::Fee => match self.fee_store().get_fee(transaction_id)? {
Some(fee) => Ok(Some(Transaction::Fee(*transaction_id, fee))),
None => bail!("Failed to get fee for transaction '{transaction_id}'"),
},
}
}
}
#[derive(Clone)]
pub struct TransactionStore<N: Network, T: TransactionStorage<N>> {
transaction_ids: T::IDMap,
storage: T,
}
impl<N: Network, T: TransactionStorage<N>> TransactionStore<N, T> {
pub fn open(transition_store: TransitionStore<N, T::TransitionStorage>) -> Result<Self> {
let storage = T::open(transition_store)?;
Ok(Self { transaction_ids: storage.id_map().clone(), storage })
}
pub fn from(storage: T) -> Self {
Self { transaction_ids: storage.id_map().clone(), storage }
}
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 deployment_store(&self) -> &DeploymentStore<N, T::DeploymentStorage> {
self.storage.deployment_store()
}
pub fn transition_store(&self) -> &TransitionStore<N, T::TransitionStorage> {
self.storage.transition_store()
}
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, T: TransactionStorage<N>> TransactionStore<N, T> {
pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
self.storage.get_transaction(transaction_id)
}
pub fn get_deployment(&self, transaction_id: &N::TransactionID) -> Result<Option<Deployment<N>>> {
let transaction_type = match self.transaction_ids.get_confirmed(transaction_id)? {
Some(transaction_type) => cow_to_copied!(transaction_type),
None => bail!("Failed to get the type for transaction '{transaction_id}'"),
};
match transaction_type {
TransactionType::Deploy => self.storage.deployment_store().get_deployment(transaction_id),
TransactionType::Execute => bail!("Tried to get a deployment for execution transaction '{transaction_id}'"),
TransactionType::Fee => bail!("Tried to get a deployment for fee transaction '{transaction_id}'"),
}
}
pub fn get_execution(&self, transaction_id: &N::TransactionID) -> Result<Option<Execution<N>>> {
let transaction_type = match self.transaction_ids.get_confirmed(transaction_id)? {
Some(transaction_type) => cow_to_copied!(transaction_type),
None => bail!("Failed to get the type for transaction '{transaction_id}'"),
};
match transaction_type {
TransactionType::Deploy => bail!("Tried to get an execution for deployment transaction '{transaction_id}'"),
TransactionType::Execute => self.storage.execution_store().get_execution(transaction_id),
TransactionType::Fee => bail!("Tried to get an execution for fee transaction '{transaction_id}'"),
}
}
pub fn get_edition(&self, transaction_id: &N::TransactionID) -> Result<Option<u16>> {
let transaction_type = match self.transaction_ids.get_confirmed(transaction_id)? {
Some(transaction_type) => cow_to_copied!(transaction_type),
None => bail!("Failed to get the type for transaction '{transaction_id}'"),
};
match transaction_type {
TransactionType::Deploy => {
let program_id = self.storage.deployment_store().get_program_id(transaction_id)?;
match program_id {
Some(program_id) => self.storage.deployment_store().get_edition(&program_id),
None => bail!("Failed to get the program ID for deployment transaction '{transaction_id}'"),
}
}
TransactionType::Execute => Ok(None),
TransactionType::Fee => Ok(None),
}
}
pub fn get_program_id(&self, transaction_id: &N::TransactionID) -> Result<Option<ProgramID<N>>> {
self.storage.deployment_store().get_program_id(transaction_id)
}
pub fn get_program(&self, program_id: &ProgramID<N>) -> Result<Option<Program<N>>> {
self.storage.deployment_store().get_program(program_id)
}
pub fn get_verifying_key(
&self,
program_id: &ProgramID<N>,
function_name: &Identifier<N>,
) -> Result<Option<VerifyingKey<N>>> {
self.storage.deployment_store().get_verifying_key(program_id, function_name)
}
pub fn get_certificate(
&self,
program_id: &ProgramID<N>,
function_name: &Identifier<N>,
) -> Result<Option<Certificate<N>>> {
self.storage.deployment_store().get_certificate(program_id, function_name)
}
}
impl<N: Network, T: TransactionStorage<N>> TransactionStore<N, T> {
pub fn find_transaction_id_from_program_id(&self, program_id: &ProgramID<N>) -> Result<Option<N::TransactionID>> {
self.storage.deployment_store().find_transaction_id_from_program_id(program_id)
}
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, T: TransactionStorage<N>> TransactionStore<N, T> {
pub fn contains_transaction_id(&self, transaction_id: &N::TransactionID) -> Result<bool> {
self.transaction_ids.contains_key_confirmed(transaction_id)
}
pub fn contains_program_id(&self, program_id: &ProgramID<N>) -> Result<bool> {
self.storage.deployment_store().contains_program_id(program_id)
}
}
impl<N: Network, T: TransactionStorage<N>> TransactionStore<N, T> {
pub fn transaction_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, N::TransactionID>> {
self.transaction_ids.keys_confirmed()
}
pub fn deployment_transaction_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, N::TransactionID>> {
self.storage.deployment_store().deployment_transaction_ids()
}
pub fn execution_transaction_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, N::TransactionID>> {
self.storage.execution_store().execution_transaction_ids()
}
pub fn program_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, ProgramID<N>>> {
self.storage.deployment_store().program_ids()
}
pub fn programs(&self) -> impl '_ + Iterator<Item = Cow<'_, Program<N>>> {
self.storage.deployment_store().programs()
}
pub fn verifying_keys(
&self,
) -> impl '_ + Iterator<Item = (Cow<'_, (ProgramID<N>, Identifier<N>, u16)>, Cow<'_, VerifyingKey<N>>)> {
self.storage.deployment_store().verifying_keys()
}
pub fn certificates(
&self,
) -> impl '_ + Iterator<Item = (Cow<'_, (ProgramID<N>, Identifier<N>, u16)>, Cow<'_, Certificate<N>>)> {
self.storage.deployment_store().certificates()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::memory::{TransactionMemory, TransitionMemory};
#[test]
fn test_insert_get_remove() {
let rng = &mut TestRng::default();
for transaction in [
ledger_test_helpers::sample_deployment_transaction(true, rng),
ledger_test_helpers::sample_deployment_transaction(false, rng),
ledger_test_helpers::sample_execution_transaction_with_fee(true, rng),
ledger_test_helpers::sample_execution_transaction_with_fee(false, rng),
ledger_test_helpers::sample_fee_private_transaction(rng),
ledger_test_helpers::sample_fee_public_transaction(rng),
] {
let transaction_id = transaction.id();
let transition_store = TransitionStore::<_, TransitionMemory<_>>::open(None).unwrap();
let transaction_store = TransactionStore::<_, TransactionMemory<_>>::open(transition_store).unwrap();
let candidate = transaction_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
transaction_store.insert(&transaction).unwrap();
let candidate = transaction_store.get_transaction(&transaction_id).unwrap();
assert_eq!(Some(transaction), candidate);
transaction_store.remove(&transaction_id).unwrap();
let candidate = transaction_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
}
}
#[test]
fn test_find_transaction_id() {
let rng = &mut TestRng::default();
for transaction in [
ledger_test_helpers::sample_deployment_transaction(true, rng),
ledger_test_helpers::sample_deployment_transaction(false, rng),
ledger_test_helpers::sample_execution_transaction_with_fee(true, rng),
ledger_test_helpers::sample_execution_transaction_with_fee(false, rng),
ledger_test_helpers::sample_fee_private_transaction(rng),
ledger_test_helpers::sample_fee_public_transaction(rng),
] {
let transaction_id = transaction.id();
let transition_ids = transaction.transition_ids();
let transition_store = TransitionStore::<_, TransitionMemory<_>>::open(None).unwrap();
let transaction_store = TransactionStore::<_, TransactionMemory<_>>::open(transition_store).unwrap();
let candidate = transaction_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
for transition_id in transition_ids {
let candidate = transaction_store.find_transaction_id_from_transition_id(transition_id).unwrap();
assert_eq!(None, candidate);
transaction_store.insert(&transaction).unwrap();
let candidate = transaction_store.find_transaction_id_from_transition_id(transition_id).unwrap();
assert_eq!(Some(transaction_id), candidate);
transaction_store.remove(&transaction_id).unwrap();
let candidate = transaction_store.find_transaction_id_from_transition_id(transition_id).unwrap();
assert_eq!(None, candidate);
}
}
}
}