Add GetFilteredNotes to Orchard wallet.
This commit is contained in:
parent
6587b2ed86
commit
4c53499f11
|
@ -523,6 +523,16 @@ public:
|
||||||
std::string ToString() const;
|
std::string ToString() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** An outpoint - a combination of a transaction hash and an index n into its orchard
|
||||||
|
* actions */
|
||||||
|
class OrchardOutPoint : public BaseOutPoint
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OrchardOutPoint() : BaseOutPoint() {};
|
||||||
|
OrchardOutPoint(uint256 hashIn, uint32_t nIn) : BaseOutPoint(hashIn, nIn) {};
|
||||||
|
std::string ToString() const;
|
||||||
|
};
|
||||||
|
|
||||||
/** An input of a transaction. It contains the location of the previous
|
/** An input of a transaction. It contains the location of the previous
|
||||||
* transaction's output that it claims and a signature that matches the
|
* transaction's output that it claims and a signature that matches the
|
||||||
* output's public key.
|
* output's public key.
|
||||||
|
|
|
@ -32,6 +32,15 @@ OrchardRawAddressPtr* orchard_address_clone(
|
||||||
*/
|
*/
|
||||||
void orchard_address_free(OrchardRawAddressPtr* ptr);
|
void orchard_address_free(OrchardRawAddressPtr* ptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the "less than" operation `k0 < k1` for comparing two Orchard
|
||||||
|
* addresses. This is a comparison of the raw bytes, only useful for cases
|
||||||
|
* where a semantically irrelevant ordering is needed (such as for map keys.)
|
||||||
|
*/
|
||||||
|
bool orchard_address_lt(
|
||||||
|
const OrchardRawAddressPtr* k0,
|
||||||
|
const OrchardRawAddressPtr* k1);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Incoming Viewing Keys
|
// Incoming Viewing Keys
|
||||||
//
|
//
|
||||||
|
|
|
@ -106,6 +106,51 @@ bool orchard_wallet_add_raw_address(
|
||||||
const OrchardRawAddressPtr* addr,
|
const OrchardRawAddressPtr* addr,
|
||||||
const OrchardIncomingViewingKeyPtr* ivk);
|
const OrchardIncomingViewingKeyPtr* ivk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pointer to the Orchard incoming viewing key
|
||||||
|
* corresponding to the specified raw address, if it is
|
||||||
|
* known to the wallet, or `nullptr` otherwise.
|
||||||
|
*/
|
||||||
|
OrchardIncomingViewingKeyPtr* orchard_wallet_get_ivk_for_address(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const OrchardRawAddressPtr* addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A C struct used to transfer note metadata information across the Rust FFI
|
||||||
|
* boundary. This must have the same in-memory representation as the
|
||||||
|
* `NoteMetadata` type in orchard_ffi/wallet.rs.
|
||||||
|
*/
|
||||||
|
struct RawOrchardNoteMetadata {
|
||||||
|
unsigned char txid[32];
|
||||||
|
uint32_t actionIdx;
|
||||||
|
OrchardRawAddressPtr* addr;
|
||||||
|
CAmount noteValue;
|
||||||
|
unsigned char memo[512];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (*push_callback_t)(void* resultVector, const RawOrchardNoteMetadata noteMeta);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds notes that belong to the wallet that were sent to addresses derived
|
||||||
|
* from the specified incoming viewing key, subject to the specified flags, and
|
||||||
|
* uses the provided callback to push RawOrchardNoteMetadata values
|
||||||
|
* corresponding to those notes on to the provided result vector. Note that
|
||||||
|
* the push_cb callback can perform any necessary conversion from a
|
||||||
|
* RawOrchardNoteMetadata value prior in addition to modifying the provided
|
||||||
|
* result vector.
|
||||||
|
*
|
||||||
|
* If `ivk` is null, all notes belonging to the wallet will be returned.
|
||||||
|
*/
|
||||||
|
void orchard_wallet_get_filtered_notes(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const OrchardIncomingViewingKeyPtr* ivk,
|
||||||
|
bool ignoreSpent,
|
||||||
|
bool ignoreLocked,
|
||||||
|
bool requireSpendingKey,
|
||||||
|
void* resultVector,
|
||||||
|
push_callback_t push_cb
|
||||||
|
);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -5,7 +5,10 @@ use tracing::error;
|
||||||
use orchard::keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, SpendingKey};
|
use orchard::keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, SpendingKey};
|
||||||
use orchard::Address;
|
use orchard::Address;
|
||||||
|
|
||||||
use crate::streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb};
|
use crate::{
|
||||||
|
streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb},
|
||||||
|
zcashd_orchard::OrderedAddress,
|
||||||
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Addresses
|
// Addresses
|
||||||
|
@ -25,6 +28,13 @@ pub extern "C" fn orchard_address_free(addr: *mut Address) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_address_lt(a0: *const Address, a1: *const Address) -> bool {
|
||||||
|
let a0 = unsafe { a0.as_ref() };
|
||||||
|
let a1 = unsafe { a1.as_ref() };
|
||||||
|
a0.map(|a| OrderedAddress::new(*a)) < a1.map(|a| OrderedAddress::new(*a))
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Incoming viewing keys
|
// Incoming viewing keys
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Tree};
|
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Tree};
|
||||||
use libc::c_uchar;
|
use libc::c_uchar;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
|
@ -29,6 +29,12 @@ pub struct LastObserved {
|
||||||
block_tx_idx: usize,
|
block_tx_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct OutPoint {
|
||||||
|
txid: TxId,
|
||||||
|
action_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DecryptedNote {
|
pub struct DecryptedNote {
|
||||||
note: Note,
|
note: Note,
|
||||||
|
@ -36,9 +42,20 @@ pub struct DecryptedNote {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TxNotes {
|
struct TxNotes {
|
||||||
|
txid: TxId,
|
||||||
decrypted_notes: BTreeMap<usize, DecryptedNote>,
|
decrypted_notes: BTreeMap<usize, DecryptedNote>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type used to pass note metadata across the FFI boundary
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct NoteMetadata {
|
||||||
|
txid: [u8; 32],
|
||||||
|
action_idx: u32,
|
||||||
|
recipient: *const Address,
|
||||||
|
note_value: i64,
|
||||||
|
memo: [u8; 512],
|
||||||
|
}
|
||||||
|
|
||||||
struct KeyStore {
|
struct KeyStore {
|
||||||
payment_addresses: BTreeMap<OrderedAddress, IncomingViewingKey>,
|
payment_addresses: BTreeMap<OrderedAddress, IncomingViewingKey>,
|
||||||
viewing_keys: BTreeMap<IncomingViewingKey, FullViewingKey>,
|
viewing_keys: BTreeMap<IncomingViewingKey, FullViewingKey>,
|
||||||
|
@ -73,6 +90,16 @@ impl KeyStore {
|
||||||
.insert(OrderedAddress::new(addr), ivk);
|
.insert(OrderedAddress::new(addr), ivk);
|
||||||
has_fvk
|
has_fvk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spending_key_for_ivk(&self, ivk: &IncomingViewingKey) -> Option<&SpendingKey> {
|
||||||
|
self.viewing_keys
|
||||||
|
.get(ivk)
|
||||||
|
.and_then(|fvk| self.spending_keys.get(fvk))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ivk_for_address(&self, addr: &Address) -> Option<&IncomingViewingKey> {
|
||||||
|
self.payment_addresses.get(&OrderedAddress::new(*addr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Wallet {
|
pub struct Wallet {
|
||||||
|
@ -86,6 +113,10 @@ pub struct Wallet {
|
||||||
witness_tree: BridgeTree<MerkleHashOrchard, MERKLE_DEPTH>,
|
witness_tree: BridgeTree<MerkleHashOrchard, MERKLE_DEPTH>,
|
||||||
/// The block height and transaction index of the note most recently added to `witness_tree`
|
/// The block height and transaction index of the note most recently added to `witness_tree`
|
||||||
last_observed: Option<LastObserved>,
|
last_observed: Option<LastObserved>,
|
||||||
|
/// Notes marked as locked (currently reserved for pending transactions)
|
||||||
|
locked_notes: HashSet<OutPoint>,
|
||||||
|
/// Notes marked as spent
|
||||||
|
spent_notes: HashSet<OutPoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -101,6 +132,8 @@ impl Wallet {
|
||||||
wallet_tx_notes: HashMap::new(),
|
wallet_tx_notes: HashMap::new(),
|
||||||
witness_tree: BridgeTree::new(MAX_CHECKPOINTS),
|
witness_tree: BridgeTree::new(MAX_CHECKPOINTS),
|
||||||
last_observed: None,
|
last_observed: None,
|
||||||
|
locked_notes: HashSet::new(),
|
||||||
|
spent_notes: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +154,7 @@ impl Wallet {
|
||||||
bundle: &Bundle<Authorized, Amount>,
|
bundle: &Bundle<Authorized, Amount>,
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
let mut tx_notes = TxNotes {
|
let mut tx_notes = TxNotes {
|
||||||
|
txid: *txid,
|
||||||
decrypted_notes: BTreeMap::new(),
|
decrypted_notes: BTreeMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -199,6 +233,45 @@ impl Wallet {
|
||||||
pub fn tx_contains_my_notes(&self, txid: &TxId) -> bool {
|
pub fn tx_contains_my_notes(&self, txid: &TxId) -> bool {
|
||||||
self.wallet_tx_notes.get(txid).is_some()
|
self.wallet_tx_notes.get(txid).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_filtered_notes(
|
||||||
|
&self,
|
||||||
|
ivk: Option<&IncomingViewingKey>,
|
||||||
|
ignore_spent: bool,
|
||||||
|
ignore_locked: bool,
|
||||||
|
require_spending_key: bool,
|
||||||
|
) -> Vec<(OutPoint, DecryptedNote)> {
|
||||||
|
self.wallet_tx_notes
|
||||||
|
.values()
|
||||||
|
.flat_map(|tx_notes| {
|
||||||
|
tx_notes
|
||||||
|
.decrypted_notes
|
||||||
|
.iter()
|
||||||
|
.filter_map(move |(idx, dnote)| {
|
||||||
|
let outpoint = OutPoint {
|
||||||
|
txid: tx_notes.txid,
|
||||||
|
action_idx: *idx,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.key_store
|
||||||
|
.ivk_for_address(&dnote.note.recipient())
|
||||||
|
// if `ivk` is `None`, return all notes that match the other conditions
|
||||||
|
.filter(|dnote_ivk| ivk.iter().all(|ivk| ivk == dnote_ivk))
|
||||||
|
.and_then(|dnote_ivk| {
|
||||||
|
if (ignore_spent && self.spent_notes.contains(&outpoint))
|
||||||
|
|| (ignore_locked && self.locked_notes.contains(&outpoint))
|
||||||
|
|| (require_spending_key
|
||||||
|
&& self.key_store.spending_key_for_ivk(dnote_ivk).is_none())
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((outpoint, (*dnote).clone()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -293,6 +366,21 @@ pub extern "C" fn orchard_wallet_add_raw_address(
|
||||||
wallet.key_store.add_raw_address(*addr, ivk.clone())
|
wallet.key_store.add_raw_address(*addr, ivk.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_wallet_get_ivk_for_address(
|
||||||
|
wallet: *const Wallet,
|
||||||
|
addr: *const Address,
|
||||||
|
) -> *mut IncomingViewingKey {
|
||||||
|
let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null.");
|
||||||
|
let addr = unsafe { addr.as_ref() }.expect("Address may not be null.");
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.key_store
|
||||||
|
.ivk_for_address(addr)
|
||||||
|
.map(|ivk| Box::into_raw(Box::new(ivk.clone())))
|
||||||
|
.unwrap_or(std::ptr::null_mut())
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_wallet_tx_contains_my_notes(
|
pub extern "C" fn orchard_wallet_tx_contains_my_notes(
|
||||||
wallet: *const Wallet,
|
wallet: *const Wallet,
|
||||||
|
@ -303,3 +391,38 @@ pub extern "C" fn orchard_wallet_tx_contains_my_notes(
|
||||||
|
|
||||||
wallet.tx_contains_my_notes(&txid)
|
wallet.tx_contains_my_notes(&txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type VecObj = std::ptr::NonNull<libc::c_void>;
|
||||||
|
pub type PushCb = unsafe extern "C" fn(obj: Option<VecObj>, meta: NoteMetadata);
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_wallet_get_filtered_notes(
|
||||||
|
wallet: *const Wallet,
|
||||||
|
ivk: *const IncomingViewingKey,
|
||||||
|
ignore_spent: bool,
|
||||||
|
ignore_locked: bool,
|
||||||
|
require_spending_key: bool,
|
||||||
|
result: Option<VecObj>,
|
||||||
|
push_cb: Option<PushCb>,
|
||||||
|
) {
|
||||||
|
let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null.");
|
||||||
|
let ivk = unsafe { ivk.as_ref() };
|
||||||
|
|
||||||
|
for (outpoint, dnote) in
|
||||||
|
wallet.get_filtered_notes(ivk, ignore_spent, ignore_locked, require_spending_key)
|
||||||
|
{
|
||||||
|
let recipient = Box::new(dnote.note.recipient());
|
||||||
|
unsafe {
|
||||||
|
(push_cb.unwrap())(
|
||||||
|
result,
|
||||||
|
NoteMetadata {
|
||||||
|
txid: *outpoint.txid.as_ref(),
|
||||||
|
action_idx: outpoint.action_idx as u32,
|
||||||
|
recipient: Box::into_raw(recipient),
|
||||||
|
note_value: dnote.note.value().inner() as i64,
|
||||||
|
memo: dnote.memo,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -80,12 +80,13 @@ bool AsyncRPCOperation_saplingmigration::main_impl() {
|
||||||
|
|
||||||
std::vector<SproutNoteEntry> sproutEntries;
|
std::vector<SproutNoteEntry> sproutEntries;
|
||||||
std::vector<SaplingNoteEntry> saplingEntries;
|
std::vector<SaplingNoteEntry> saplingEntries;
|
||||||
|
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||||
{
|
{
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
// We set minDepth to 11 to avoid unconfirmed notes and in anticipation of specifying
|
// We set minDepth to 11 to avoid unconfirmed notes and in anticipation of specifying
|
||||||
// an anchor at height N-10 for each Sprout JoinSplit description
|
// an anchor at height N-10 for each Sprout JoinSplit description
|
||||||
// Consider, should notes be sorted?
|
// Consider, should notes be sorted?
|
||||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 11);
|
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 11);
|
||||||
}
|
}
|
||||||
CAmount availableFunds = 0;
|
CAmount availableFunds = 0;
|
||||||
for (const SproutNoteEntry& sproutEntry : sproutEntries) {
|
for (const SproutNoteEntry& sproutEntry : sproutEntries) {
|
||||||
|
|
|
@ -207,11 +207,13 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
||||||
// We currently have an unspent and unconfirmed note in the wallet (depth of -1)
|
// We currently have an unspent and unconfirmed note in the wallet (depth of -1)
|
||||||
std::vector<SproutNoteEntry> sproutEntries;
|
std::vector<SproutNoteEntry> sproutEntries;
|
||||||
std::vector<SaplingNoteEntry> saplingEntries;
|
std::vector<SaplingNoteEntry> saplingEntries;
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 0);
|
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||||
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0);
|
||||||
EXPECT_EQ(0, sproutEntries.size());
|
EXPECT_EQ(0, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, -1);
|
orchardEntries.clear();
|
||||||
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, -1);
|
||||||
EXPECT_EQ(1, sproutEntries.size());
|
EXPECT_EQ(1, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
@ -234,18 +236,21 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
||||||
|
|
||||||
|
|
||||||
// We now have an unspent and confirmed note in the wallet (depth of 1)
|
// We now have an unspent and confirmed note in the wallet (depth of 1)
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 0);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0);
|
||||||
EXPECT_EQ(1, sproutEntries.size());
|
EXPECT_EQ(1, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 1);
|
orchardEntries.clear();
|
||||||
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 1);
|
||||||
EXPECT_EQ(1, sproutEntries.size());
|
EXPECT_EQ(1, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 2);
|
orchardEntries.clear();
|
||||||
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 2);
|
||||||
EXPECT_EQ(0, sproutEntries.size());
|
EXPECT_EQ(0, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
|
|
||||||
|
|
||||||
// Let's spend the note.
|
// Let's spend the note.
|
||||||
|
@ -272,25 +277,29 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
||||||
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));
|
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));
|
||||||
|
|
||||||
// The note has been spent. By default, GetFilteredNotes() ignores spent notes.
|
// The note has been spent. By default, GetFilteredNotes() ignores spent notes.
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 0);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0);
|
||||||
EXPECT_EQ(0, sproutEntries.size());
|
EXPECT_EQ(0, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
// Let's include spent notes to retrieve it.
|
// Let's include spent notes to retrieve it.
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 0, INT_MAX, false);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0, INT_MAX, false);
|
||||||
EXPECT_EQ(1, sproutEntries.size());
|
EXPECT_EQ(1, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
// The spent note has two confirmations.
|
// The spent note has two confirmations.
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 2, INT_MAX, false);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 2, INT_MAX, false);
|
||||||
EXPECT_EQ(1, sproutEntries.size());
|
EXPECT_EQ(1, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
// It does not have 3 confirmations.
|
// It does not have 3 confirmations.
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 3, INT_MAX, false);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 3, INT_MAX, false);
|
||||||
EXPECT_EQ(0, sproutEntries.size());
|
EXPECT_EQ(0, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
|
|
||||||
|
|
||||||
// Let's receive a new note
|
// Let's receive a new note
|
||||||
|
@ -330,25 +339,29 @@ TEST(WalletTests, FindUnspentSproutNotes) {
|
||||||
wallet.AddToWallet(wtx3, true, NULL);
|
wallet.AddToWallet(wtx3, true, NULL);
|
||||||
|
|
||||||
// We now have an unspent note which has one confirmation, in addition to our spent note.
|
// We now have an unspent note which has one confirmation, in addition to our spent note.
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 1);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 1);
|
||||||
EXPECT_EQ(1, sproutEntries.size());
|
EXPECT_EQ(1, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
// Let's return the spent note too.
|
// Let's return the spent note too.
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 1, INT_MAX, false);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 1, INT_MAX, false);
|
||||||
EXPECT_EQ(2, sproutEntries.size());
|
EXPECT_EQ(2, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
// Increasing number of confirmations will exclude our new unspent note.
|
// Increasing number of confirmations will exclude our new unspent note.
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 2, INT_MAX, false);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 2, INT_MAX, false);
|
||||||
EXPECT_EQ(1, sproutEntries.size());
|
EXPECT_EQ(1, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
// If we also ignore spent notes at this depth, we won't find any notes.
|
// If we also ignore spent notes at this depth, we won't find any notes.
|
||||||
wallet.GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 2, INT_MAX, true);
|
wallet.GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 2, INT_MAX, true);
|
||||||
EXPECT_EQ(0, sproutEntries.size());
|
EXPECT_EQ(0, sproutEntries.size());
|
||||||
sproutEntries.clear();
|
sproutEntries.clear();
|
||||||
saplingEntries.clear();
|
saplingEntries.clear();
|
||||||
|
orchardEntries.clear();
|
||||||
|
|
||||||
// Tear down
|
// Tear down
|
||||||
chainActive.SetTip(NULL);
|
chainActive.SetTip(NULL);
|
||||||
|
|
|
@ -5,10 +5,46 @@
|
||||||
#ifndef ZCASH_WALLET_ORCHARD_H
|
#ifndef ZCASH_WALLET_ORCHARD_H
|
||||||
#define ZCASH_WALLET_ORCHARD_H
|
#define ZCASH_WALLET_ORCHARD_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "primitives/transaction.h"
|
||||||
#include "rust/orchard/keys.h"
|
#include "rust/orchard/keys.h"
|
||||||
#include "rust/orchard/wallet.h"
|
#include "rust/orchard/wallet.h"
|
||||||
#include "zcash/address/orchard.hpp"
|
#include "zcash/address/orchard.hpp"
|
||||||
|
|
||||||
|
class OrchardNoteMetadata
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
OrchardOutPoint op;
|
||||||
|
libzcash::OrchardRawAddress address;
|
||||||
|
CAmount noteValue;
|
||||||
|
std::array<uint8_t, ZC_MEMO_SIZE> memo;
|
||||||
|
int confirmations;
|
||||||
|
public:
|
||||||
|
OrchardNoteMetadata(
|
||||||
|
OrchardOutPoint op,
|
||||||
|
const libzcash::OrchardRawAddress& address,
|
||||||
|
CAmount noteValue,
|
||||||
|
const std::array<unsigned char, ZC_MEMO_SIZE>& memo):
|
||||||
|
op(op), address(address), noteValue(noteValue), memo(memo), confirmations(0) {}
|
||||||
|
|
||||||
|
const OrchardOutPoint& GetOutPoint() const {
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetConfirmations(int c) {
|
||||||
|
confirmations = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetConfirmations() const {
|
||||||
|
return confirmations;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAmount GetNoteValue() const {
|
||||||
|
return noteValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class OrchardWallet
|
class OrchardWallet
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -85,6 +121,13 @@ public:
|
||||||
orchard_wallet_add_full_viewing_key(inner.get(), fvk.inner.get());
|
orchard_wallet_add_full_viewing_key(inner.get(), fvk.inner.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<libzcash::OrchardIncomingViewingKey> GetIncomingViewingKeyForAddress(
|
||||||
|
const libzcash::OrchardRawAddress& addr) const {
|
||||||
|
auto ivkPtr = orchard_wallet_get_ivk_for_address(inner.get(), addr.inner.get());
|
||||||
|
if (ivkPtr == nullptr) return std::nullopt;
|
||||||
|
return libzcash::OrchardIncomingViewingKey(ivkPtr);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an address/IVK pair to the wallet, and returns `true` if the
|
* Adds an address/IVK pair to the wallet, and returns `true` if the
|
||||||
* IVK corresponds to a full viewing key known to the wallet, `false`
|
* IVK corresponds to a full viewing key known to the wallet, `false`
|
||||||
|
@ -95,6 +138,42 @@ public:
|
||||||
const libzcash::OrchardIncomingViewingKey& ivk) {
|
const libzcash::OrchardIncomingViewingKey& ivk) {
|
||||||
return orchard_wallet_add_raw_address(inner.get(), addr.inner.get(), ivk.inner.get());
|
return orchard_wallet_add_raw_address(inner.get(), addr.inner.get(), ivk.inner.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void PushOrchardNoteMeta(void* orchardNotesRet, RawOrchardNoteMetadata rawNoteMeta) {
|
||||||
|
uint256 txid;
|
||||||
|
std::move(std::begin(rawNoteMeta.txid), std::end(rawNoteMeta.txid), txid.begin());
|
||||||
|
OrchardOutPoint op(txid, rawNoteMeta.actionIdx);
|
||||||
|
// TODO: what's the efficient way to copy the memo in the OrchardNoteMetadata
|
||||||
|
// constructor?
|
||||||
|
std::array<uint8_t, ZC_MEMO_SIZE> memo;
|
||||||
|
std::move(std::begin(rawNoteMeta.memo), std::end(rawNoteMeta.memo), memo.begin());
|
||||||
|
OrchardNoteMetadata noteMeta(
|
||||||
|
op,
|
||||||
|
libzcash::OrchardRawAddress(rawNoteMeta.addr),
|
||||||
|
rawNoteMeta.noteValue,
|
||||||
|
memo);
|
||||||
|
// TODO: noteMeta.confirmations is only available from the C++ wallet
|
||||||
|
|
||||||
|
reinterpret_cast<std::vector<OrchardNoteMetadata>*>(orchardNotesRet)->push_back(noteMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetFilteredNotes(
|
||||||
|
std::vector<OrchardNoteMetadata>& orchardNotesRet,
|
||||||
|
const std::optional<libzcash::OrchardIncomingViewingKey>& ivk,
|
||||||
|
bool ignoreSpent,
|
||||||
|
bool ignoreLocked,
|
||||||
|
bool requireSpendingKey) const {
|
||||||
|
|
||||||
|
orchard_wallet_get_filtered_notes(
|
||||||
|
inner.get(),
|
||||||
|
ivk.has_value() ? ivk.value().inner.get() : nullptr,
|
||||||
|
ignoreSpent,
|
||||||
|
ignoreLocked,
|
||||||
|
requireSpendingKey,
|
||||||
|
&orchardNotesRet,
|
||||||
|
PushOrchardNoteMeta
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZCASH_ORCHARD_WALLET_H
|
#endif // ZCASH_ORCHARD_WALLET_H
|
||||||
|
|
|
@ -2313,7 +2313,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
|
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
std::optional<AddrSet> noteFilter = std::nullopt;
|
std::optional<NoteFilter> noteFilter = std::nullopt;
|
||||||
std::set<std::pair<libzcash::SproutPaymentAddress, uint256>> sproutNullifiers;
|
std::set<std::pair<libzcash::SproutPaymentAddress, uint256>> sproutNullifiers;
|
||||||
std::set<std::pair<libzcash::SaplingPaymentAddress, uint256>> saplingNullifiers;
|
std::set<std::pair<libzcash::SaplingPaymentAddress, uint256>> saplingNullifiers;
|
||||||
|
|
||||||
|
@ -2340,7 +2340,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
sourceAddrs.push_back(zaddr.value());
|
sourceAddrs.push_back(zaddr.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
noteFilter = AddrSet::ForPaymentAddresses(sourceAddrs);
|
noteFilter = NoteFilter::ForPaymentAddresses(sourceAddrs);
|
||||||
sproutNullifiers = pwalletMain->GetSproutNullifiers(noteFilter.value().GetSproutAddresses());
|
sproutNullifiers = pwalletMain->GetSproutNullifiers(noteFilter.value().GetSproutAddresses());
|
||||||
saplingNullifiers = pwalletMain->GetSaplingNullifiers(noteFilter.value().GetSaplingAddresses());
|
saplingNullifiers = pwalletMain->GetSaplingNullifiers(noteFilter.value().GetSaplingAddresses());
|
||||||
|
|
||||||
|
@ -2365,7 +2365,8 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
|
||||||
|
|
||||||
std::vector<SproutNoteEntry> sproutEntries;
|
std::vector<SproutNoteEntry> sproutEntries;
|
||||||
std::vector<SaplingNoteEntry> saplingEntries;
|
std::vector<SaplingNoteEntry> saplingEntries;
|
||||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, noteFilter, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
|
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||||
|
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
|
||||||
|
|
||||||
for (auto & entry : sproutEntries) {
|
for (auto & entry : sproutEntries) {
|
||||||
UniValue obj(UniValue::VOBJ);
|
UniValue obj(UniValue::VOBJ);
|
||||||
|
@ -3382,14 +3383,15 @@ CAmount getBalanceZaddr(std::optional<libzcash::PaymentAddress> address, int min
|
||||||
CAmount balance = 0;
|
CAmount balance = 0;
|
||||||
std::vector<SproutNoteEntry> sproutEntries;
|
std::vector<SproutNoteEntry> sproutEntries;
|
||||||
std::vector<SaplingNoteEntry> saplingEntries;
|
std::vector<SaplingNoteEntry> saplingEntries;
|
||||||
|
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
std::optional<AddrSet> noteFilter = std::nullopt;
|
std::optional<NoteFilter> noteFilter = std::nullopt;
|
||||||
if (address.has_value()) {
|
if (address.has_value()) {
|
||||||
noteFilter = AddrSet::ForPaymentAddresses(std::vector({address.value()}));
|
noteFilter = NoteFilter::ForPaymentAddresses(std::vector({address.value()}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, noteFilter, minDepth, maxDepth, true, ignoreUnspendable);
|
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, minDepth, maxDepth, true, ignoreUnspendable);
|
||||||
for (auto & entry : sproutEntries) {
|
for (auto & entry : sproutEntries) {
|
||||||
balance += CAmount(entry.note.value());
|
balance += CAmount(entry.note.value());
|
||||||
}
|
}
|
||||||
|
@ -3477,8 +3479,10 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
|
||||||
|
|
||||||
std::vector<SproutNoteEntry> sproutEntries;
|
std::vector<SproutNoteEntry> sproutEntries;
|
||||||
std::vector<SaplingNoteEntry> saplingEntries;
|
std::vector<SaplingNoteEntry> saplingEntries;
|
||||||
auto noteFilter = AddrSet::ForPaymentAddresses(std::vector({decoded.value()}));
|
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, noteFilter, nMinDepth, INT_MAX, false, false);
|
|
||||||
|
auto noteFilter = NoteFilter::ForPaymentAddresses(std::vector({decoded.value()}));
|
||||||
|
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter, nMinDepth, INT_MAX, false, false);
|
||||||
|
|
||||||
auto push_transparent_result = [&](const CTxDestination& dest) -> void {
|
auto push_transparent_result = [&](const CTxDestination& dest) -> void {
|
||||||
const CScript scriptPubKey{GetScriptForDestination(dest)};
|
const CScript scriptPubKey{GetScriptForDestination(dest)};
|
||||||
|
@ -4698,9 +4702,10 @@ UniValue z_getmigrationstatus(const UniValue& params, bool fHelp) {
|
||||||
{
|
{
|
||||||
std::vector<SproutNoteEntry> sproutEntries;
|
std::vector<SproutNoteEntry> sproutEntries;
|
||||||
std::vector<SaplingNoteEntry> saplingEntries;
|
std::vector<SaplingNoteEntry> saplingEntries;
|
||||||
|
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||||
// Here we are looking for any and all Sprout notes for which we have the spending key, including those
|
// Here we are looking for any and all Sprout notes for which we have the spending key, including those
|
||||||
// which are locked and/or only exist in the mempool, as they should be included in the unmigrated amount.
|
// which are locked and/or only exist in the mempool, as they should be included in the unmigrated amount.
|
||||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 0, INT_MAX, true, true, false);
|
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 0, INT_MAX, true, true, false);
|
||||||
CAmount unmigratedAmount = 0;
|
CAmount unmigratedAmount = 0;
|
||||||
for (const auto& sproutEntry : sproutEntries) {
|
for (const auto& sproutEntry : sproutEntries) {
|
||||||
unmigratedAmount += sproutEntry.note.value();
|
unmigratedAmount += sproutEntry.note.value();
|
||||||
|
@ -5304,11 +5309,12 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
||||||
// Get available notes
|
// Get available notes
|
||||||
std::vector<SproutNoteEntry> sproutEntries;
|
std::vector<SproutNoteEntry> sproutEntries;
|
||||||
std::vector<SaplingNoteEntry> saplingEntries;
|
std::vector<SaplingNoteEntry> saplingEntries;
|
||||||
std::optional<AddrSet> noteFilter =
|
std::vector<OrchardNoteMetadata> orchardEntries;
|
||||||
|
std::optional<NoteFilter> noteFilter =
|
||||||
useAnySprout || useAnySapling ?
|
useAnySprout || useAnySapling ?
|
||||||
std::nullopt :
|
std::nullopt :
|
||||||
std::optional(AddrSet::ForPaymentAddresses(zaddrs));
|
std::optional(NoteFilter::ForPaymentAddresses(zaddrs));
|
||||||
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, noteFilter);
|
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter);
|
||||||
|
|
||||||
// If Sapling is not active, do not allow sending from a sapling addresses.
|
// If Sapling is not active, do not allow sending from a sapling addresses.
|
||||||
if (!saplingActive && saplingEntries.size() > 0) {
|
if (!saplingActive && saplingEntries.size() > 0) {
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "timedata.h"
|
#include "timedata.h"
|
||||||
#include "utilmoneystr.h"
|
#include "utilmoneystr.h"
|
||||||
#include "util/match.h"
|
#include "util/match.h"
|
||||||
|
#include "zcash/Address.hpp"
|
||||||
#include "zcash/JoinSplit.hpp"
|
#include "zcash/JoinSplit.hpp"
|
||||||
#include "zcash/Note.hpp"
|
#include "zcash/Note.hpp"
|
||||||
#include "crypter.h"
|
#include "crypter.h"
|
||||||
|
@ -6014,8 +6015,8 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
|
||||||
return ::AcceptToMemoryPool(Params(), mempool, state, *this, fLimitFree, NULL, fRejectAbsurdFee);
|
return ::AcceptToMemoryPool(Params(), mempool, state, *this, fLimitFree, NULL, fRejectAbsurdFee);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddrSet AddrSet::ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>& paymentAddrs) {
|
NoteFilter NoteFilter::ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>& paymentAddrs) {
|
||||||
AddrSet addrs;
|
NoteFilter addrs;
|
||||||
for (const auto& addr: paymentAddrs) {
|
for (const auto& addr: paymentAddrs) {
|
||||||
std::visit(match {
|
std::visit(match {
|
||||||
[&](const CKeyID& keyId) { },
|
[&](const CKeyID& keyId) { },
|
||||||
|
@ -6041,12 +6042,13 @@ AddrSet AddrSet::ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>
|
||||||
return addrs;
|
return addrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::HasSpendingKeys(const AddrSet& addrSet) const {
|
bool CWallet::HasSpendingKeys(const NoteFilter& addrSet) const {
|
||||||
for (const auto& zaddr : addrSet.GetSproutAddresses()) {
|
for (const auto& zaddr : addrSet.GetSproutAddresses()) {
|
||||||
if (!HaveSproutSpendingKey(zaddr)) {
|
if (!HaveSproutSpendingKey(zaddr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& zaddr : addrSet.GetSaplingAddresses()) {
|
for (const auto& zaddr : addrSet.GetSaplingAddresses()) {
|
||||||
if (!HaveSaplingSpendingKeyForAddress(zaddr)) {
|
if (!HaveSaplingSpendingKeyForAddress(zaddr)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -6068,9 +6070,10 @@ bool CWallet::HasSpendingKeys(const AddrSet& addrSet) const {
|
||||||
* will be unmodified.
|
* will be unmodified.
|
||||||
*/
|
*/
|
||||||
void CWallet::GetFilteredNotes(
|
void CWallet::GetFilteredNotes(
|
||||||
std::vector<SproutNoteEntry>& sproutEntries,
|
std::vector<SproutNoteEntry>& sproutEntriesRet,
|
||||||
std::vector<SaplingNoteEntry>& saplingEntries,
|
std::vector<SaplingNoteEntry>& saplingEntriesRet,
|
||||||
const std::optional<AddrSet>& noteFilter,
|
std::vector<OrchardNoteMetadata>& orchardNotesRet,
|
||||||
|
const std::optional<NoteFilter>& noteFilter,
|
||||||
int minDepth,
|
int minDepth,
|
||||||
int maxDepth,
|
int maxDepth,
|
||||||
bool ignoreSpent,
|
bool ignoreSpent,
|
||||||
|
@ -6095,7 +6098,7 @@ void CWallet::GetFilteredNotes(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter coinbase transactions that don't have Sapling outputs
|
// Filter coinbase transactions that don't have Sapling outputs
|
||||||
if (wtx.IsCoinBase() && wtx.mapSaplingNoteData.empty()) {
|
if (wtx.IsCoinBase() && wtx.mapSaplingNoteData.empty() && true/* TODO ORCHARD */) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6147,7 +6150,7 @@ void CWallet::GetFilteredNotes(
|
||||||
hSig,
|
hSig,
|
||||||
(unsigned char) j);
|
(unsigned char) j);
|
||||||
|
|
||||||
sproutEntries.push_back(SproutNoteEntry {
|
sproutEntriesRet.push_back(SproutNoteEntry {
|
||||||
jsop, pa, plaintext.note(pa), plaintext.memo(), wtx.GetDepthInMainChain() });
|
jsop, pa, plaintext.note(pa), plaintext.memo(), wtx.GetDepthInMainChain() });
|
||||||
|
|
||||||
} catch (const note_decryption_failed &err) {
|
} catch (const note_decryption_failed &err) {
|
||||||
|
@ -6194,10 +6197,41 @@ void CWallet::GetFilteredNotes(
|
||||||
}
|
}
|
||||||
|
|
||||||
auto note = notePt.note(nd.ivk).value();
|
auto note = notePt.note(nd.ivk).value();
|
||||||
saplingEntries.push_back(SaplingNoteEntry {
|
saplingEntriesRet.push_back(SaplingNoteEntry {
|
||||||
op, pa, note, notePt.memo(), wtx.GetDepthInMainChain() });
|
op, pa, note, notePt.memo(), wtx.GetDepthInMainChain() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (noteFilter.has_value()) {
|
||||||
|
for (const OrchardRawAddress& addr: noteFilter.value().GetOrchardAddresses()) {
|
||||||
|
auto ivk = orchardWallet.GetIncomingViewingKeyForAddress(addr);
|
||||||
|
if (ivk.has_value()) {
|
||||||
|
orchardWallet.GetFilteredNotes(
|
||||||
|
orchardNotesRet,
|
||||||
|
ivk.value(),
|
||||||
|
ignoreSpent,
|
||||||
|
ignoreLocked,
|
||||||
|
requireSpendingKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// return all Orchard notes
|
||||||
|
orchardWallet.GetFilteredNotes(
|
||||||
|
orchardNotesRet,
|
||||||
|
std::nullopt,
|
||||||
|
ignoreSpent,
|
||||||
|
ignoreLocked,
|
||||||
|
requireSpendingKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &orchardNoteMeta : orchardNotesRet) {
|
||||||
|
auto wtx = GetWalletTx(orchardNoteMeta.GetOutPoint().hash);
|
||||||
|
if (wtx) {
|
||||||
|
orchardNoteMeta.SetConfirmations(wtx->GetDepthInMainChain());
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Wallet inconsistency: We have an Orchard WalletTx without a corresponding CWalletTx");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<UFVKId> CWallet::FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const {
|
std::optional<UFVKId> CWallet::FindUnifiedFullViewingKey(const libzcash::UnifiedAddress& addr) const {
|
||||||
|
|
|
@ -645,15 +645,16 @@ public:
|
||||||
std::set<uint256> GetConflicts() const;
|
std::set<uint256> GetConflicts() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddrSet {
|
class NoteFilter {
|
||||||
private:
|
private:
|
||||||
std::set<libzcash::SproutPaymentAddress> sproutAddresses;
|
std::set<libzcash::SproutPaymentAddress> sproutAddresses;
|
||||||
std::set<libzcash::SaplingPaymentAddress> saplingAddresses;
|
std::set<libzcash::SaplingPaymentAddress> saplingAddresses;
|
||||||
|
std::set<libzcash::OrchardRawAddress> orchardAddresses;
|
||||||
|
|
||||||
AddrSet() {}
|
NoteFilter() {}
|
||||||
public:
|
public:
|
||||||
static AddrSet Empty() { return AddrSet(); }
|
static NoteFilter Empty() { return NoteFilter(); }
|
||||||
static AddrSet ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>& addrs);
|
static NoteFilter ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>& addrs);
|
||||||
|
|
||||||
const std::set<libzcash::SproutPaymentAddress>& GetSproutAddresses() const {
|
const std::set<libzcash::SproutPaymentAddress>& GetSproutAddresses() const {
|
||||||
return sproutAddresses;
|
return sproutAddresses;
|
||||||
|
@ -663,8 +664,15 @@ public:
|
||||||
return saplingAddresses;
|
return saplingAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::set<libzcash::OrchardRawAddress>& GetOrchardAddresses() const {
|
||||||
|
return orchardAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsEmpty() const {
|
bool IsEmpty() const {
|
||||||
return sproutAddresses.empty() && saplingAddresses.empty();
|
return
|
||||||
|
sproutAddresses.empty() &&
|
||||||
|
saplingAddresses.empty() &&
|
||||||
|
orchardAddresses.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasSproutAddress(libzcash::SproutPaymentAddress addr) const {
|
bool HasSproutAddress(libzcash::SproutPaymentAddress addr) const {
|
||||||
|
@ -674,6 +682,10 @@ public:
|
||||||
bool HasSaplingAddress(libzcash::SaplingPaymentAddress addr) const {
|
bool HasSaplingAddress(libzcash::SaplingPaymentAddress addr) const {
|
||||||
return saplingAddresses.count(addr) > 0;
|
return saplingAddresses.count(addr) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HasOrchardAddress(libzcash::OrchardRawAddress addr) const {
|
||||||
|
return orchardAddresses.count(addr) > 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class COutput
|
class COutput
|
||||||
|
@ -1751,13 +1763,14 @@ public:
|
||||||
* Check whether the wallet contains spending keys for all the addresses
|
* Check whether the wallet contains spending keys for all the addresses
|
||||||
* contained in the given address set.
|
* contained in the given address set.
|
||||||
*/
|
*/
|
||||||
bool HasSpendingKeys(const AddrSet& noteFilter) const;
|
bool HasSpendingKeys(const NoteFilter& noteFilter) const;
|
||||||
|
|
||||||
/* Find notes filtered by payment addresses, min depth, max depth, if they are spent,
|
/* Find notes filtered by payment addresses, min depth, max depth, if they are spent,
|
||||||
if a spending key is required, and if they are locked */
|
if a spending key is required, and if they are locked */
|
||||||
void GetFilteredNotes(std::vector<SproutNoteEntry>& sproutEntries,
|
void GetFilteredNotes(std::vector<SproutNoteEntry>& sproutEntriesRet,
|
||||||
std::vector<SaplingNoteEntry>& saplingEntries,
|
std::vector<SaplingNoteEntry>& saplingEntriesRet,
|
||||||
const std::optional<AddrSet>& noteFilter,
|
std::vector<OrchardNoteMetadata>& orchardNotesRet,
|
||||||
|
const std::optional<NoteFilter>& noteFilter,
|
||||||
int minDepth=1,
|
int minDepth=1,
|
||||||
int maxDepth=INT_MAX,
|
int maxDepth=INT_MAX,
|
||||||
bool ignoreSpent=true,
|
bool ignoreSpent=true,
|
||||||
|
|
|
@ -51,6 +51,10 @@ public:
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
friend bool operator<(const OrchardRawAddress& c1, const OrchardRawAddress& c2) {
|
||||||
|
return orchard_address_lt(c1.inner.get(), c2.inner.get());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class OrchardIncomingViewingKey
|
class OrchardIncomingViewingKey
|
||||||
|
|
Loading…
Reference in New Issue