Skip to content

Commit

Permalink
Merge branch 'ec2/memwallet' into willem/tree-and-source-impls
Browse files Browse the repository at this point in the history
  • Loading branch information
ec2 committed Aug 28, 2024
2 parents 144bfca + f73f286 commit d50429d
Show file tree
Hide file tree
Showing 15 changed files with 1,226 additions and 1,051 deletions.
25 changes: 20 additions & 5 deletions zcash_client_memory/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::convert::Infallible;

use shardtree::error::ShardTreeError;
use zcash_keys::keys::{AddressGenerationError, DerivationError};
use zcash_primitives::transaction::TxId;
use zcash_protocol::memo;
use zcash_protocol::{consensus::BlockHeight, memo};

use crate::mem_wallet::AccountId;
use crate::AccountId;

type Type = AddressGenerationError;

Expand All @@ -16,7 +19,7 @@ pub enum Error {
MemoDecryption(memo::Error),
#[error("Error deriving key: {0}")]
KeyDerivation(DerivationError),
#[error("Unknown ZIP32 derivation ")]
#[error("Unknown ZIP32 derivation")]
UnknownZip32Derivation,
#[error("Error generating address: {0}")]
AddressGeneration(Type),
Expand All @@ -31,11 +34,17 @@ pub enum Error {
#[error("Conflicting Tx Locator map entry")]
ConflictingTxLocator,
#[error("Io Error: {0}")]
IoError(std::io::Error),
Io(std::io::Error),
#[error("Corrupted Data: {0}")]
CorruptedData(String),
#[error("An error occurred while processing an account due to a failure in deriving the account's keys: {0}")]
BadAccountData(String),
#[error("Blocks are non sequental")]
NonSequentialBlocks,
#[error("Invalid scan range start {0}, end {1}: {2}")]
InvalidScanRange(BlockHeight, BlockHeight, String),
#[error("ShardTree error: {0}")]
ShardTree(ShardTreeError<Infallible>),
#[error("Other error: {0}")]
Other(String),
}
Expand All @@ -60,6 +69,12 @@ impl From<memo::Error> for Error {

impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
Error::IoError(value)
Error::Io(value)
}
}

