use crate::{
atomic_write_batch,
cow_to_cloned,
cow_to_copied,
ledger::{
map::{memory_map::MemoryMap, Map, MapRead},
store::{TransitionMemory, TransitionStorage, TransitionStore},
AdditionalFee,
Transaction,
Transition,
},
process::Execution,
};
use console::network::prelude::*;
use anyhow::Result;
use core::marker::PhantomData;
use std::borrow::Cow;
pub trait ExecutionStorage<N: Network>: Clone + Sync {
type IDMap: for<'a> Map<'a, N::TransactionID, (Vec<N::TransitionID>, Option<N::TransitionID>)>;
type ReverseIDMap: for<'a> Map<'a, N::TransitionID, N::TransactionID>;
type EditionMap: for<'a> Map<'a, N::TransactionID, u16>;
type TransitionStorage: TransitionStorage<N>;
fn open(transition_store: TransitionStore<N, Self::TransitionStorage>) -> Result<Self>;
fn id_map(&self) -> &Self::IDMap;
fn reverse_id_map(&self) -> &Self::ReverseIDMap;
fn edition_map(&self) -> &Self::EditionMap;
fn transition_store(&self) -> &TransitionStore<N, Self::TransitionStorage>;
fn start_atomic(&self) {
self.id_map().start_atomic();
self.reverse_id_map().start_atomic();
self.edition_map().start_atomic();
self.transition_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.edition_map().is_atomic_in_progress()
|| self.transition_store().is_atomic_in_progress()
}
fn abort_atomic(&self) {
self.id_map().abort_atomic();
self.reverse_id_map().abort_atomic();
self.edition_map().abort_atomic();
self.transition_store().abort_atomic();
}
fn finish_atomic(&self) -> Result<()> {
self.id_map().finish_atomic()?;
self.reverse_id_map().finish_atomic()?;
self.edition_map().finish_atomic()?;
self.transition_store().finish_atomic()
}
fn insert(&self, transaction: &Transaction<N>) -> Result<()> {
let (transaction_id, execution, optional_additional_fee) = match transaction {
Transaction::Deploy(..) => {
bail!("Attempted to insert non-execution transaction into execution storage.")
}
Transaction::Execute(transaction_id, execution, optional_additional_fee) => {
(transaction_id, execution, optional_additional_fee)
}
};
let edition = execution.edition();
let transitions: Vec<_> = execution.clone().into_transitions().collect();
let transition_ids = transitions.iter().map(Transition::id).copied().collect();
let optional_additional_fee_id = optional_additional_fee.as_ref().map(|additional_fee| *additional_fee.id());
atomic_write_batch!(self, {
self.id_map().insert(*transaction_id, (transition_ids, optional_additional_fee_id))?;
self.edition_map().insert(*transaction_id, edition)?;
for transition in transitions {
self.reverse_id_map().insert(*transition.id(), *transaction_id)?;
self.transition_store().insert(transition)?;
}
if let Some(additional_fee) = optional_additional_fee {
self.reverse_id_map().insert(*additional_fee.id(), *transaction_id)?;
self.transition_store().insert(additional_fee.clone())?;
}
Ok(())
});
Ok(())
}
fn remove(&self, transaction_id: &N::TransactionID) -> Result<()> {
let (transition_ids, optional_additional_fee_id) = match self.id_map().get(transaction_id)? {
Some(ids) => cow_to_cloned!(ids),
None => bail!("Failed to get the transition IDs for the transaction '{transaction_id}'"),
};
atomic_write_batch!(self, {
self.id_map().remove(transaction_id)?;
self.edition_map().remove(transaction_id)?;
for transition_id in transition_ids {
self.reverse_id_map().remove(&transition_id)?;
self.transition_store().remove(&transition_id)?;
}
if let Some(additional_fee_id) = optional_additional_fee_id {
self.reverse_id_map().remove(&additional_fee_id)?;
self.transition_store().remove(&additional_fee_id)?;
}
Ok(())
});
Ok(())
}
fn find_transaction_id(&self, transition_id: &N::TransitionID) -> Result<Option<N::TransactionID>> {
match self.reverse_id_map().get(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 edition = match self.edition_map().get(transaction_id)? {
Some(edition) => cow_to_copied!(edition),
None => return Ok(None),
};
let (transition_ids, _) = match self.id_map().get(transaction_id)? {
Some(ids) => cow_to_cloned!(ids),
None => bail!("Failed to get the transition IDs 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(edition, &transitions)?))
}
fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
let edition = match self.edition_map().get(transaction_id)? {
Some(edition) => cow_to_copied!(edition),
None => return Ok(None),
};
let (transition_ids, optional_additional_fee_id) = match self.id_map().get(transaction_id)? {
Some(ids) => cow_to_cloned!(ids),
None => bail!("Failed to get the transition IDs 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(edition, &transitions)?;
let transaction = match optional_additional_fee_id {
Some(additional_fee_id) => {
let additional_fee = match self.transition_store().get_transition(&additional_fee_id)? {
Some(additional_fee) => additional_fee,
None => bail!("Failed to get the additional fee for transaction '{transaction_id}'"),
};
Transaction::from_execution(execution, Some(additional_fee))?
}
None => 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)]
#[allow(clippy::type_complexity)]
pub struct ExecutionMemory<N: Network> {
id_map: MemoryMap<N::TransactionID, (Vec<N::TransitionID>, Option<N::TransitionID>)>,
reverse_id_map: MemoryMap<N::TransitionID, N::TransactionID>,
edition_map: MemoryMap<N::TransactionID, u16>,
transition_store: TransitionStore<N, TransitionMemory<N>>,
}
#[rustfmt::skip]
impl<N: Network> ExecutionStorage<N> for ExecutionMemory<N> {
type IDMap = MemoryMap<N::TransactionID, (Vec<N::TransitionID>, Option<N::TransitionID>)>;
type ReverseIDMap = MemoryMap<N::TransitionID, N::TransactionID>;
type EditionMap = MemoryMap<N::TransactionID, u16>;
type TransitionStorage = TransitionMemory<N>;
fn open(transition_store: TransitionStore<N, Self::TransitionStorage>) -> Result<Self> {
Ok(Self {
id_map: MemoryMap::default(),
reverse_id_map: MemoryMap::default(),
edition_map: MemoryMap::default(),
transition_store,
})
}
fn id_map(&self) -> &Self::IDMap {
&self.id_map
}
fn reverse_id_map(&self) -> &Self::ReverseIDMap {
&self.reverse_id_map
}
fn edition_map(&self) -> &Self::EditionMap {
&self.edition_map
}
fn transition_store(&self) -> &TransitionStore<N, Self::TransitionStorage> {
&self.transition_store
}
}
#[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(transition_store: TransitionStore<N, E::TransitionStorage>) -> Result<Self> {
let storage = E::open(transition_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 transition_store(&self) -> &TransitionStore<N, E::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 abort_atomic(&self) {
self.storage.abort_atomic();
}
pub fn finish_atomic(&self) -> Result<()> {
self.storage.finish_atomic()
}
}
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)
}
pub fn get_edition(&self, transaction_id: &N::TransactionID) -> Result<Option<u16>> {
match self.storage.edition_map().get(transaction_id)? {
Some(edition) => Ok(Some(cow_to_copied!(edition))),
None => Ok(None),
}
}
pub fn get_additional_fee(&self, transaction_id: &N::TransactionID) -> Result<Option<AdditionalFee<N>>> {
let (_, optional_additional_fee_id) = match self.storage.id_map().get(transaction_id)? {
Some(ids) => cow_to_cloned!(ids),
None => bail!("Failed to get the transition IDs for the transaction '{transaction_id}'"),
};
match optional_additional_fee_id {
Some(additional_fee_id) => {
match self.storage.transition_store().get_transition(&additional_fee_id)? {
Some(additional_fee) => Ok(Some(additional_fee)),
None => bail!("Failed to get the additional fee for transaction '{transaction_id}'"),
}
}
None => Ok(None),
}
}
}
impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
pub fn find_transaction_id(&self, transition_id: &N::TransitionID) -> Result<Option<N::TransactionID>> {
self.storage.find_transaction_id(transition_id)
}
}
impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
pub fn execution_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, N::TransactionID>> {
self.storage.edition_map().keys()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_insert_get_remove() {
let transaction = crate::ledger::vm::test_helpers::sample_execution_transaction();
let transaction_id = transaction.id();
let transition_store = TransitionStore::open().unwrap();
let execution_store = ExecutionMemory::open(transition_store).unwrap();
let candidate = execution_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
execution_store.insert(&transaction).unwrap();
let candidate = execution_store.get_transaction(&transaction_id).unwrap();
assert_eq!(Some(transaction), candidate);
execution_store.remove(&transaction_id).unwrap();
let candidate = execution_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
}
#[test]
fn test_find_transaction_id() {
let transaction = crate::ledger::vm::test_helpers::sample_execution_transaction();
let transaction_id = transaction.id();
let transition_ids = match transaction {
Transaction::Execute(_, ref execution, _) => {
execution.clone().into_transitions().map(|transition| *transition.id()).collect::<Vec<_>>()
}
_ => panic!("Incorrect transaction type"),
};
let transition_store = TransitionStore::open().unwrap();
let execution_store = ExecutionMemory::open(transition_store).unwrap();
let candidate = execution_store.get_transaction(&transaction_id).unwrap();
assert_eq!(None, candidate);
for transition_id in transition_ids {
let candidate = execution_store.find_transaction_id(&transition_id).unwrap();
assert_eq!(None, candidate);
execution_store.insert(&transaction).unwrap();
let candidate = execution_store.find_transaction_id(&transition_id).unwrap();
assert_eq!(Some(transaction_id), candidate);
execution_store.remove(&transaction_id).unwrap();
let candidate = execution_store.find_transaction_id(&transition_id).unwrap();
assert_eq!(None, candidate);
}
}
}