use crate::ledger::{
map::{memory_map::MemoryMap, Map, MapRead},
transition::Output,
};
use console::{
network::prelude::*,
program::{Ciphertext, Plaintext, Record},
types::{Field, Group},
};
use anyhow::Result;
use std::borrow::Cow;
pub trait OutputStorage<N: Network>: Clone + Sync {
type IDMap: for<'a> Map<'a, N::TransitionID, Vec<Field<N>>>;
type ReverseIDMap: for<'a> Map<'a, Field<N>, N::TransitionID>;
type ConstantMap: for<'a> Map<'a, Field<N>, Option<Plaintext<N>>>;
type PublicMap: for<'a> Map<'a, Field<N>, Option<Plaintext<N>>>;
type PrivateMap: for<'a> Map<'a, Field<N>, Option<Ciphertext<N>>>;
type RecordMap: for<'a> Map<'a, Field<N>, (Field<N>, Option<Record<N, Ciphertext<N>>>)>;
type RecordNonceMap: for<'a> Map<'a, Group<N>, Field<N>>;
type ExternalRecordMap: for<'a> Map<'a, Field<N>, ()>;
fn open() -> Result<Self>;
fn id_map(&self) -> &Self::IDMap;
fn reverse_id_map(&self) -> &Self::ReverseIDMap;
fn constant_map(&self) -> &Self::ConstantMap;
fn public_map(&self) -> &Self::PublicMap;
fn private_map(&self) -> &Self::PrivateMap;
fn record_map(&self) -> &Self::RecordMap;
fn record_nonce_map(&self) -> &Self::RecordNonceMap;
fn external_record_map(&self) -> &Self::ExternalRecordMap;
fn start_atomic(&self) {
self.id_map().start_atomic();
self.reverse_id_map().start_atomic();
self.constant_map().start_atomic();
self.public_map().start_atomic();
self.private_map().start_atomic();
self.record_map().start_atomic();
self.record_nonce_map().start_atomic();
self.external_record_map().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.constant_map().is_atomic_in_progress()
|| self.public_map().is_atomic_in_progress()
|| self.private_map().is_atomic_in_progress()
|| self.record_map().is_atomic_in_progress()
|| self.record_nonce_map().is_atomic_in_progress()
|| self.external_record_map().is_atomic_in_progress()
}
fn abort_atomic(&self) {
self.id_map().abort_atomic();
self.reverse_id_map().abort_atomic();
self.constant_map().abort_atomic();
self.public_map().abort_atomic();
self.private_map().abort_atomic();
self.record_map().abort_atomic();
self.record_nonce_map().abort_atomic();
self.external_record_map().abort_atomic();
}
fn finish_atomic(&self) -> Result<()> {
self.id_map().finish_atomic()?;
self.reverse_id_map().finish_atomic()?;
self.constant_map().finish_atomic()?;
self.public_map().finish_atomic()?;
self.private_map().finish_atomic()?;
self.record_map().finish_atomic()?;
self.record_nonce_map().finish_atomic()?;
self.external_record_map().finish_atomic()
}
fn insert(&self, transition_id: N::TransitionID, outputs: &[Output<N>]) -> Result<()> {
let is_part_of_atomic_batch = self.is_atomic_in_progress();
if !is_part_of_atomic_batch {
self.start_atomic();
}
let run_atomic_ops = || -> Result<()> {
self.id_map().insert(transition_id, outputs.iter().map(Output::id).copied().collect())?;
for output in outputs {
self.reverse_id_map().insert(*output.id(), transition_id)?;
match output.clone() {
Output::Constant(output_id, constant) => self.constant_map().insert(output_id, constant)?,
Output::Public(output_id, public) => self.public_map().insert(output_id, public)?,
Output::Private(output_id, private) => self.private_map().insert(output_id, private)?,
Output::Record(commitment, checksum, optional_record) => {
if let Some(record) = &optional_record {
self.record_nonce_map().insert(*record.nonce(), commitment)?;
}
self.record_map().insert(commitment, (checksum, optional_record))?
}
Output::ExternalRecord(output_id) => self.external_record_map().insert(output_id, ())?,
}
}
Ok(())
};
run_atomic_ops().map_err(|err| {
self.abort_atomic();
err
})?;
if !is_part_of_atomic_batch {
self.finish_atomic()?;
}
Ok(())
}
fn remove(&self, transition_id: &N::TransitionID) -> Result<()> {
let output_ids: Vec<_> = match self.id_map().get(transition_id)? {
Some(Cow::Borrowed(ids)) => ids.to_vec(),
Some(Cow::Owned(ids)) => ids.into_iter().collect(),
None => return Ok(()),
};
let is_part_of_atomic_batch = self.is_atomic_in_progress();
if !is_part_of_atomic_batch {
self.start_atomic();
}
let run_atomic_ops = || -> Result<()> {
self.id_map().remove(transition_id)?;
for output_id in output_ids {
self.reverse_id_map().remove(&output_id)?;
if let Some(record) = self.record_map().get(&output_id)? {
if let Some(record) = &record.1 {
self.record_nonce_map().remove(record.nonce())?;
}
}
self.constant_map().remove(&output_id)?;
self.public_map().remove(&output_id)?;
self.private_map().remove(&output_id)?;
self.record_map().remove(&output_id)?;
self.external_record_map().remove(&output_id)?;
}
Ok(())
};
run_atomic_ops().map_err(|err| {
self.abort_atomic();
err
})?;
if !is_part_of_atomic_batch {
self.finish_atomic()?;
}
Ok(())
}
fn find_transition_id(&self, output_id: &Field<N>) -> Result<Option<N::TransitionID>> {
match self.reverse_id_map().get(output_id)? {
Some(Cow::Borrowed(transition_id)) => Ok(Some(*transition_id)),
Some(Cow::Owned(transition_id)) => Ok(Some(transition_id)),
None => Ok(None),
}
}
fn get_ids(&self, transition_id: &N::TransitionID) -> Result<Vec<Field<N>>> {
match self.id_map().get(transition_id)? {
Some(Cow::Borrowed(outputs)) => Ok(outputs.to_vec()),
Some(Cow::Owned(outputs)) => Ok(outputs),
None => Ok(vec![]),
}
}
fn get(&self, transition_id: &N::TransitionID) -> Result<Vec<Output<N>>> {
macro_rules! into_output {
(Output::Record($output_id:ident, $output:expr)) => {
match $output {
Cow::Borrowed((checksum, opt_record)) => Output::Record($output_id, *checksum, opt_record.clone()),
Cow::Owned((checksum, opt_record)) => Output::Record($output_id, checksum, opt_record),
}
};
(Output::$Variant:ident($output_id:ident, $output:expr)) => {
match $output {
Cow::Borrowed(output) => Output::$Variant($output_id, output.clone()),
Cow::Owned(output) => Output::$Variant($output_id, output),
}
};
}
let construct_output = |output_id| {
let constant = self.constant_map().get(&output_id)?;
let public = self.public_map().get(&output_id)?;
let private = self.private_map().get(&output_id)?;
let record = self.record_map().get(&output_id)?;
let external_record = self.external_record_map().get(&output_id)?;
let output = match (constant, public, private, record, external_record) {
(Some(constant), None, None, None, None) => into_output!(Output::Constant(output_id, constant)),
(None, Some(public), None, None, None) => into_output!(Output::Public(output_id, public)),
(None, None, Some(private), None, None) => into_output!(Output::Private(output_id, private)),
(None, None, None, Some(record), None) => into_output!(Output::Record(output_id, record)),
(None, None, None, None, Some(_)) => Output::ExternalRecord(output_id),
(None, None, None, None, None) => bail!("Missing output '{output_id}' in transition '{transition_id}'"),
_ => bail!("Found multiple outputs for the output ID '{output_id}' in transition '{transition_id}'"),
};
Ok(output)
};
match self.id_map().get(transition_id)? {
Some(Cow::Borrowed(ids)) => ids.iter().map(|output_id| construct_output(*output_id)).collect(),
Some(Cow::Owned(ids)) => ids.iter().map(|output_id| construct_output(*output_id)).collect(),
None => Ok(vec![]),
}
}
}
#[derive(Clone)]
#[allow(clippy::type_complexity)]
pub struct OutputMemory<N: Network> {
id_map: MemoryMap<N::TransitionID, Vec<Field<N>>>,
reverse_id_map: MemoryMap<Field<N>, N::TransitionID>,
constant: MemoryMap<Field<N>, Option<Plaintext<N>>>,
public: MemoryMap<Field<N>, Option<Plaintext<N>>>,
private: MemoryMap<Field<N>, Option<Ciphertext<N>>>,
record: MemoryMap<Field<N>, (Field<N>, Option<Record<N, Ciphertext<N>>>)>,
record_nonce: MemoryMap<Group<N>, Field<N>>,
external_record: MemoryMap<Field<N>, ()>,
}
#[rustfmt::skip]
impl<N: Network> OutputStorage<N> for OutputMemory<N> {
type IDMap = MemoryMap<N::TransitionID, Vec<Field<N>>>;
type ReverseIDMap = MemoryMap<Field<N>, N::TransitionID>;
type ConstantMap = MemoryMap<Field<N>, Option<Plaintext<N>>>;
type PublicMap = MemoryMap<Field<N>, Option<Plaintext<N>>>;
type PrivateMap = MemoryMap<Field<N>, Option<Ciphertext<N>>>;
type RecordMap = MemoryMap<Field<N>, (Field<N>, Option<Record<N, Ciphertext<N>>>)>;
type RecordNonceMap = MemoryMap<Group<N>, Field<N>>;
type ExternalRecordMap = MemoryMap<Field<N>, ()>;
fn open() -> Result<Self> {
Ok(Self {
id_map: Default::default(),
reverse_id_map: Default::default(),
constant: Default::default(),
public: Default::default(),
private: Default::default(),
record: Default::default(),
record_nonce: Default::default(),
external_record: Default::default(),
})
}
fn id_map(&self) -> &Self::IDMap {
&self.id_map
}
fn reverse_id_map(&self) -> &Self::ReverseIDMap {
&self.reverse_id_map
}
fn constant_map(&self) -> &Self::ConstantMap {
&self.constant
}
fn public_map(&self) -> &Self::PublicMap {
&self.public
}
fn private_map(&self) -> &Self::PrivateMap {
&self.private
}
fn record_map(&self) -> &Self::RecordMap {
&self.record
}
fn record_nonce_map(&self) -> &Self::RecordNonceMap {
&self.record_nonce
}
fn external_record_map(&self) -> &Self::ExternalRecordMap {
&self.external_record
}
}
#[derive(Clone)]
pub struct OutputStore<N: Network, O: OutputStorage<N>> {
constant: O::ConstantMap,
public: O::PublicMap,
private: O::PrivateMap,
record: O::RecordMap,
record_nonce: O::RecordNonceMap,
external_record: O::ExternalRecordMap,
storage: O,
}
impl<N: Network, O: OutputStorage<N>> OutputStore<N, O> {
pub fn open() -> Result<Self> {
let storage = O::open()?;
Ok(Self {
constant: storage.constant_map().clone(),
public: storage.public_map().clone(),
private: storage.private_map().clone(),
record: storage.record_map().clone(),
record_nonce: storage.record_nonce_map().clone(),
external_record: storage.external_record_map().clone(),
storage,
})
}
pub fn from(storage: O) -> Self {
Self {
constant: storage.constant_map().clone(),
public: storage.public_map().clone(),
private: storage.private_map().clone(),
record: storage.record_map().clone(),
record_nonce: storage.record_nonce_map().clone(),
external_record: storage.external_record_map().clone(),
storage,
}
}
pub fn insert(&self, transition_id: N::TransitionID, outputs: &[Output<N>]) -> Result<()> {
self.storage.insert(transition_id, outputs)
}
pub fn remove(&self, transition_id: &N::TransitionID) -> Result<()> {
self.storage.remove(transition_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 abort_atomic(&self) {
self.storage.abort_atomic();
}
pub fn finish_atomic(&self) -> Result<()> {
self.storage.finish_atomic()
}
}
impl<N: Network, O: OutputStorage<N>> OutputStore<N, O> {
pub fn get_output_ids(&self, transition_id: &N::TransitionID) -> Result<Vec<Field<N>>> {
self.storage.get_ids(transition_id)
}
pub fn get_outputs(&self, transition_id: &N::TransitionID) -> Result<Vec<Output<N>>> {
self.storage.get(transition_id)
}
pub fn get_record(&self, commitment: &Field<N>) -> Result<Option<Record<N, Ciphertext<N>>>> {
match self.record.get(commitment) {
Ok(Some(Cow::Borrowed((_, Some(record))))) => Ok(Some((*record).clone())),
Ok(Some(Cow::Owned((_, Some(record))))) => Ok(Some(record)),
Ok(Some(Cow::Borrowed((_, None)))) => Ok(None),
Ok(Some(Cow::Owned((_, None)))) => Ok(None),
Ok(None) => bail!("Record '{commitment}' not found"),
Err(e) => Err(e),
}
}
}
impl<N: Network, O: OutputStorage<N>> OutputStore<N, O> {
pub fn find_transition_id(&self, output_id: &Field<N>) -> Result<Option<N::TransitionID>> {
self.storage.find_transition_id(output_id)
}
}
impl<N: Network, O: OutputStorage<N>> OutputStore<N, O> {
pub fn contains_output_id(&self, output_id: &Field<N>) -> Result<bool> {
self.storage.reverse_id_map().contains_key(output_id)
}
pub fn contains_commitment(&self, commitment: &Field<N>) -> Result<bool> {
self.record.contains_key(commitment)
}
pub fn contains_checksum(&self, checksum: &Field<N>) -> bool {
self.checksums().contains(checksum)
}
pub fn contains_nonce(&self, nonce: &Group<N>) -> Result<bool> {
self.record_nonce.contains_key(nonce)
}
}
impl<N: Network, O: OutputStorage<N>> OutputStore<N, O> {
pub fn output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.storage.reverse_id_map().keys()
}
pub fn constant_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.constant.keys()
}
pub fn public_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.public.keys()
}
pub fn private_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.private.keys()
}
pub fn commitments(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.record.keys()
}
pub fn external_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.external_record.keys()
}
}
impl<N: Network, I: OutputStorage<N>> OutputStore<N, I> {
pub fn constant_outputs(&self) -> impl '_ + Iterator<Item = Cow<'_, Plaintext<N>>> {
self.constant.values().flat_map(|output| match output {
Cow::Borrowed(Some(output)) => Some(Cow::Borrowed(output)),
Cow::Owned(Some(output)) => Some(Cow::Owned(output)),
_ => None,
})
}
pub fn public_outputs(&self) -> impl '_ + Iterator<Item = Cow<'_, Plaintext<N>>> {
self.public.values().flat_map(|output| match output {
Cow::Borrowed(Some(output)) => Some(Cow::Borrowed(output)),
Cow::Owned(Some(output)) => Some(Cow::Owned(output)),
_ => None,
})
}
pub fn private_outputs(&self) -> impl '_ + Iterator<Item = Cow<'_, Ciphertext<N>>> {
self.private.values().flat_map(|output| match output {
Cow::Borrowed(Some(output)) => Some(Cow::Borrowed(output)),
Cow::Owned(Some(output)) => Some(Cow::Owned(output)),
_ => None,
})
}
pub fn checksums(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.record.values().map(|output| match output {
Cow::Borrowed((checksum, _)) => Cow::Borrowed(checksum),
Cow::Owned((checksum, _)) => Cow::Owned(checksum),
})
}
pub fn nonces(&self) -> impl '_ + Iterator<Item = Cow<'_, Group<N>>> {
self.record_nonce.keys()
}
pub fn records(&self) -> impl '_ + Iterator<Item = (Cow<'_, Field<N>>, Cow<'_, Record<N, Ciphertext<N>>>)> {
self.record.iter().flat_map(|(commitment, output)| match output {
Cow::Borrowed((_, Some(record))) => Some((commitment, Cow::Borrowed(record))),
Cow::Owned((_, Some(record))) => Some((commitment, Cow::Owned(record))),
_ => None,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_insert_get_remove() {
for (transition_id, output) in crate::ledger::transition::output::test_helpers::sample_outputs() {
let output_store = OutputMemory::open().unwrap();
let candidate = output_store.get(&transition_id).unwrap();
assert!(candidate.is_empty());
output_store.insert(transition_id, &[output.clone()]).unwrap();
let candidate = output_store.get(&transition_id).unwrap();
assert_eq!(vec![output.clone()], candidate);
output_store.remove(&transition_id).unwrap();
let candidate = output_store.get(&transition_id).unwrap();
assert!(candidate.is_empty());
}
}
#[test]
fn test_find_transition_id() {
for (transition_id, output) in crate::ledger::transition::output::test_helpers::sample_outputs() {
let output_store = OutputMemory::open().unwrap();
let candidate = output_store.get(&transition_id).unwrap();
assert!(candidate.is_empty());
let candidate = output_store.find_transition_id(output.id()).unwrap();
assert!(candidate.is_none());
output_store.insert(transition_id, &[output.clone()]).unwrap();
let candidate = output_store.find_transition_id(output.id()).unwrap();
assert_eq!(Some(transition_id), candidate);
output_store.remove(&transition_id).unwrap();
let candidate = output_store.find_transition_id(output.id()).unwrap();
assert!(candidate.is_none());
}
}
}