impl From<ShardTreeError<Infallible>> for Error {
fn from(value: ShardTreeError<Infallible>) -> Self {
Error::ShardTree(value)
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use zcash_client_backend::data_api::InputSource;

use crate::mem_wallet::{AccountId, MemoryWalletDb};
use crate::{AccountId, MemoryWalletDb, NoteId};

impl InputSource for MemoryWalletDb {
type Error = crate::error::Error;
type AccountId = crate::mem_wallet::AccountId;
type NoteRef = crate::mem_wallet::NoteId;
type AccountId = AccountId;
type NoteRef = NoteId;

fn get_spendable_note(
&self,
txid: &zcash_primitives::transaction::TxId,
protocol: zcash_protocol::ShieldedProtocol,
index: u32,
_txid: &zcash_primitives::transaction::TxId,
_protocol: zcash_protocol::ShieldedProtocol,
_index: u32,
) -> Result<
Option<
zcash_client_backend::wallet::ReceivedNote<
Expand All @@ -26,11 +26,11 @@ impl InputSource for MemoryWalletDb {

fn select_spendable_notes(
&self,
account: Self::AccountId,
target_value: zcash_protocol::value::Zatoshis,
sources: &[zcash_protocol::ShieldedProtocol],
anchor_height: zcash_protocol::consensus::BlockHeight,
exclude: &[Self::NoteRef],
_account: Self::AccountId,
_target_value: zcash_protocol::value::Zatoshis,
_sources: &[zcash_protocol::ShieldedProtocol],
_anchor_height: zcash_protocol::consensus::BlockHeight,
_exclude: &[Self::NoteRef],
) -> Result<zcash_client_backend::data_api::SpendableNotes<Self::NoteRef>, Self::Error> {
todo!()
}
Expand Down
242 changes: 241 additions & 1 deletion zcash_client_memory/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,242 @@
use scanning::ScanQueue;

use shardtree::{store::memory::MemoryShardStore, ShardTree};
use std::{
collections::{hash_map::Entry, BTreeMap},
hash::Hash,
ops::Deref,
};
use subtle::ConditionallySelectable;
use zip32::fingerprint::SeedFingerprint;

use zcash_primitives::{
consensus::{BlockHeight, Network},
transaction::TxId,
};

use zcash_client_backend::{
data_api::{Account as _, AccountSource},
wallet::{NoteId, WalletSaplingOutput},
};

use zcash_client_backend::data_api::SAPLING_SHARD_HEIGHT;

#[cfg(feature = "orchard")]
use zcash_client_backend::{data_api::ORCHARD_SHARD_HEIGHT, wallet::WalletOrchardOutput};

use crate::error::Error;
mod error;
pub mod mem_wallet;
pub mod input_source;
pub mod types;
pub mod wallet_commitment_trees;
pub mod wallet_read;
pub mod wallet_write;
pub(crate) use types::*;

/// The ID type for accounts.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct AccountId(u32);

impl Deref for AccountId {
type Target = u32;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl ConditionallySelectable for AccountId {
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
AccountId(ConditionallySelectable::conditional_select(
&a.0, &b.0, choice,
))
}
}

/// The main in-memory wallet database. Implements all the traits needed to be used as a backend.
pub struct MemoryWalletDb {
network: Network,
accounts: Vec<Account>,
blocks: BTreeMap<BlockHeight, MemoryWalletBlock>,

tx_table: TransactionTable,

received_notes: ReceivedNoteTable,
receieved_note_spends: ReceievdNoteSpends,
nullifiers: NullifierMap,

tx_locator: TxLocatorMap,

scan_queue: ScanQueue,

sapling_tree: ShardTree<
MemoryShardStore<sapling::Node, BlockHeight>,
{ SAPLING_SHARD_HEIGHT * 2 },
SAPLING_SHARD_HEIGHT,
>,
#[cfg(feature = "orchard")]
orchard_tree: ShardTree<
MemoryShardStore<orchard::tree::MerkleHashOrchard, BlockHeight>,
{ ORCHARD_SHARD_HEIGHT * 2 },
ORCHARD_SHARD_HEIGHT,
>,
}

impl MemoryWalletDb {
pub fn new(network: Network, max_checkpoints: usize) -> Self {
Self {
network,
accounts: Vec::new(),
blocks: BTreeMap::new(),
sapling_tree: ShardTree::new(MemoryShardStore::empty(), max_checkpoints),
#[cfg(feature = "orchard")]
orchard_tree: ShardTree::new(MemoryShardStore::empty(), max_checkpoints),
tx_table: TransactionTable::new(),
received_notes: ReceivedNoteTable::new(),
nullifiers: NullifierMap::new(),
tx_locator: TxLocatorMap::new(),
receieved_note_spends: ReceievdNoteSpends::new(),
scan_queue: ScanQueue::new(),
}
}
pub(crate) fn mark_sapling_note_spent(
&mut self,
nf: sapling::Nullifier,
txid: TxId,
) -> Result<(), Error> {
let note_id = self
.received_notes
.0
.iter()
.filter(|v| v.nullifier() == Some(&Nullifier::Sapling(nf)))
.map(|v| v.note_id())
.next()
.ok_or_else(|| Error::NoteNotFound)?;
self.receieved_note_spends.insert_spend(note_id, txid);
Ok(())
}

pub(crate) fn get_account_mut(&mut self, account_id: AccountId) -> Option<&mut Account> {
self.accounts.get_mut(*account_id as usize)
}

#[cfg(feature = "orchard")]
pub(crate) fn mark_orchard_note_spent(
&mut self,
nf: orchard::note::Nullifier,
txid: TxId,
) -> Result<(), Error> {
let note_id = self
.received_notes
.0
.iter()
.filter(|v| v.nullifier() == Some(&Nullifier::Orchard(nf)))
.map(|v| v.note_id())
.next()
.ok_or_else(|| Error::NoteNotFound)?;
self.receieved_note_spends.insert_spend(note_id, txid);
Ok(())
}

pub(crate) fn max_zip32_account_index(
&self,
seed_fingerprint: &SeedFingerprint,
) -> Result<Option<zip32::AccountId>, Error> {
Ok(self
.accounts
.iter()
.filter_map(|a| match a.source() {
AccountSource::Derived {
seed_fingerprint: sf,
account_index,
} => {
if &sf == seed_fingerprint {
Some(account_index)
} else {
None
}
}
_ => None,
})
.max())
}
pub(crate) fn insert_received_sapling_note(
&mut self,
note_id: NoteId,
output: &WalletSaplingOutput<AccountId>,
spent_in: Option<TxId>,
) {
self.received_notes
.insert_received_note(ReceivedNote::from_wallet_sapling_output(note_id, output));
if let Some(spent_in) = spent_in {
self.receieved_note_spends.insert_spend(note_id, spent_in);
}
}
#[cfg(feature = "orchard")]
pub(crate) fn insert_received_orchard_note(
&mut self,
note_id: NoteId,
output: &WalletOrchardOutput<AccountId>,
spent_in: Option<TxId>,
) {
self.received_notes
.insert_received_note(ReceivedNote::from_wallet_orchard_output(note_id, output));
if let Some(spent_in) = spent_in {
self.receieved_note_spends.insert_spend(note_id, spent_in);
}
}
pub(crate) fn insert_sapling_nullifier_map(
&mut self,
block_height: BlockHeight,
new_entries: &[(TxId, u16, Vec<sapling::Nullifier>)],
) -> Result<(), Error> {
for (txid, tx_index, nullifiers) in new_entries {
match self.tx_locator.entry((block_height, *tx_index as u32)) {
Entry::Occupied(x) => {
if txid == x.get() {
// This is a duplicate entry
continue;
} else {
return Err(Error::ConflictingTxLocator);
}
}
Entry::Vacant(entry) => {
entry.insert(*txid);
}
}
for nf in nullifiers.iter() {
self.nullifiers
.insert(block_height, *tx_index as u32, Nullifier::Sapling(*nf));
}
}
Ok(())
}

#[cfg(feature = "orchard")]
pub(crate) fn insert_orchard_nullifier_map(
&mut self,
block_height: BlockHeight,
new_entries: &[(TxId, u16, Vec<orchard::note::Nullifier>)],
) -> Result<(), Error> {
for (txid, tx_index, nullifiers) in new_entries {
match self.tx_locator.entry((block_height, *tx_index as u32)) {
Entry::Occupied(x) => {
if txid == x.get() {
// This is a duplicate entry
continue;
} else {
return Err(Error::ConflictingTxLocator);
}
}
Entry::Vacant(entry) => {
entry.insert(*txid);
}
}
for nf in nullifiers.iter() {
self.nullifiers
.insert(block_height, *tx_index as u32, Nullifier::Orchard(*nf));
}
}
Ok(())
}
}
Loading

0 comments on commit d50429d

Please sign in to comment.