2020-08-26 14:47:47 -07:00
|
|
|
use std::cmp;
|
2020-09-11 15:17:43 -07:00
|
|
|
use std::fmt::Debug;
|
2020-08-26 14:47:47 -07:00
|
|
|
|
2020-07-22 19:44:06 -07:00
|
|
|
use zcash_primitives::{
|
|
|
|
block::BlockHash,
|
2020-08-04 19:49:41 -07:00
|
|
|
consensus::{self, BlockHeight},
|
2020-08-18 12:48:59 -07:00
|
|
|
merkle_tree::{CommitmentTree, IncrementalWitness},
|
2020-08-25 14:02:44 -07:00
|
|
|
note_encryption::Memo,
|
2021-01-08 20:49:38 -08:00
|
|
|
primitives::{Note, PaymentAddress, Nullifier},
|
2020-08-06 17:13:24 -07:00
|
|
|
sapling::Node,
|
2020-08-25 14:02:44 -07:00
|
|
|
transaction::{components::Amount, Transaction, TxId},
|
2020-08-04 19:49:41 -07:00
|
|
|
zip32::ExtendedFullViewingKey,
|
2020-07-22 19:44:06 -07:00
|
|
|
};
|
|
|
|
|
2020-08-20 10:41:43 -07:00
|
|
|
use crate::{
|
2020-08-25 17:31:21 -07:00
|
|
|
address::RecipientAddress,
|
2020-08-26 14:47:47 -07:00
|
|
|
data_api::wallet::ANCHOR_OFFSET,
|
2020-08-25 14:02:44 -07:00
|
|
|
decrypt::DecryptedOutput,
|
2020-08-20 10:41:43 -07:00
|
|
|
proto::compact_formats::CompactBlock,
|
2020-08-26 14:47:47 -07:00
|
|
|
wallet::{AccountId, SpendableNote, WalletShieldedOutput, WalletTx},
|
2020-08-20 10:41:43 -07:00
|
|
|
};
|
2020-07-22 19:44:06 -07:00
|
|
|
|
|
|
|
pub mod chain;
|
|
|
|
pub mod error;
|
2020-08-25 14:02:44 -07:00
|
|
|
pub mod wallet;
|
2020-07-22 19:44:06 -07:00
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Read-only operations require for light wallet functions.
|
|
|
|
///
|
|
|
|
/// This trait defines the read-only portion of the storage
|
|
|
|
/// interface atop which higher-level wallet operations are
|
|
|
|
/// implemented. It serves to allow wallet functions to be
|
|
|
|
/// abstracted away from any particular data storage substrate.
|
2020-10-19 14:20:34 -07:00
|
|
|
pub trait WalletRead {
|
2021-01-08 11:49:10 -08:00
|
|
|
/// The type of errors produced by a wallet backend.
|
2020-07-22 19:44:06 -07:00
|
|
|
type Error;
|
2021-01-08 11:49:10 -08:00
|
|
|
|
|
|
|
/// Backend-specific note identifier.
|
|
|
|
///
|
|
|
|
/// For example, this might be a database identifier type
|
|
|
|
/// or a UUID.
|
|
|
|
type NoteRef: Copy + Debug;
|
|
|
|
|
|
|
|
/// Backend-specific transaction identifier.
|
|
|
|
///
|
|
|
|
/// For example, this might be a database identifier type
|
|
|
|
/// or a TxId if the backend is able to support that type
|
|
|
|
/// directly.
|
2020-09-11 15:17:43 -07:00
|
|
|
type TxRef: Copy + Debug;
|
2020-07-22 19:44:06 -07:00
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the minimum and maximum block heights for stored blocks.
|
2020-07-22 19:44:06 -07:00
|
|
|
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error>;
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the default target height and anchor height, given the
|
|
|
|
/// range of block heights that the backend knows about.
|
2020-08-26 14:47:47 -07:00
|
|
|
fn get_target_and_anchor_heights(
|
|
|
|
&self,
|
|
|
|
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
|
|
|
|
self.block_height_extrema().map(|heights| {
|
|
|
|
heights.map(|(min_height, max_height)| {
|
|
|
|
let target_height = max_height + 1;
|
|
|
|
|
|
|
|
// Select an anchor ANCHOR_OFFSET back from the target block,
|
|
|
|
// unless that would be before the earliest block we have.
|
|
|
|
let anchor_height = BlockHeight::from(cmp::max(
|
|
|
|
u32::from(target_height).saturating_sub(ANCHOR_OFFSET),
|
|
|
|
u32::from(min_height),
|
|
|
|
));
|
|
|
|
|
|
|
|
(target_height, anchor_height)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the block hash for the block at the given height
|
2020-07-22 19:44:06 -07:00
|
|
|
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error>;
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the block hash for the block at the maximum height known
|
|
|
|
/// in stored data.
|
2020-10-19 14:16:06 -07:00
|
|
|
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
|
2020-10-19 14:20:34 -07:00
|
|
|
self.block_height_extrema()
|
|
|
|
.and_then(|extrema_opt| {
|
|
|
|
extrema_opt
|
|
|
|
.map(|(_, max_height)| {
|
|
|
|
self.get_block_hash(max_height)
|
|
|
|
.map(|hash_opt| hash_opt.map(move |hash| (max_height, hash)))
|
|
|
|
})
|
|
|
|
.transpose()
|
|
|
|
})
|
|
|
|
.map(|oo| oo.flatten())
|
2020-10-19 14:16:06 -07:00
|
|
|
}
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the block height in which the specified transaction was mined.
|
2020-08-25 14:02:44 -07:00
|
|
|
fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error>;
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the payment address for the specified account, if the account
|
|
|
|
/// identifier specified refers to a valid account for this wallet.
|
2020-08-05 16:01:22 -07:00
|
|
|
fn get_address<P: consensus::Parameters>(
|
|
|
|
&self,
|
|
|
|
params: &P,
|
2020-08-20 10:41:43 -07:00
|
|
|
account: AccountId,
|
2020-08-05 16:01:22 -07:00
|
|
|
) -> Result<Option<PaymentAddress>, Self::Error>;
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns all extended full viewing keys known about by this wallet
|
|
|
|
// TODO: Should this also take an AccountId as argument?
|
2020-08-25 14:51:11 -07:00
|
|
|
fn get_extended_full_viewing_keys<P: consensus::Parameters>(
|
2020-08-25 14:02:44 -07:00
|
|
|
&self,
|
|
|
|
params: &P,
|
|
|
|
) -> Result<Vec<ExtendedFullViewingKey>, Self::Error>;
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Checks whether the specified extended full viewing key is a valid
|
|
|
|
/// key for the specified account.
|
2020-08-26 14:47:47 -07:00
|
|
|
fn is_valid_account_extfvk<P: consensus::Parameters>(
|
|
|
|
&self,
|
|
|
|
params: &P,
|
|
|
|
account: AccountId,
|
|
|
|
extfvk: &ExtendedFullViewingKey,
|
|
|
|
) -> Result<bool, Self::Error>;
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the wallet balance for the specified account.
|
|
|
|
///
|
|
|
|
/// This balance amount is the raw balance of all transactions in known
|
|
|
|
/// mined blocks, irrespective of confirmation depth.
|
|
|
|
// TODO: Do we actually need this? You can always get the "verified"
|
|
|
|
// balance from the current chain tip.
|
2020-08-20 10:41:43 -07:00
|
|
|
fn get_balance(&self, account: AccountId) -> Result<Amount, Self::Error>;
|
2020-08-06 09:23:15 -07:00
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the wallet balance for an account as of the specified block
|
|
|
|
/// height. and
|
|
|
|
///
|
|
|
|
/// This may be used to obtain a balance that ignores notes that have been
|
|
|
|
/// received so recently that they are not yet deemed spendable.
|
2020-08-26 14:47:47 -07:00
|
|
|
fn get_verified_balance(
|
|
|
|
&self,
|
|
|
|
account: AccountId,
|
|
|
|
anchor_height: BlockHeight,
|
|
|
|
) -> Result<Amount, Self::Error>;
|
2020-08-06 13:11:25 -07:00
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the memo for a received note, if it is known and a valid UTF-8 string.
|
2020-08-06 13:11:25 -07:00
|
|
|
fn get_received_memo_as_utf8(
|
|
|
|
&self,
|
2020-08-20 10:41:43 -07:00
|
|
|
id_note: Self::NoteRef,
|
2020-08-06 13:11:25 -07:00
|
|
|
) -> Result<Option<String>, Self::Error>;
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the memo for a sent note, if it is known and a valid UTF-8 string.
|
2020-08-20 10:41:43 -07:00
|
|
|
fn get_sent_memo_as_utf8(&self, id_note: Self::NoteRef) -> Result<Option<String>, Self::Error>;
|
2020-08-17 10:46:44 -07:00
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the note commitment tree at the specified block height.
|
2020-08-06 17:13:24 -07:00
|
|
|
fn get_commitment_tree(
|
|
|
|
&self,
|
|
|
|
block_height: BlockHeight,
|
|
|
|
) -> Result<Option<CommitmentTree<Node>>, Self::Error>;
|
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the incremental witnesses as of the specified block height.
|
2020-08-18 12:48:59 -07:00
|
|
|
fn get_witnesses(
|
|
|
|
&self,
|
|
|
|
block_height: BlockHeight,
|
2020-08-20 10:41:43 -07:00
|
|
|
) -> Result<Vec<(Self::NoteRef, IncrementalWitness<Node>)>, Self::Error>;
|
2020-08-18 12:48:59 -07:00
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns the unspent nullifiers, along with the account identifiers
|
|
|
|
/// with which they are associated.
|
2021-01-08 20:49:38 -08:00
|
|
|
fn get_nullifiers(&self) -> Result<Vec<(Nullifier, AccountId)>, Self::Error>;
|
2020-08-20 10:41:43 -07:00
|
|
|
|
2021-01-08 11:49:10 -08:00
|
|
|
/// Returns a list of spendable notes sufficient to cover the specified
|
|
|
|
/// target value, if possible.
|
2020-08-26 14:47:47 -07:00
|
|
|
fn select_spendable_notes(
|
|
|
|
&self,
|
|
|
|
account: AccountId,
|
|
|
|
target_value: Amount,
|
|
|
|
anchor_height: BlockHeight,
|
|
|
|
) -> Result<Vec<SpendableNote>, Self::Error>;
|
2020-07-22 19:44:06 -07:00
|
|
|
}
|
|
|
|
|
2021-01-08 17:08:02 -08:00
|
|
|
/// This trait encapsulate the write capabilities required to update stored
|
|
|
|
/// wallet data.
|
2021-01-08 11:49:10 -08:00
|
|
|
pub trait WalletWrite: WalletRead {
|
2021-01-08 17:08:02 -08:00
|
|
|
/// 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.
|
2020-08-26 16:52:21 -07:00
|
|
|
fn transactionally<F, A>(&mut self, f: F) -> Result<A, Self::Error>
|
|
|
|
where
|
|
|
|
F: FnOnce(&mut Self) -> Result<A, Self::Error>;
|
|
|
|
|
2021-01-08 17:08:02 -08:00
|
|
|
/// Add the data for a block to the data store.
|
2020-08-20 10:41:43 -07:00
|
|
|
fn insert_block(
|
|
|
|
&mut self,
|
|
|
|
block_height: BlockHeight,
|
|
|
|
block_hash: BlockHash,
|
|
|
|
block_time: u32,
|
|
|
|
commitment_tree: &CommitmentTree<Node>,
|
|
|
|
) -> Result<(), Self::Error>;
|
|
|
|
|
2021-01-08 17:08:02 -08:00
|
|
|
/// This method assumes that the state of the underlying data store is
|
|
|
|
/// consistent up to a particular block height. Since it is possible that
|
|
|
|
/// a chain reorg might invalidate some stored state, this method must be
|
|
|
|
/// implemented in order to allow users of this API to "reset" the data store
|
|
|
|
/// to correctly represent chainstate as of a specified block height.
|
2020-08-26 16:52:21 -07:00
|
|
|
fn rewind_to_height<P: consensus::Parameters>(
|
|
|
|
&mut self,
|
|
|
|
parameters: &P,
|
|
|
|
block_height: BlockHeight,
|
|
|
|
) -> Result<(), Self::Error>;
|
|
|
|
|
2021-01-08 17:08:02 -08:00
|
|
|
/// Add wallet-relevant metadata for a specific transaction to the data
|
|
|
|
/// store.
|
2020-08-25 14:02:44 -07:00
|
|
|
fn put_tx_meta(
|
|
|
|
&mut self,
|
|
|
|
tx: &WalletTx,
|
|
|
|
height: BlockHeight,
|
|
|
|
) -> Result<Self::TxRef, Self::Error>;
|
|
|
|
|
2021-01-08 20:49:38 -08:00
|
|
|
/// Add a full transaction contents to the data store.
|
2020-08-26 14:47:47 -07:00
|
|
|
fn put_tx_data(
|
|
|
|
&mut self,
|
|
|
|
tx: &Transaction,
|
|
|
|
created_at: Option<time::OffsetDateTime>,
|
|
|
|
) -> Result<Self::TxRef, Self::Error>;
|
2020-08-20 10:41:43 -07:00
|
|
|
|
2021-01-08 20:49:38 -08:00
|
|
|
fn mark_spent(&mut self, tx_ref: Self::TxRef, nf: &Nullifier) -> Result<(), Self::Error>;
|
2020-08-20 10:41:43 -07:00
|
|
|
|
2020-08-25 14:02:44 -07:00
|
|
|
fn put_received_note<T: ShieldedOutput>(
|
2020-08-20 10:41:43 -07:00
|
|
|
&mut self,
|
2020-08-25 14:02:44 -07:00
|
|
|
output: &T,
|
2021-01-08 20:49:38 -08:00
|
|
|
nf: &Option<Nullifier>,
|
2020-08-20 10:41:43 -07:00
|
|
|
tx_ref: Self::TxRef,
|
|
|
|
) -> Result<Self::NoteRef, Self::Error>;
|
|
|
|
|
|
|
|
fn insert_witness(
|
|
|
|
&mut self,
|
|
|
|
note_id: Self::NoteRef,
|
|
|
|
witness: &IncrementalWitness<Node>,
|
|
|
|
height: BlockHeight,
|
|
|
|
) -> Result<(), Self::Error>;
|
|
|
|
|
|
|
|
fn prune_witnesses(&mut self, from_height: BlockHeight) -> Result<(), Self::Error>;
|
|
|
|
|
|
|
|
fn update_expired_notes(&mut self, from_height: BlockHeight) -> Result<(), Self::Error>;
|
2020-08-25 14:02:44 -07:00
|
|
|
|
|
|
|
fn put_sent_note<P: consensus::Parameters>(
|
|
|
|
&mut self,
|
|
|
|
params: &P,
|
|
|
|
output: &DecryptedOutput,
|
|
|
|
tx_ref: Self::TxRef,
|
|
|
|
) -> Result<(), Self::Error>;
|
2020-08-25 17:31:21 -07:00
|
|
|
|
|
|
|
fn insert_sent_note<P: consensus::Parameters>(
|
|
|
|
&mut self,
|
|
|
|
params: &P,
|
|
|
|
tx_ref: Self::TxRef,
|
|
|
|
output_index: usize,
|
|
|
|
account: AccountId,
|
|
|
|
to: &RecipientAddress,
|
|
|
|
value: Amount,
|
|
|
|
memo: Option<Memo>,
|
|
|
|
) -> Result<(), Self::Error>;
|
2020-08-20 10:41:43 -07:00
|
|
|
}
|
|
|
|
|
2020-10-19 14:20:34 -07:00
|
|
|
pub trait BlockSource {
|
2020-07-22 19:44:06 -07:00
|
|
|
type Error;
|
|
|
|
|
2020-08-04 15:34:09 -07:00
|
|
|
fn init_cache(&self) -> Result<(), Self::Error>;
|
|
|
|
|
2020-10-19 15:52:48 -07:00
|
|
|
fn with_blocks<F>(
|
2020-08-18 15:33:34 -07:00
|
|
|
&self,
|
|
|
|
from_height: BlockHeight,
|
|
|
|
limit: Option<u32>,
|
|
|
|
with_row: F,
|
|
|
|
) -> Result<(), Self::Error>
|
|
|
|
where
|
2020-10-19 15:52:48 -07:00
|
|
|
F: FnMut(CompactBlock) -> Result<(), Self::Error>;
|
2020-07-22 19:44:06 -07:00
|
|
|
}
|
2020-08-25 14:02:44 -07:00
|
|
|
|
|
|
|
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 {
|
2021-01-08 20:49:38 -08:00
|
|
|
self.account
|
2020-08-25 14:02:44 -07:00
|
|
|
}
|
|
|
|
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 {
|
|
|
|
AccountId(self.account as u32)
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|