Skip to content

Commit

Permalink
Merge pull request #353 from nuttycom/data_api/transactional_types
Browse files Browse the repository at this point in the history
Make data access API write calls atomic.
  • Loading branch information
str4d authored Mar 17, 2021
2 parents c724ed1 + 2b49517 commit 3a8e729
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 449 deletions.
279 changes: 55 additions & 224 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use zcash_primitives::{
consensus::BlockHeight,
merkle_tree::{CommitmentTree, IncrementalWitness},
note_encryption::Memo,
primitives::{Note, Nullifier, PaymentAddress},
primitives::{Nullifier, PaymentAddress},
sapling::Node,
transaction::{components::Amount, Transaction, TxId},
zip32::ExtendedFullViewingKey,
Expand All @@ -20,7 +20,7 @@ use crate::{
data_api::wallet::ANCHOR_OFFSET,
decrypt::DecryptedOutput,
proto::compact_formats::CompactBlock,
wallet::{AccountId, SpendableNote, WalletShieldedOutput, WalletTx},
wallet::{AccountId, SpendableNote, WalletTx},
};

pub mod chain;
Expand Down Expand Up @@ -177,26 +177,47 @@ pub trait WalletRead {
) -> Result<Vec<SpendableNote>, Self::Error>;
}

/// The subset of information that is relevant to this wallet that has been
/// decrypted and extracted from a [CompactBlock].
pub struct PrunedBlock<'a> {
pub block_height: BlockHeight,
pub block_hash: BlockHash,
pub block_time: u32,
pub commitment_tree: &'a CommitmentTree<Node>,
pub transactions: &'a Vec<WalletTx<Nullifier>>,
}

pub struct ReceivedTransaction<'a> {
pub tx: &'a Transaction,
pub outputs: &'a Vec<DecryptedOutput>,
}

pub struct SentTransaction<'a> {
pub tx: &'a Transaction,
pub created: time::OffsetDateTime,
pub output_index: usize,
pub account: AccountId,
pub recipient_address: &'a RecipientAddress,
pub value: Amount,
pub memo: Option<Memo>,
}

