Compare commits
26 Commits
b268ff233b
...
71aa921704
Author | SHA1 | Date |
---|---|---|
Kris Nuttycombe | 71aa921704 | |
Kris Nuttycombe | 5d2cd6ff46 | |
Kris Nuttycombe | 21313a253f | |
Alfredo Garcia | c9a236e90e | |
Alfredo Garcia | cbb49b4686 | |
Alfredo Garcia | 147c033f6a | |
Alfredo Garcia | f77dfc1ee3 | |
Alfredo Garcia | 588156f263 | |
Alfredo Garcia | 2f28306c61 | |
Alfredo Garcia | 23a2ae8fc6 | |
Alfredo Garcia | b1a0fcc697 | |
Alfredo Garcia | 370a4aa7e5 | |
Alfredo Garcia | a3b4de0459 | |
Alfredo Garcia | a4323c2d60 | |
Alfredo Garcia | 0393a69af5 | |
Alfredo Garcia | 7f746212e1 | |
Alfredo Garcia | 4d3c441c9a | |
Alfredo Garcia | 9c0c6b079a | |
Alfredo Garcia | 09e8bc9177 | |
Alfredo Garcia | 69c1e6815d | |
Kris Nuttycombe | fc757b193a | |
Alfredo Garcia | b39765cc1a | |
Kris Nuttycombe | 5ef5f043f5 | |
Kris Nuttycombe | 99260577a4 | |
Kris Nuttycombe | d013322aa1 | |
Kris Nuttycombe | 64c7514941 |
|
@ -115,18 +115,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.78"
|
||||
version = "0.1.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85"
|
||||
checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -791,7 +791,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1626,7 +1626,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1721,7 +1721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1736,9 +1736,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.79"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -1790,7 +1790,7 @@ dependencies = [
|
|||
"prost",
|
||||
"prost-types",
|
||||
"regex",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
"tempfile",
|
||||
"which",
|
||||
]
|
||||
|
@ -1805,7 +1805,7 @@ dependencies = [
|
|||
"itertools 0.11.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1834,9 +1834,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -2233,7 +2233,7 @@ checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2260,9 +2260,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shardtree"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d766257c56a1bdd75479c256b97c92e72788a9afb18b5199f58faf7188dc99d9"
|
||||
checksum = "3b3cdd24424ce0b381646737fedddc33c4dcf7dcd2d545056b53f7982097bef5"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"bitflags 2.4.1",
|
||||
|
@ -2379,9 +2379,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.53"
|
||||
version = "2.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2436,7 +2436,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2579,7 +2579,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"prost-build",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2633,7 +2633,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2835,7 +2835,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -2857,7 +2857,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -3294,7 +3294,7 @@ checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3314,7 +3314,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.53",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3327,3 +3327,8 @@ dependencies = [
|
|||
"memuse",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[patch.unused]]
|
||||
name = "sapling-crypto"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/nuttycom/sapling-crypto?rev=827534329f3ff61f9212f7933ed1688fa04e987c#827534329f3ff61f9212f7933ed1688fa04e987c"
|
||||
|
|
|
@ -16,7 +16,7 @@ members = [
|
|||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
rust-version = "1.69"
|
||||
repository = "https://github.com/zcash/librustzcash"
|
||||
license = "MIT OR Apache-2.0"
|
||||
categories = ["cryptography::cryptocurrencies"]
|
||||
|
|
|
@ -25,7 +25,7 @@ pub mod memo;
|
|||
pub mod value;
|
||||
|
||||
/// A Zcash shielded transfer protocol.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum ShieldedProtocol {
|
||||
/// The Sapling protocol
|
||||
Sapling,
|
||||
|
@ -34,7 +34,7 @@ pub enum ShieldedProtocol {
|
|||
}
|
||||
|
||||
/// A value pool in the Zcash protocol.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PoolType {
|
||||
/// The transparent value pool
|
||||
Transparent,
|
||||
|
|
|
@ -144,9 +144,10 @@ impl Deref for TextMemo {
|
|||
}
|
||||
|
||||
/// An unencrypted memo received alongside a shielded note in a Zcash transaction.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub enum Memo {
|
||||
/// An empty memo field.
|
||||
#[default]
|
||||
Empty,
|
||||
/// A memo field containing a UTF-8 string.
|
||||
Text(TextMemo),
|
||||
|
@ -171,12 +172,6 @@ impl fmt::Debug for Memo {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Memo {
|
||||
fn default() -> Self {
|
||||
Memo::Empty
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Memo {
|
||||
fn eq(&self, rhs: &Memo) -> bool {
|
||||
match (self, rhs) {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "1.65.0"
|
||||
channel = "1.69.0"
|
||||
components = [ "clippy", "rustfmt" ]
|
||||
|
|
|
@ -107,6 +107,9 @@ pub mod error;
|
|||
pub mod scanning;
|
||||
pub mod wallet;
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod mem_wallet;
|
||||
|
||||
/// The height of subtree roots in the Sapling note commitment tree.
|
||||
///
|
||||
/// This conforms to the structure of subtree data returned by
|
||||
|
|
|
@ -0,0 +1,623 @@
|
|||
#![allow(unused)]
|
||||
use incrementalmerkletree::{Address, Retention};
|
||||
use sapling::NullifierDerivingKey;
|
||||
use secrecy::{ExposeSecret, SecretVec};
|
||||
use shardtree::{error::ShardTreeError, store::memory::MemoryShardStore, ShardTree};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
convert::Infallible,
|
||||
hash::Hash,
|
||||
num::NonZeroU32,
|
||||
};
|
||||
use zcash_keys::keys::{AddressGenerationError, DerivationError};
|
||||
use zip32::{fingerprint::SeedFingerprint, DiversifierIndex, Scope};
|
||||
|
||||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
consensus::{BlockHeight, Network},
|
||||
transaction::{Transaction, TxId},
|
||||
zip32::AccountId,
|
||||
};
|
||||
use zcash_protocol::{
|
||||
memo::{self, Memo, MemoBytes},
|
||||
value::Zatoshis,
|
||||
ShieldedProtocol::{Orchard, Sapling},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
address::UnifiedAddress,
|
||||
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||
wallet::{NoteId, WalletSpend, WalletTransparentOutput, WalletTx},
|
||||
};
|
||||
|
||||
use super::{
|
||||
chain::CommitmentTreeRoot, scanning::ScanRange, Account, AccountBirthday, BlockMetadata,
|
||||
DecryptedTransaction, NullifierQuery, ScannedBlock, SentTransaction, WalletCommitmentTrees,
|
||||
WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {crate::wallet::TransparentAddressMetadata, zcash_primitives::legacy::TransparentAddress};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
use super::ORCHARD_SHARD_HEIGHT;
|
||||
|
||||
struct MemoryWalletBlock {
|
||||
height: BlockHeight,
|
||||
hash: BlockHash,
|
||||
block_time: u32,
|
||||
// Just the transactions that involve an account in this wallet
|
||||
transactions: HashMap<TxId, WalletTx<u32>>,
|
||||
memos: HashMap<NoteId, MemoBytes>,
|
||||
}
|
||||
|
||||
impl PartialEq for MemoryWalletBlock {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
(self.height, self.block_time) == (other.height, other.block_time)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MemoryWalletBlock {}
|
||||
|
||||
impl PartialOrd for MemoryWalletBlock {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some((self.height, self.block_time).cmp(&(other.height, other.block_time)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for MemoryWalletBlock {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
(self.height, self.block_time).cmp(&(other.height, other.block_time))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoryWalletAccount {
|
||||
seed_fingerprint: SeedFingerprint,
|
||||
account_id: AccountId,
|
||||
ufvk: UnifiedFullViewingKey,
|
||||
birthday: AccountBirthday,
|
||||
addresses: BTreeMap<DiversifierIndex, UnifiedAddressRequest>,
|
||||
notes: HashSet<NoteId>,
|
||||
}
|
||||
|
||||
pub struct MemoryWalletDb {
|
||||
network: Network,
|
||||
accounts: BTreeMap<u32, MemoryWalletAccount>,
|
||||
blocks: BTreeMap<BlockHeight, MemoryWalletBlock>,
|
||||
tx_idx: HashMap<TxId, BlockHeight>,
|
||||
sapling_spends: BTreeMap<sapling::Nullifier, (TxId, bool)>,
|
||||
#[cfg(feature = "orchard")]
|
||||
orchard_spends: BTreeMap<orchard::note::Nullifier, (TxId, bool)>,
|
||||
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: BTreeMap::new(),
|
||||
blocks: BTreeMap::new(),
|
||||
tx_idx: HashMap::new(),
|
||||
sapling_spends: BTreeMap::new(),
|
||||
#[cfg(feature = "orchard")]
|
||||
orchard_spends: BTreeMap::new(),
|
||||
sapling_tree: ShardTree::new(MemoryShardStore::empty(), max_checkpoints),
|
||||
#[cfg(feature = "orchard")]
|
||||
orchard_tree: ShardTree::new(MemoryShardStore::empty(), max_checkpoints),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
AccountUnknown(u32),
|
||||
ViewingKeyNotFound(u32),
|
||||
MemoDecryption(memo::Error),
|
||||
KeyDerivation(DerivationError),
|
||||
AddressGeneration(AddressGenerationError),
|
||||
}
|
||||
|
||||
impl From<DerivationError> for Error {
|
||||
fn from(value: DerivationError) -> Self {
|
||||
Error::KeyDerivation(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddressGenerationError> for Error {
|
||||
fn from(value: AddressGenerationError) -> Self {
|
||||
Error::AddressGeneration(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<memo::Error> for Error {
|
||||
fn from(value: memo::Error) -> Self {
|
||||
Error::MemoDecryption(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletRead for MemoryWalletDb {
|
||||
type Error = Error;
|
||||
type AccountId = u32;
|
||||
type Account = (u32, UnifiedFullViewingKey);
|
||||
|
||||
fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, Self::Error> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn get_account(
|
||||
&self,
|
||||
_account_id: Self::AccountId,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_derived_account(
|
||||
&self,
|
||||
_seed: &SeedFingerprint,
|
||||
_account_id: zip32::AccountId,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn validate_seed(
|
||||
&self,
|
||||
_account_id: Self::AccountId,
|
||||
_seed: &SecretVec<u8>,
|
||||
) -> Result<bool, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn seed_relevance_to_derived_accounts(
|
||||
&self,
|
||||
seed: &SecretVec<u8>,
|
||||
) -> Result<super::SeedRelevance<Self::AccountId>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_account_for_ufvk(
|
||||
&self,
|
||||
ufvk: &UnifiedFullViewingKey,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
let ufvk_req =
|
||||
UnifiedAddressRequest::all().expect("At least one protocol should be enabled");
|
||||
Ok(self.accounts.iter().find_map(|(id, acct)| {
|
||||
if acct.ufvk.default_address(ufvk_req).unwrap()
|
||||
== ufvk.default_address(ufvk_req).unwrap()
|
||||
{
|
||||
Some((*id, acct.ufvk.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn get_current_address(
|
||||
&self,
|
||||
account: Self::AccountId,
|
||||
) -> Result<Option<UnifiedAddress>, Self::Error> {
|
||||
self.accounts
|
||||
.get(&account)
|
||||
.map(|account| {
|
||||
account
|
||||
.ufvk
|
||||
.default_address(
|
||||
UnifiedAddressRequest::all()
|
||||
.expect("At least one protocol should be enabled."),
|
||||
)
|
||||
.map(|(addr, _)| addr)
|
||||
})
|
||||
.transpose()
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_account_birthday(&self, _account: Self::AccountId) -> Result<BlockHeight, Self::Error> {
|
||||
Err(Error::AccountUnknown(_account))
|
||||
}
|
||||
|
||||
fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_wallet_summary(
|
||||
&self,
|
||||
_min_confirmations: u32,
|
||||
) -> Result<Option<WalletSummary<Self::AccountId>>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error> {
|
||||
Ok(self.blocks.iter().find_map(|b| {
|
||||
if b.0 == &block_height {
|
||||
Some(b.1.hash)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn block_metadata(&self, _height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn block_fully_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn block_max_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn suggest_scan_ranges(&self) -> Result<Vec<ScanRange>, Self::Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn get_target_and_anchor_heights(
|
||||
&self,
|
||||
_min_confirmations: NonZeroU32,
|
||||
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_tx_height(&self, _txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_unified_full_viewing_keys(
|
||||
&self,
|
||||
) -> Result<HashMap<Self::AccountId, UnifiedFullViewingKey>, Self::Error> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
fn get_memo(&self, id_note: NoteId) -> Result<Option<Memo>, Self::Error> {
|
||||
self.tx_idx
|
||||
.get(id_note.txid())
|
||||
.and_then(|height| self.blocks.get(height))
|
||||
.and_then(|block| block.memos.get(&id_note))
|
||||
.map(Memo::try_from)
|
||||
.transpose()
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
fn get_transaction(&self, _id_tx: TxId) -> Result<Option<Transaction>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_sapling_nullifiers(
|
||||
&self,
|
||||
_query: NullifierQuery,
|
||||
) -> Result<Vec<(Self::AccountId, sapling::Nullifier)>, Self::Error> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
fn get_orchard_nullifiers(
|
||||
&self,
|
||||
_query: NullifierQuery,
|
||||
) -> Result<Vec<(Self::AccountId, orchard::note::Nullifier)>, Self::Error> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn get_transparent_receivers(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, Self::Error> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn get_transparent_balances(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
_max_height: BlockHeight,
|
||||
) -> Result<HashMap<TransparentAddress, Zatoshis>, Self::Error> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletWrite for MemoryWalletDb {
|
||||
type UtxoRef = u32;
|
||||
|
||||
fn create_account(
|
||||
&mut self,
|
||||
seed: &SecretVec<u8>,
|
||||
birthday: &AccountBirthday,
|
||||
) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error> {
|
||||
let seed_fingerprint =
|
||||
SeedFingerprint::from_seed(seed.expose_secret()).expect("Valid seed.");
|
||||
let account_id = self.accounts.last_key_value().map_or(0, |(id, _)| id + 1);
|
||||
let account_index = AccountId::try_from(account_id).unwrap();
|
||||
let usk =
|
||||
UnifiedSpendingKey::from_seed(&self.network, seed.expose_secret(), account_index)?;
|
||||
let ufvk = usk.to_unified_full_viewing_key();
|
||||
self.accounts.insert(
|
||||
account_id,
|
||||
MemoryWalletAccount {
|
||||
seed_fingerprint,
|
||||
account_id: account_index,
|
||||
ufvk,
|
||||
birthday: birthday.clone(),
|
||||
addresses: BTreeMap::new(),
|
||||
notes: HashSet::new(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok((account_id, usk))
|
||||
}
|
||||
|
||||
fn get_next_available_address(
|
||||
&mut self,
|
||||
_account: Self::AccountId,
|
||||
_request: UnifiedAddressRequest,
|
||||
) -> Result<Option<UnifiedAddress>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn update_chain_tip(&mut self, _tip_height: BlockHeight) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Adds a sequence of blocks to the data store.
|
||||
///
|
||||
/// Assumes blocks will be here in order.
|
||||
fn put_blocks(
|
||||
&mut self,
|
||||
from_state: &super::chain::ChainState,
|
||||
blocks: Vec<ScannedBlock<Self::AccountId>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
// TODO:
|
||||
// - Make sure blocks are coming in order.
|
||||
// - Make sure the first block in the sequence is tip + 1?
|
||||
// - Add a check to make sure the blocks are not already in the data store.
|
||||
for block in blocks.into_iter() {
|
||||
let mut transactions = HashMap::new();
|
||||
let mut memos = HashMap::new();
|
||||
for transaction in block.transactions().iter() {
|
||||
let txid = transaction.txid();
|
||||
for account_id in self.get_account_ids()? {
|
||||
let ufvk = self
|
||||
.get_account(account_id)?
|
||||
.ok_or(Error::AccountUnknown(account_id))?
|
||||
.ufvk()
|
||||
.ok_or(Error::ViewingKeyNotFound(account_id))?
|
||||
.clone();
|
||||
let dfvk = ufvk
|
||||
.sapling()
|
||||
.ok_or(Error::ViewingKeyNotFound(account_id))?;
|
||||
let nk = dfvk.to_nk(Scope::External);
|
||||
|
||||
transaction.sapling_outputs().iter().map(|o| {
|
||||
// Insert the Sapling nullifiers of the spent notes into the `sapling_spends` map.
|
||||
let nullifier = o.note().nf(&nk, o.note_commitment_tree_position().into());
|
||||
self.sapling_spends
|
||||
.entry(nullifier)
|
||||
.or_insert((txid, false));
|
||||
|
||||
// Insert the memo into the `memos` map.
|
||||
let note_id = NoteId::new(
|
||||
txid,
|
||||
Sapling,
|
||||
u16::try_from(o.index())
|
||||
.expect("output indices are representable as u16"),
|
||||
);
|
||||
if let Ok(Some(memo)) = self.get_memo(note_id) {
|
||||
memos.insert(note_id, memo.encode());
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
transaction.orchard_outputs().iter().map(|o| {
|
||||
// Insert the Orchard nullifiers of the spent notes into the `orchard_spends` map.
|
||||
if let Some(nullifier) = o.nf() {
|
||||
self.orchard_spends
|
||||
.entry(*nullifier)
|
||||
.or_insert((txid, false));
|
||||
}
|
||||
|
||||
// Insert the memo into the `memos` map.
|
||||
let note_id = NoteId::new(
|
||||
txid,
|
||||
Orchard,
|
||||
u16::try_from(o.index())
|
||||
.expect("output indices are representable as u16"),
|
||||
);
|
||||
if let Ok(Some(memo)) = self.get_memo(note_id) {
|
||||
memos.insert(note_id, memo.encode());
|
||||
}
|
||||
});
|
||||
|
||||
// Add frontier to the sapling tree
|
||||
self.sapling_tree.insert_frontier(
|
||||
from_state.final_sapling_tree().clone(),
|
||||
Retention::Checkpoint {
|
||||
id: from_state.block_height(),
|
||||
is_marked: false,
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
// Add frontier to the orchard tree
|
||||
self.orchard_tree.insert_frontier(
|
||||
from_state.final_orchard_tree().clone(),
|
||||
Retention::Checkpoint {
|
||||
id: from_state.block_height(),
|
||||
is_marked: false,
|
||||
},
|
||||
);
|
||||
|
||||
// Mark the Sapling nullifiers of the spent notes as spent in the `sapling_spends` map.
|
||||
transaction.sapling_spends().iter().map(|s| {
|
||||
let nullifier = s.nf();
|
||||
if let Some((txid, spent)) = self.sapling_spends.get_mut(nullifier) {
|
||||
*spent = true;
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
// Mark the Orchard nullifiers of the spent notes as spent in the `orchard_spends` map.
|
||||
transaction.orchard_spends().iter().map(|s| {
|
||||
let nullifier = s.nf();
|
||||
if let Some((txid, spent)) = self.orchard_spends.get_mut(nullifier) {
|
||||
*spent = true;
|
||||
}
|
||||
});
|
||||
|
||||
self.tx_idx.insert(txid, block.block_height);
|
||||
transactions.insert(txid, transaction.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let memory_block = MemoryWalletBlock {
|
||||
height: block.block_height,
|
||||
hash: block.block_hash,
|
||||
block_time: block.block_time,
|
||||
transactions,
|
||||
memos,
|
||||
};
|
||||
|
||||
self.blocks.insert(block.block_height, memory_block);
|
||||
|
||||
// Add the Sapling commitments to the sapling tree.
|
||||
let block_commitments = block.into_commitments();
|
||||
let start_position = from_state
|
||||
.final_sapling_tree()
|
||||
.value()
|
||||
.map_or(0.into(), |t| t.position() + 1);
|
||||
self.sapling_tree
|
||||
.batch_insert(start_position, block_commitments.sapling.into_iter());
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
{
|
||||
// Add the Orchard commitments to the orchard tree.
|
||||
let start_position = from_state
|
||||
.final_orchard_tree()
|
||||
.value()
|
||||
.map_or(0.into(), |t| t.position() + 1);
|
||||
self.orchard_tree
|
||||
.batch_insert(start_position, block_commitments.orchard.into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a transparent UTXO received by the wallet to the data store.
|
||||
fn put_received_transparent_utxo(
|
||||
&mut self,
|
||||
_output: &WalletTransparentOutput,
|
||||
) -> Result<Self::UtxoRef, Self::Error> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn store_decrypted_tx(
|
||||
&mut self,
|
||||
_received_tx: DecryptedTransaction<Self::AccountId>,
|
||||
) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn store_sent_tx(
|
||||
&mut self,
|
||||
_sent_tx: &SentTransaction<Self::AccountId>,
|
||||
) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn truncate_to_height(&mut self, _block_height: BlockHeight) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletCommitmentTrees for MemoryWalletDb {
|
||||
type Error = Infallible;
|
||||
type SaplingShardStore<'a> = MemoryShardStore<sapling::Node, BlockHeight>;
|
||||
|
||||
fn with_sapling_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
|
||||
where
|
||||
for<'a> F: FnMut(
|
||||
&'a mut ShardTree<
|
||||
Self::SaplingShardStore<'a>,
|
||||
{ sapling::NOTE_COMMITMENT_TREE_DEPTH },
|
||||
SAPLING_SHARD_HEIGHT,
|
||||
>,
|
||||
) -> Result<A, E>,
|
||||
E: From<ShardTreeError<Infallible>>,
|
||||
{
|
||||
callback(&mut self.sapling_tree)
|
||||
}
|
||||
|
||||
fn put_sapling_subtree_roots(
|
||||
&mut self,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<sapling::Node>],
|
||||
) -> Result<(), ShardTreeError<Self::Error>> {
|
||||
self.with_sapling_tree_mut(|t| {
|
||||
for (root, i) in roots.iter().zip(0u64..) {
|
||||
let root_addr = Address::from_parts(SAPLING_SHARD_HEIGHT.into(), start_index + i);
|
||||
t.insert(root_addr, *root.root_hash())?;
|
||||
}
|
||||
Ok::<_, ShardTreeError<Self::Error>>(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
type OrchardShardStore<'a> = MemoryShardStore<orchard::tree::MerkleHashOrchard, BlockHeight>;
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
|
||||
where
|
||||
for<'a> F: FnMut(
|
||||
&'a mut ShardTree<
|
||||
Self::OrchardShardStore<'a>,
|
||||
{ ORCHARD_SHARD_HEIGHT * 2 },
|
||||
ORCHARD_SHARD_HEIGHT,
|
||||
>,
|
||||
) -> Result<A, E>,
|
||||
E: From<ShardTreeError<Self::Error>>,
|
||||
{
|
||||
callback(&mut self.orchard_tree)
|
||||
}
|
||||
|
||||
/// Adds a sequence of note commitment tree subtree roots to the data store.
|
||||
#[cfg(feature = "orchard")]
|
||||
fn put_orchard_subtree_roots(
|
||||
&mut self,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
|
||||
) -> Result<(), ShardTreeError<Self::Error>> {
|
||||
self.with_orchard_tree_mut(|t| {
|
||||
for (root, i) in roots.iter().zip(0u64..) {
|
||||
let root_addr = Address::from_parts(ORCHARD_SHARD_HEIGHT.into(), start_index + i);
|
||||
t.insert(root_addr, *root.root_hash())?;
|
||||
}
|
||||
Ok::<_, ShardTreeError<Self::Error>>(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ use crate::fees::orchard as orchard_fees;
|
|||
use zcash_primitives::legacy::keys::{NonHardenedChildIndex, TransparentKeyScope};
|
||||
|
||||
/// A unique identifier for a shielded transaction output
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct NoteId {
|
||||
txid: TxId,
|
||||
protocol: ShieldedProtocol,
|
||||
|
@ -119,6 +119,7 @@ impl<AccountId, N> Recipient<AccountId, Option<N>> {
|
|||
/// The shielded subset of a [`Transaction`]'s data that is relevant to a particular wallet.
|
||||
///
|
||||
/// [`Transaction`]: zcash_primitives::transaction::Transaction
|
||||
#[derive(Clone)]
|
||||
pub struct WalletTx<AccountId> {
|
||||
txid: TxId,
|
||||
block_index: usize,
|
||||
|
@ -248,6 +249,7 @@ impl transparent_fees::InputView for WalletTransparentOutput {
|
|||
}
|
||||
|
||||
/// A reference to a spent note belonging to the wallet within a transaction.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletSpend<Nf, AccountId> {
|
||||
index: usize,
|
||||
nf: Nf,
|
||||
|
@ -287,6 +289,7 @@ pub type WalletSaplingSpend<AccountId> = WalletSpend<sapling::Nullifier, Account
|
|||
pub type WalletOrchardSpend<AccountId> = WalletSpend<orchard::note::Nullifier, AccountId>;
|
||||
|
||||
/// An output that was successfully decrypted in the process of wallet scanning.
|
||||
#[derive(Clone)]
|
||||
pub struct WalletOutput<Note, Nullifier, AccountId> {
|
||||
index: usize,
|
||||
ephemeral_key: EphemeralKeyBytes,
|
||||
|
|
|
@ -684,8 +684,8 @@ mod parse {
|
|||
)(input)
|
||||
}
|
||||
|
||||
fn to_indexed_param<'a, P: consensus::Parameters>(
|
||||
params: &'a P,
|
||||
fn to_indexed_param<P: consensus::Parameters>(
|
||||
params: &P,
|
||||
((name, iopt), value): ((&str, Option<&str>), &str),
|
||||
) -> Result<IndexedParam, String> {
|
||||
let param = match name {
|
||||
|
|
|
@ -71,7 +71,7 @@ use zcash_primitives::{
|
|||
consensus::{self, BlockHeight},
|
||||
memo::{Memo, MemoBytes},
|
||||
transaction::{components::amount::NonNegativeAmount, Transaction, TxId},
|
||||
zip32::{self, DiversifierIndex, Scope},
|
||||
zip32::{self, DiversifierIndex},
|
||||
};
|
||||
use zip32::fingerprint::SeedFingerprint;
|
||||
|
||||
|
@ -686,7 +686,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
let spent_in = output
|
||||
.nf()
|
||||
.map(|nf| {
|
||||
wallet::query_nullifier_map::<_, Scope>(
|
||||
wallet::query_nullifier_map(
|
||||
wdb.conn.0,
|
||||
ShieldedProtocol::Sapling,
|
||||
nf,
|
||||
|
@ -704,7 +704,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
let spent_in = output
|
||||
.nf()
|
||||
.map(|nf| {
|
||||
wallet::query_nullifier_map::<_, Scope>(
|
||||
wallet::query_nullifier_map(
|
||||
wdb.conn.0,
|
||||
ShieldedProtocol::Orchard,
|
||||
&nf.to_bytes(),
|
||||
|
|
|
@ -2722,7 +2722,7 @@ pub(crate) fn insert_nullifier_map<N: AsRef<[u8]>>(
|
|||
|
||||
/// Returns the row of the `transactions` table corresponding to the transaction in which
|
||||
/// this nullifier is revealed, if any.
|
||||
pub(crate) fn query_nullifier_map<N: AsRef<[u8]>, S>(
|
||||
pub(crate) fn query_nullifier_map<N: AsRef<[u8]>>(
|
||||
conn: &rusqlite::Transaction<'_>,
|
||||
spend_pool: ShieldedProtocol,
|
||||
nf: &N,
|
||||
|
@ -2902,7 +2902,7 @@ mod tests {
|
|||
height_1,
|
||||
&[]
|
||||
).as_deref(),
|
||||
Ok(&[ref ret]) if (ret.outpoint(), ret.txout(), ret.height()) == (utxo.outpoint(), utxo.txout(), height_1)
|
||||
Ok([ret]) if (ret.outpoint(), ret.txout(), ret.height()) == (utxo.outpoint(), utxo.txout(), height_1)
|
||||
);
|
||||
assert_matches!(
|
||||
st.wallet().get_unspent_transparent_output(utxo.outpoint()),
|
||||
|
@ -2935,7 +2935,7 @@ mod tests {
|
|||
st.wallet()
|
||||
.get_unspent_transparent_outputs(taddr, height_2, &[])
|
||||
.as_deref(),
|
||||
Ok(&[ref ret]) if (ret.outpoint(), ret.txout(), ret.height()) == (utxo.outpoint(), utxo.txout(), height_2)
|
||||
Ok([ret]) if (ret.outpoint(), ret.txout(), ret.height()) == (utxo.outpoint(), utxo.txout(), height_2)
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
|
|
|
@ -1209,7 +1209,6 @@ mod tests {
|
|||
|
||||
// introduce some roots
|
||||
let roots = (0u32..4)
|
||||
.into_iter()
|
||||
.map(|idx| {
|
||||
CommitmentTreeRoot::from_parts(
|
||||
BlockHeight::from((idx + 1) * 3),
|
||||
|
@ -1228,7 +1227,7 @@ mod tests {
|
|||
let checkpoint_height = BlockHeight::from(3);
|
||||
tree.batch_insert(
|
||||
Position::from(24),
|
||||
('a'..='h').into_iter().map(|c| {
|
||||
('a'..='h').map(|c| {
|
||||
(
|
||||
c.to_string(),
|
||||
match c {
|
||||
|
|
|
@ -76,13 +76,12 @@ pub trait TransparentAuthorizingContext: transparent::Authorization {
|
|||
/// set of precomputed hashes produced in the construction of the
|
||||
/// transaction ID.
|
||||
pub fn signature_hash<
|
||||
'a,
|
||||
TA: TransparentAuthorizingContext,
|
||||
SA: sapling::bundle::Authorization<SpendProof = GrothProofBytes, OutputProof = GrothProofBytes>,
|
||||
A: Authorization<SaplingAuth = SA, TransparentAuth = TA>,
|
||||
>(
|
||||
tx: &TransactionData<A>,
|
||||
signable_input: &SignableInput<'a>,
|
||||
signable_input: &SignableInput<'_>,
|
||||
txid_parts: &TxDigests<Blake2bHash>,
|
||||
) -> SignatureHash {
|
||||
SignatureHash(match tx.version {
|
||||
|
|
|
@ -66,7 +66,7 @@ impl InputNote {
|
|||
let lhs = cur;
|
||||
let rhs = witness_u256(
|
||||
cs.namespace(|| "sibling"),
|
||||
layer.as_ref().map(|&(ref sibling, _)| &sibling[..]),
|
||||
layer.as_ref().map(|(sibling, _)| &sibling[..]),
|
||||
)?;
|
||||
|
||||
// Conditionally swap if cur is right
|
||||
|
|
Loading…
Reference in New Issue