use crate::{
atomic_batch_scope,
helpers::{Map, MapRead},
};
use console::{
network::prelude::*,
program::{Ciphertext, Future, Plaintext, Record},
types::{Field, Group},
};
use ledger_block::Output;
use anyhow::Result;
use std::borrow::Cow;
pub trait OutputStorage<N: Network>: Clone + Send + 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>, ()>;
type FutureMap: for<'a> Map<'a, Field<N>, Option<Future<N>>>;
fn open(dev: Option<u16>) -> 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 future_map(&self) -> &Self::FutureMap;
fn dev(&self) -> Option<u16>;
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();
self.future_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()
|| self.future_map().is_atomic_in_progress()
}
fn atomic_checkpoint(&self) {
self.id_map().atomic_checkpoint();
self.reverse_id_map().atomic_checkpoint();
self.constant_map().atomic_checkpoint();
self.public_map().atomic_checkpoint();
self.private_map().atomic_checkpoint();
self.record_map().atomic_checkpoint();
self.record_nonce_map().atomic_checkpoint();
self.external_record_map().atomic_checkpoint();
self.future_map().atomic_checkpoint();
}
fn clear_latest_checkpoint(&self) {
self.id_map().clear_latest_checkpoint();
self.reverse_id_map().clear_latest_checkpoint();
self.constant_map().clear_latest_checkpoint();
self.public_map().clear_latest_checkpoint();
self.private_map().clear_latest_checkpoint();
self.record_map().clear_latest_checkpoint();
self.record_nonce_map().clear_latest_checkpoint();
self.external_record_map().clear_latest_checkpoint();
self.future_map().clear_latest_checkpoint();
}
fn atomic_rewind(&self) {
self.id_map().atomic_rewind();
self.reverse_id_map().atomic_rewind();
self.constant_map().atomic_rewind();
self.public_map().atomic_rewind();
self.private_map().atomic_rewind();
self.record_map().atomic_rewind();
self.record_nonce_map().atomic_rewind();
self.external_record_map().atomic_rewind();
self.future_map().atomic_rewind();
}
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();
self.future_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()?;
self.future_map().finish_atomic()
}
fn insert(&self, transition_id: N::TransitionID, outputs: &[Output<N>]) -> Result<()> {
atomic_batch_scope!(self, {
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, ())?,
Output::Future(output_id, future) => self.future_map().insert(output_id, future)?,
}
}
Ok(())
})
}
fn remove(&self, transition_id: &N::TransitionID) -> Result<()> {
let output_ids: Vec<_> = match self.id_map().get_confirmed(transition_id)? {
Some(Cow::Borrowed(ids)) => ids.to_vec(),
Some(Cow::Owned(ids)) => ids.into_iter().collect(),
None => return Ok(()),
};
atomic_batch_scope!(self, {
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_confirmed(&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)?;
self.future_map().remove(&output_id)?;
}
Ok(())
})
}
fn find_transition_id(&self, output_id: &Field<N>) -> Result<Option<N::TransitionID>> {
match self.reverse_id_map().get_confirmed(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_confirmed(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| {
if let Some(constant) = self.constant_map().get_confirmed(&output_id)? {
return Ok(into_output!(Output::Constant(output_id, constant)));
}
if let Some(public) = self.public_map().get_confirmed(&output_id)? {
return Ok(into_output!(Output::Public(output_id, public)));
}
if let Some(private) = self.private_map().get_confirmed(&output_id)? {
return Ok(into_output!(Output::Private(output_id, private)));
}
if let Some(record) = self.record_map().get_confirmed(&output_id)? {
return Ok(into_output!(Output::Record(output_id, record)));
}
if self.external_record_map().get_confirmed(&output_id)?.is_some() {
return Ok(Output::ExternalRecord(output_id));
}
if let Some(future) = self.future_map().get_confirmed(&output_id)? {
return Ok(into_output!(Output::Future(output_id, future)));
}
bail!("Missing output '{output_id}' in transition '{transition_id}'")
};
match self.id_map().get_confirmed(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)]
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,
future: O::FutureMap,
storage: O,
}
impl<N: Network, O: OutputStorage<N>> OutputStore<N, O> {
pub fn open(dev: Option<u16>) -> Result<Self> {
let storage = O::open(dev)?;
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(),
future: storage.future_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(),
future: storage.future_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 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, 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_confirmed(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_confirmed(output_id)
}
pub fn contains_commitment(&self, commitment: &Field<N>) -> Result<bool> {
self.record.contains_key_confirmed(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_confirmed(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_confirmed()
}
pub fn constant_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.constant.keys_confirmed()
}
pub fn public_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.public.keys_confirmed()
}
pub fn private_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.private.keys_confirmed()
}
pub fn commitments(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.record.keys_confirmed()
}
pub fn external_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.external_record.keys_confirmed()
}
pub fn future_output_ids(&self) -> impl '_ + Iterator<Item = Cow<'_, Field<N>>> {
self.future.keys_confirmed()
}
}
impl<N: Network, I: OutputStorage<N>> OutputStore<N, I> {
pub fn constant_outputs(&self) -> impl '_ + Iterator<Item = Cow<'_, Plaintext<N>>> {
self.constant.values_confirmed().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_confirmed().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_confirmed().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_confirmed().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_confirmed()
}
pub fn records(&self) -> impl '_ + Iterator<Item = (Cow<'_, Field<N>>, Cow<'_, Record<N, Ciphertext<N>>>)> {
self.record.iter_confirmed().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,
})
}
pub fn future_outputs(&self) -> impl '_ + Iterator<Item = Cow<'_, Future<N>>> {
self.future.values_confirmed().flat_map(|output| match output {
Cow::Borrowed(Some(output)) => Some(Cow::Borrowed(output)),
Cow::Owned(Some(output)) => Some(Cow::Owned(output)),
_ => None,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::memory::OutputMemory;
#[test]
fn test_insert_get_remove() {
for (transition_id, output) in ledger_test_helpers::sample_outputs() {
let output_store = OutputMemory::open(None).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 ledger_test_helpers::sample_outputs() {
let output_store = OutputMemory::open(None).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());
}
}
}