/// This trait encapsulates the write capabilities required to update stored
/// wallet data.
pub trait WalletWrite: WalletRead {
/// Perform one or more write operations of this trait transactionally.
/// Implementations of this method must ensure that all mutations to the
/// state of the data store made by the provided closure must be performed
/// atomically and modifications to state must be automatically rolled back
/// if the provided closure returns an error.
fn transactionally<F, A>(&mut self, f: F) -> Result<A, Self::Error>
where
F: FnOnce(&mut Self) -> Result<A, Self::Error>;
#[allow(clippy::type_complexity)]
fn advance_by_block(
&mut self,
block: &PrunedBlock,
updated_witnesses: &[(Self::NoteRef, IncrementalWitness<Node>)],
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error>;

/// Add the data for a block to the data store.
fn insert_block(
fn store_received_tx(
&mut self,
block_height: BlockHeight,
block_hash: BlockHash,
block_time: u32,
commitment_tree: &CommitmentTree<Node>,
) -> Result<(), Self::Error>;
received_tx: &ReceivedTransaction,
) -> Result<Self::TxRef, Self::Error>;

fn store_sent_tx(&mut self, sent_tx: &SentTransaction) -> Result<Self::TxRef, Self::Error>;

/// Rewinds the wallet database to the specified height.
///
Expand All @@ -212,80 +233,6 @@ pub trait WalletWrite: WalletRead {
///
/// There may be restrictions on how far it is possible to rewind.
fn rewind_to_height(&mut self, block_height: BlockHeight) -> Result<(), Self::Error>;

/// Add wallet-relevant metadata for a specific transaction to the data
/// store.
fn put_tx_meta(
&mut self,
tx: &WalletTx,
height: BlockHeight,
) -> Result<Self::TxRef, Self::Error>;

/// Add a full transaction contents to the data store.
fn put_tx_data(
&mut self,
tx: &Transaction,
created_at: Option<time::OffsetDateTime>,
) -> Result<Self::TxRef, Self::Error>;

/// Mark the specified transaction as spent and record the nullifier.
fn mark_spent(&mut self, tx_ref: Self::TxRef, nf: &Nullifier) -> Result<(), Self::Error>;

/// Record a note as having been received, along with its nullifier and the transaction
/// within which the note was created.
///
/// Implementations of this method must be exclusively additive with respect to stored
/// data; passing `None` for the nullifier should not be interpreted as deleting nullifier
/// information from the underlying store.
///
/// Implementations of this method must ensure that attempting to record the same note
/// with a different nullifier to that already stored will return an error.
fn put_received_note<T: ShieldedOutput>(
&mut self,
output: &T,
nf: &Option<Nullifier>,
tx_ref: Self::TxRef,
) -> Result<Self::NoteRef, Self::Error>;

/// Add the incremental witness for the specified note to the database.
fn insert_witness(
&mut self,
note_id: Self::NoteRef,
witness: &IncrementalWitness<Node>,
height: BlockHeight,
) -> Result<(), Self::Error>;

/// Remove all incremental witness data before the specified block height.
// TODO: this is a backend-specific optimization that probably shouldn't be part of
// the public API
fn prune_witnesses(&mut self, from_height: BlockHeight) -> Result<(), Self::Error>;

/// Remove the spent marker from any received notes that had been spent in a
/// transaction constructed by the wallet, but which transaction had not been mined
/// by the specified block height.
// TODO: this is a backend-specific optimization that probably shouldn't be part of
// the public API
fn update_expired_notes(&mut self, from_height: BlockHeight) -> Result<(), Self::Error>;

/// Add the decrypted contents of a sent note to the database if it does not exist;
/// otherwise, update the note. This is useful in the case of a wallet restore where
/// the send of the note is being discovered via trial decryption.
fn put_sent_note(
&mut self,
output: &DecryptedOutput,
tx_ref: Self::TxRef,
) -> Result<(), Self::Error>;

/// Add the decrypted contents of a sent note to the database.
fn insert_sent_note(
&mut self,
tx_ref: Self::TxRef,
output_index: usize,
account: AccountId,
to: &RecipientAddress,
value: Amount,
memo: Option<Memo>,
) -> Result<(), Self::Error>;
}

/// This trait provides sequential access to raw blockchain data via a callback-oriented
Expand All @@ -305,62 +252,6 @@ pub trait BlockSource {
F: FnMut(CompactBlock) -> Result<(), Self::Error>;
}

/// This trait provides a generalization over shielded output representations
/// that allows a wallet to avoid coupling to a specific one.
// TODO: it'd probably be better not to unify the definitions of
// `WalletShieldedOutput` and `DecryptedOutput` via a compositional
// approach, if possible.
pub trait ShieldedOutput {
fn index(&self) -> usize;
fn account(&self) -> AccountId;
fn to(&self) -> &PaymentAddress;
fn note(&self) -> &Note;
fn memo(&self) -> Option<&Memo>;
fn is_change(&self) -> Option<bool>;
}

impl ShieldedOutput for WalletShieldedOutput {
fn index(&self) -> usize {
self.index
}
fn account(&self) -> AccountId {
self.account
}
fn to(&self) -> &PaymentAddress {
&self.to
}
fn note(&self) -> &Note {
&self.note
}
fn memo(&self) -> Option<&Memo> {
None
}
fn is_change(&self) -> Option<bool> {
Some(self.is_change)
}
}

impl ShieldedOutput for DecryptedOutput {
fn index(&self) -> usize {
self.index
}
fn account(&self) -> AccountId {
self.account
}
fn to(&self) -> &PaymentAddress {
&self.to
}
fn note(&self) -> &Note {
&self.note
}
fn memo(&self) -> Option<&Memo> {
Some(&self.memo)
}
fn is_change(&self) -> Option<bool> {
None
}
}

#[cfg(feature = "test-dependencies")]
pub mod testing {
use std::collections::HashMap;
Expand All @@ -369,21 +260,21 @@ pub mod testing {
block::BlockHash,
consensus::BlockHeight,
merkle_tree::{CommitmentTree, IncrementalWitness},
note_encryption::Memo,
primitives::{Nullifier, PaymentAddress},
sapling::Node,
transaction::{components::Amount, Transaction, TxId},
transaction::{components::Amount, TxId},
zip32::ExtendedFullViewingKey,
};

use crate::{
address::RecipientAddress,
decrypt::DecryptedOutput,
proto::compact_formats::CompactBlock,
wallet::{AccountId, SpendableNote, WalletTx},
wallet::{AccountId, SpendableNote},
};

use super::{error::Error, BlockSource, ShieldedOutput, WalletRead, WalletWrite};
use super::{
error::Error, BlockSource, PrunedBlock, ReceivedTransaction, SentTransaction, WalletRead,
WalletWrite,
};

pub struct MockBlockSource {}

Expand Down Expand Up @@ -493,90 +384,30 @@ pub mod testing {
}

impl WalletWrite for MockWalletDB {
fn transactionally<F, A>(&mut self, f: F) -> Result<A, Self::Error>
where
F: FnOnce(&mut Self) -> Result<A, Self::Error>,
{
f(self)
}

fn insert_block(
#[allow(clippy::type_complexity)]
fn advance_by_block(
&mut self,
_block_height: BlockHeight,
_block_hash: BlockHash,
_block_time: u32,
_commitment_tree: &CommitmentTree<Node>,
) -> Result<(), Self::Error> {
Ok(())
}

fn rewind_to_height(&mut self, _block_height: BlockHeight) -> Result<(), Self::Error> {
Ok(())
_block: &PrunedBlock,
_updated_witnesses: &[(Self::NoteRef, IncrementalWitness<Node>)],
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error> {
Ok(vec![])
}

fn put_tx_meta(
fn store_received_tx(
&mut self,
_tx: &WalletTx,
_height: BlockHeight,
_received_tx: &ReceivedTransaction,
) -> Result<Self::TxRef, Self::Error> {
Ok(TxId([0u8; 32]))
}

fn put_tx_data(
fn store_sent_tx(
&mut self,
_tx: &Transaction,
_created_at: Option<time::OffsetDateTime>,
_sent_tx: &SentTransaction,
) -> Result<Self::TxRef, Self::Error> {
Ok(TxId([0u8; 32]))
}

fn mark_spent(&mut self, _tx_ref: Self::TxRef, _nf: &Nullifier) -> Result<(), Self::Error> {
Ok(())
}

fn put_received_note<T: ShieldedOutput>(
&mut self,
_output: &T,
_nf: &Option<Nullifier>,
_tx_ref: Self::TxRef,
) -> Result<Self::NoteRef, Self::Error> {
Ok(0u32)
}

fn insert_witness(
&mut self,
_note_id: Self::NoteRef,
_witness: &IncrementalWitness<Node>,
_height: BlockHeight,
) -> Result<(), Self::Error> {
Ok(())
}

fn prune_witnesses(&mut self, _from_height: BlockHeight) -> Result<(), Self::Error> {
Ok(())
}

fn update_expired_notes(&mut self, _from_height: BlockHeight) -> Result<(), Self::Error> {
Ok(())
}

fn put_sent_note(
&mut self,
_output: &DecryptedOutput,
_tx_ref: Self::TxRef,
) -> Result<(), Self::Error> {
Ok(())
}

fn insert_sent_note(
&mut self,
_tx_ref: Self::TxRef,
_output_index: usize,
_account: AccountId,
_to: &RecipientAddress,
_value: Amount,
_memo: Option<Memo>,
) -> Result<(), Self::Error> {
fn rewind_to_height(&mut self, _block_height: BlockHeight) -> Result<(), Self::Error> {
Ok(())
}
}
Expand Down
Loading

0 comments on commit 3a8e729

Please sign in to comment.