use crate::{
atomic_batch_scope,
cow_to_cloned,
cow_to_copied,
helpers::{Map, MapRead},
FeeStorage,
FeeStore,
};
use console::{
network::prelude::*,
program::{Identifier, ProgramID, ProgramOwner},
};
use ledger_block::{Deployment, Fee, Transaction};
use synthesizer_program::Program;
use synthesizer_snark::{Certificate, VerifyingKey};
use anyhow::Result;
use core::marker::PhantomData;
use std::borrow::Cow;
pub trait DeploymentStorage<N: Network>: Clone + Send + Sync {
type IDMap: for<'a> Map<'a, N::TransactionID, ProgramID<N>>;
type EditionMap: for<'a> Map<'a, ProgramID<N>, u16>;
type ReverseIDMap: for<'a> Map<'a, (ProgramID<N>, u16), N::TransactionID>;
type OwnerMap: for<'a> Map<'a, (ProgramID<N>, u16), ProgramOwner<N>>;
type ProgramMap: for<'a> Map<'a, (ProgramID<N>, u16), Program<N>>;
type VerifyingKeyMap: for<'a> Map<'a, (ProgramID<N>, Identifier<N>, u16), VerifyingKey<N>>;
type CertificateMap: for<'a> Map<'a, (ProgramID<N>, Identifier<N>, u16), Certificate<N>>;
type FeeStorage: FeeStorage<N>;
fn open(fee_store: FeeStore<N, Self::FeeStorage>) -> Result<Self>;
fn id_map(&self) -> &Self::IDMap;
fn edition_map(&self) -> &Self::EditionMap;
fn reverse_id_map(&self) -> &Self::ReverseIDMap;
fn owner_map(&self) -> &Self::OwnerMap;
fn program_map(&self) -> &Self::ProgramMap;
fn verifying_key_map(&self) -> &Self::VerifyingKeyMap;
fn certificate_map(&self) -> &Self::CertificateMap;
fn fee_store(&self) -> &FeeStore<N, Self::FeeStorage>;
fn dev(&self) -> Option<u16> {
self.fee_store().dev()
}
fn start_atomic(&self) {
self.id_map().start_atomic();
self.edition_map().start_atomic();
self.reverse_id_map().start_atomic();
self.owner_map().start_atomic();
self.program_map().start_atomic();
self.verifying_key_map().start_atomic();
self.certificate_map().start_atomic();
self.fee_store().start_atomic();
}
fn is_atomic_in_progress(&self) -> bool {
self.id_map().is_atomic_in_progress()
|| self.edition_map().is_atomic_in_progress()
|| self.reverse_id_map().is_atomic_in_progress()
|| self.owner_map().is_atomic_in_progress()
|| self.program_map().is_atomic_in_progress()
|| self.verifying_key_map().is_atomic_in_progress()
|| self.certificate_map().is_atomic_in_progress()
|| self.fee_store().is_atomic_in_progress()
}
fn atomic_checkpoint(&self) {
self.id_map().atomic_checkpoint();
self.edition_map().atomic_checkpoint();
self.reverse_id_map().atomic_checkpoint();
self.owner_map().atomic_checkpoint();
self.program_map().atomic_checkpoint();
self.verifying_key_map().atomic_checkpoint();
self.certificate_map().atomic_checkpoint();
self.fee_store().atomic_checkpoint();
}
fn clear_latest_checkpoint(&self) {
self.id_map().clear_latest_checkpoint();
self.edition_map().clear_latest_checkpoint();
self.reverse_id_map().clear_latest_checkpoint();
self.owner_map().clear_latest_checkpoint();
self.program_map().clear_latest_checkpoint();
self.verifying_key_map().clear_latest_checkpoint();
self.certificate_map().clear_latest_checkpoint();
self.fee_store().clear_latest_checkpoint();
}
fn atomic_rewind(&self) {
self.id_map().atomic_rewind();
self.edition_map().atomic_rewind();
self.reverse_id_map().atomic_rewind();
self.owner_map().atomic_rewind();
self.program_map().atomic_rewind();
self.verifying_key_map().atomic_rewind();
self.certificate_map().atomic_rewind();
self.fee_store().atomic_rewind();
}
fn abort_atomic(&self) {
self.id_map().abort_atomic();
self.edition_map().abort_atomic();
self.reverse_id_map().abort_atomic();
self.owner_map().abort_atomic();
self.program_map().abort_atomic();
self.verifying_key_map().abort_atomic();
self.certificate_map().abort_atomic();
self.fee_store().abort_atomic();
}
fn finish_atomic(&self) -> Result<()> {
self.id_map().finish_atomic()?;
self.edition_map().finish_atomic()?;
self.reverse_id_map().finish_atomic()?;
self.owner_map().finish_atomic()?;
self.program_map().finish_atomic()?;
self.verifying_key_map().finish_atomic()?;
self.certificate_map().finish_atomic()?;
self.fee_store().finish_atomic()
}
fn insert(&self, transaction: &Transaction<N>) -> Result<()> {
let (transaction_id, owner, deployment, fee) = match transaction {
Transaction::Deploy(transaction_id, owner, deployment, fee) => (transaction_id, owner, deployment, fee),
Transaction::Execute(..) => bail!("Attempted to insert an execute transaction into deployment storage."),
Transaction::Fee(..) => bail!("Attempted to insert fee transaction into deployment storage."),
};
if let Err(error) = deployment.check_is_ordered() {
bail!("Failed to insert malformed deployment transaction: {error}")
}
let edition = deployment.edition();
let program = deployment.program();
let program_id = *program.id();
atomic_batch_scope!(self, {
self.id_map().insert(*transaction_id, program_id)?;
self.edition_map().insert(program_id, edition)?;
self.reverse_id_map().insert((program_id, edition), *transaction_id)?;
self.owner_map().insert((program_id, edition), *owner)?;
self.program_map().insert((program_id, edition), program.clone())?;
for (function_name, (verifying_key, certificate)) in deployment.verifying_keys() {
self.verifying_key_map().insert((program_id, *function_name, edition), verifying_key.clone())?;
self.certificate_map().insert((program_id, *function_name, edition), certificate.clone())?;
}
self.fee_store().insert(*transaction_id, fee)?;
Ok(())
})
}
fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
let program_id = match self.get_program_id(transaction_id)? {
Some(edition) => edition,
None => bail!("Failed to get the program ID for transaction '{transaction_id}'"),
};
let edition = match self.get_edition(&program_id)? {
Some(edition) => edition,
None => bail!("Failed to locate the edition for program '{program_id}'"),
};
let program = match self.program_map().get_confirmed(&(program_id, edition))? {
Some(program) => cow_to_cloned!(program),
None => bail!("Failed to locate program '{program_id}' for transaction '{transaction_id}'"),
};
atomic_batch_scope!(self, {
self.id_map().remove(transaction_id)?;
self.edition_map().remove(&program_id)?;
self.reverse_id_map().remove(&(program_id, edition))?;
self.owner_map().remove(&(program_id, edition))?;
self.program_map().remove(&(program_id, edition))?;
for function_name in program.functions().keys() {
self.verifying_key_map().remove(&(program_id, *function_name, edition))?;
self.certificate_map().remove(&(program_id, *function_name, edition))?;
}
self.fee_store().remove(transaction_id)?;
Ok(())
})
}
fn find_transaction_id_from_program_id(&self, program_id: &ProgramID<N>) -> Result<Option<N::TransactionID>> {
if program_id == &ProgramID::from_str("credits.aleo")? {
return Ok(None);
}
let edition = match self.get_edition(program_id)? {
Some(edition) => edition,
None => return Ok(None),
};
match self.reverse_id_map().get_confirmed(&(*program_id, edition))? {
Some(transaction_id) => Ok(Some(cow_to_copied!(transaction_id))),
None => bail!("Failed to find the transaction ID for program '{program_id}' (edition {edition})"),
}
}
fn find_transaction_id_from_transition_id(
&self,
transition_id: &N::TransitionID,
) -> Result<Option<N::TransactionID>> {
self.fee_store().find_transaction_id_from_transition_id(transition_id)
}
fn get_program_id(&self, transaction_id: &N::TransactionID) -> Result<Option<ProgramID<N>>> {
match self.id_map().get_confirmed(transaction_id)? {
Some(program_id) => Ok(Some(cow_to_copied!(program_id))),
None => Ok(None),
}
}
fn get_edition(&self, program_id: &ProgramID<N>) -> Result<Option<u16>> {
if program_id == &ProgramID::from_str("credits.aleo")? {
return Ok(None);
}
match self.edition_map().get_confirmed(program_id)? {
Some(edition) => Ok(Some(cow_to_copied!(edition))),
None => Ok(None),
}
}
fn get_program(&self, program_id: &ProgramID<N>) -> Result<Option<Program<N>>> {
if program_id == &ProgramID::from_str("credits.aleo")? {
return Ok(Some(Program::credits()?));
}
let edition = match self.get_edition(program_id)? {
Some(edition) => edition,
None => return Ok(None),
};
match self.program_map().get_confirmed(&(*program_id, edition))? {
Some(program) => Ok(Some(cow_to_cloned!(program))),
None => bail!("Failed to get program '{program_id}' (edition {edition})"),
}
}
fn get_verifying_key(
&self,
program_id: &ProgramID<N>,
function_name: &Identifier<N>,
) -> Result<Option<VerifyingKey<N>>> {
if program_id == &ProgramID::from_str("credits.aleo")? {
let verifying_key = N::get_credits_verifying_key(function_name.to_string())?;
return Ok(Some(VerifyingKey::new(verifying_key.clone())));
}
let edition = match self.get_edition(program_id)? {
Some(edition) => edition,
None => return Ok(None),
};
match self.verifying_key_map().get_confirmed(&(*program_id, *function_name, edition))? {
Some(verifying_key) => Ok(Some(cow_to_cloned!(verifying_key))),
None => bail!("Failed to get the verifying key for '{program_id}/{function_name}' (edition {edition})"),
}
}
fn get_certificate(
&self,
program_id: &ProgramID<N>,
function_name: &Identifier<N>,
) -> Result<Option<Certificate<N>>> {
if program_id == &ProgramID::from_str("credits.aleo")? {
return Ok(None);
}
let edition = match self.get_edition(program_id)? {
Some(edition) => edition,
None => return Ok(None),
};
match self.certificate_map().get_confirmed(&(*program_id, *function_name, edition))? {
Some(certificate) => Ok(Some(cow_to_cloned!(certificate))),
None => bail!("Failed to get the certificate for '{program_id}/{function_name}' (edition {edition})"),
}
}
fn get_deployment(&self, transaction_id: &N::TransactionID) -> Result<Option<Deployment<N>>> {
let program_id = match self.get_program_id(transaction_id)? {
Some(edition) => edition,
None => return Ok(None),
};
let edition = match self.get_edition(&program_id)? {
Some(edition) => edition,
None => bail!("Failed to get the edition for program '{program_id}'"),
};
let program = match self.program_map().get_confirmed(&(program_id, edition))? {
Some(program) => cow_to_cloned!(program),
None => bail!("Failed to get the deployed program '{program_id}' (edition {edition})"),
};
let mut verifying_keys = Vec::with_capacity(program.functions().len());
for function_name in program.functions().keys() {
let verifying_key = match self.verifying_key_map().get_confirmed(&(program_id, *function_name, edition))? {
Some(verifying_key) => cow_to_cloned!(verifying_key),
None => bail!("Failed to get the verifying key for '{program_id}/{function_name}' (edition {edition})"),
};
let certificate = match self.certificate_map().get_confirmed(&(program_id, *function_name, edition))? {
Some(certificate) => cow_to_cloned!(certificate),
None => bail!("Failed to get the certificate for '{program_id}/{function_name}' (edition {edition})"),
};
verifying_keys.push((*function_name, (verifying_key, certificate)));
}
Ok(Some(Deployment::new(edition, program, verifying_keys)?))
}
fn get_fee(&self, transaction_id: &N::TransactionID) -> Result<Option<Fee<N>>> {
self.fee_store().get_fee(transaction_id)
}
fn get_owner(&self, program_id: &ProgramID<N>) -> Result<Option<ProgramOwner<N>>> {
if program_id == &ProgramID::from_str("credits.aleo")? {
return Ok(None);
}
let edition = match self.get_edition(program_id)? {
Some(edition) => edition,
None => return Ok(None),
};
match self.owner_map().get_confirmed(&(*program_id, edition))? {
Some(owner) => Ok(Some(cow_to_copied!(owner))),
None => bail!("Failed to find the Owner for program '{program_id}' (edition {edition})"),
}
}
fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
let deployment = match self.get_deployment(transaction_id)? {
Some(deployment) => deployment,
None => return Ok(None),
};
let fee = match self.get_fee(transaction_id)? {
Some(fee) => fee,
None => bail!("Failed to get the fee for transaction '{transaction_id}'"),
};
let owner = match self.get_owner(deployment.program_id())? {
Some(owner) => owner,
None => bail!("Failed to get the owner for transaction '{transaction_id}'"),
};
let deployment_transaction = Transaction::from_deployment(owner, deployment, fee)?;
match *transaction_id == deployment_transaction.id() {
true => Ok(Some(deployment_transaction)),
false => bail!("The deployment transaction ID does not match '{transaction_id}'"),
}
}
}
#[derive(Clone)]
pub struct DeploymentStore<N: Network, D: DeploymentStorage<N>> {
storage: D,
_phantom: PhantomData<N>,
}
impl<N: Network, D: DeploymentStorage<N>> DeploymentStore<N, D> {
pub fn open(fee_store: FeeStore<N, D::FeeStorage>) -> Result<Self> {
let storage = D::open(fee_store)?;
Ok(Self { storage, _phantom: PhantomData })
}
pub fn from(storage: D) -> 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 dev(&self) -> Option<u16> {
self.storage.dev()
}
}
impl<N: Network, D: DeploymentStorage<N>> DeploymentStore<N, D> {
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>>> {
self.storage.get_deployment(transaction_id)
}
pub fn get_edition(&self, program_id: &ProgramID<N>) -> Result<Option<u16>> {
self.storage.get_edition(program_id)
}
pub fn get_program_id(&self, transaction_id: &N::TransactionID) -> Result<Option<ProgramID<N>>> {
self.storage.get_program_id(transaction_id)
}
pub fn get_program(&self, program_id: &ProgramID<N>) -> Result<Option<Program<N>>> {
self.storage.get_program(program_id)
}
pub fn get_verifying_key(
&self,
program_id: &ProgramID<N>,
function_name: &Identifier<N>,
) -> Result<Option<VerifyingKey<N>>> {
self.storage.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.get_certificate(program_id, function_name)
}
pub fn get_fee(&self, transaction_id: &N::TransactionID) -> Result<Option<Fee<N>>> {
self.storage.get_fee(transaction_id)
}
}
impl<N: Network, D: DeploymentStorage<N>> DeploymentStore<N, D> {
pub fn find_transaction_id_from_program_id(&self, program_id: &ProgramID<N>) -> Result<Option<N::TransactionID>> {
self.storage.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, D: DeploymentStorage<N>> DeploymentStore<N, D> {
pub fn contains_program_id(&self, program_id: &ProgramID<N>) -> Result<bool> {
self.storage.edition_map().contains_key_confirmed(program_id)
}
}
impl<N: Network, D: DeploymentStorage<N>> DeploymentStore<N, D> {
pub fn deployment_transaction_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, N::TransactionID>> {
self.storage.id_map().keys_confirmed()
}
pub fn program_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, ProgramID<N>>> {
self.storage.id_map().values_confirmed().map(|id| match id {
Cow::Borrowed(id) => Cow::Borrowed(id),
Cow::Owned(id) => Cow::Owned(id),
})
}
pub fn programs(&self) -> impl '_ + Iterator<Item = Cow<'_, Program<N>>> {
self.storage.program_map().values_confirmed().map(|program| match program {
Cow::Borrowed(program) => Cow::Borrowed(program),
Cow::Owned(program) => Cow::Owned(program),
})
}
pub fn verifying_keys(
&self,
) -> impl '_ + Iterator<Item = (Cow<'_, (ProgramID<N>, Identifier<N>, u16)>, Cow<'_, VerifyingKey<N>>)> {
self.storage.verifying_key_map().iter_confirmed()
}
pub fn certificates(
&self,
) -> impl '_ + Iterator<Item = (Cow<'_, (ProgramID<N>, Identifier<N>, u16)>, Cow<'_, Certificate<N>>)> {
self.storage.certificate_map().iter_confirmed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{helpers::memory::DeploymentMemory, TransitionStore};
#[test]
fn test_insert_get_remove() {
let rng = &mut TestRng::default();
let transaction_0 = ledger_test_helpers::sample_deployment_transaction(true, rng);
let transaction_1 = ledger_test_helpers::sample_deployment_transaction(false, rng);
let transactions = vec![transaction_0, transaction_1];
for transaction in transactions {
let transaction_id = transaction.id();
let transition_store = TransitionStore::open(None).unwrap();
let fee_store = FeeStore::open(transition_store).unwrap();
let deployment_store = DeploymentMemory::open(fee_store).unwrap();
let candidate = deployment_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
deployment_store.insert(&transaction).unwrap();
let candidate = deployment_store.get_transaction(&transaction_id).unwrap();
assert_eq!(Some(transaction), candidate);
deployment_store.remove(&transaction_id).unwrap();
let candidate = deployment_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
}
}
#[test]
fn test_find_transaction_id() {
let rng = &mut TestRng::default();
let transaction_0 = ledger_test_helpers::sample_deployment_transaction(true, rng);
let transaction_1 = ledger_test_helpers::sample_deployment_transaction(false, rng);
let transactions = vec![transaction_0, transaction_1];
for transaction in transactions {
let transaction_id = transaction.id();
let program_id = match transaction {
Transaction::Deploy(_, _, ref deployment, _) => *deployment.program_id(),
_ => panic!("Incorrect transaction type"),
};
let transition_store = TransitionStore::open(None).unwrap();
let fee_store = FeeStore::open(transition_store).unwrap();
let deployment_store = DeploymentMemory::open(fee_store).unwrap();
let candidate = deployment_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
let candidate = deployment_store.find_transaction_id_from_program_id(&program_id).unwrap();
assert_eq!(None, candidate);
deployment_store.insert(&transaction).unwrap();
let candidate = deployment_store.find_transaction_id_from_program_id(&program_id).unwrap();
assert_eq!(Some(transaction_id), candidate);
deployment_store.remove(&transaction_id).unwrap();
let candidate = deployment_store.find_transaction_id_from_program_id(&program_id).unwrap();
assert_eq!(None, candidate);
}
}
}