Add GetFilteredNotes to Orchard wallet.

This commit is contained in:
Kris Nuttycombe 2021-08-03 16:43:47 -06:00
parent 6587b2ed86
commit 4c53499f11
12 changed files with 393 additions and 46 deletions

View File

@ -523,6 +523,16 @@ public:
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
* transaction's output that it claims and a signature that matches the
* output's public key.

View File

@ -32,6 +32,15 @@ OrchardRawAddressPtr* orchard_address_clone(
*/
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
//

View File

@ -106,6 +106,51 @@ bool orchard_wallet_add_raw_address(
const OrchardRawAddressPtr* addr,
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
}
#endif

View File

@ -5,7 +5,10 @@ use tracing::error;
use orchard::keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, SpendingKey};
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
@ -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
//

View File

@ -1,6 +1,6 @@
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Tree};
use libc::c_uchar;
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, HashMap, HashSet};
use tracing::error;
use zcash_primitives::{
@ -29,6 +29,12 @@ pub struct LastObserved {
block_tx_idx: usize,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct OutPoint {
txid: TxId,
action_idx: usize,
}
#[derive(Debug, Clone)]
pub struct DecryptedNote {
note: Note,
@ -36,9 +42,20 @@ pub struct DecryptedNote {
}
struct TxNotes {
txid: TxId,
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 {
payment_addresses: BTreeMap<OrderedAddress, IncomingViewingKey>,
viewing_keys: BTreeMap<IncomingViewingKey, FullViewingKey>,
@ -73,6 +90,16 @@ impl KeyStore {
.insert(OrderedAddress::new(addr), ivk);
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 {
@ -86,6 +113,10 @@ pub struct Wallet {
witness_tree: BridgeTree<MerkleHashOrchard, MERKLE_DEPTH>,
/// The block height and transaction index of the note most recently added to `witness_tree`
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)]
@ -101,6 +132,8 @@ impl Wallet {
wallet_tx_notes: HashMap::new(),
witness_tree: BridgeTree::new(MAX_CHECKPOINTS),
last_observed: None,
locked_notes: HashSet::new(),
spent_notes: HashSet::new(),
}
}
@ -121,6 +154,7 @@ impl Wallet {
bundle: &Bundle<Authorized, Amount>,
) -> Vec<usize> {
let mut tx_notes = TxNotes {
txid: *txid,
decrypted_notes: BTreeMap::new(),
};
@ -199,6 +233,45 @@ impl Wallet {
pub fn tx_contains_my_notes(&self, txid: &TxId) -> bool {
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]
@ -293,6 +366,21 @@ pub extern "C" fn orchard_wallet_add_raw_address(
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]
pub extern "C" fn orchard_wallet_tx_contains_my_notes(
wallet: *const Wallet,
@ -303,3 +391,38 @@ pub extern "C" fn orchard_wallet_tx_contains_my_notes(
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,
},
)
};
}
}

View File

@ -80,12 +80,13 @@ bool AsyncRPCOperation_saplingmigration::main_impl() {
std::vector<SproutNoteEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
std::vector<OrchardNoteMetadata> orchardEntries;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
// 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
// Consider, should notes be sorted?
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, std::nullopt, 11);
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, std::nullopt, 11);
}
CAmount availableFunds = 0;
for (const SproutNoteEntry& sproutEntry : sproutEntries) {

View File

@ -207,11 +207,13 @@ TEST(WalletTests, FindUnspentSproutNotes) {
// We currently have an unspent and unconfirmed note in the wallet (depth of -1)
std::vector<SproutNoteEntry> sproutEntries;
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());
sproutEntries.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());
sproutEntries.clear();
saplingEntries.clear();
@ -234,18 +236,21 @@ TEST(WalletTests, FindUnspentSproutNotes) {
// 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());
sproutEntries.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());
sproutEntries.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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// Let's spend the note.
@ -272,25 +277,29 @@ TEST(WalletTests, FindUnspentSproutNotes) {
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));
// 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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// 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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// 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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// 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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// Let's receive a new note
@ -330,25 +339,29 @@ TEST(WalletTests, FindUnspentSproutNotes) {
wallet.AddToWallet(wtx3, true, NULL);
// 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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// 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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// 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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// 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());
sproutEntries.clear();
saplingEntries.clear();
orchardEntries.clear();
// Tear down
chainActive.SetTip(NULL);

View File

@ -5,10 +5,46 @@
#ifndef ZCASH_WALLET_ORCHARD_H
#define ZCASH_WALLET_ORCHARD_H
#include <array>
#include "primitives/transaction.h"
#include "rust/orchard/keys.h"
#include "rust/orchard/wallet.h"
#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
{
private:
@ -85,6 +121,13 @@ public:
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
* IVK corresponds to a full viewing key known to the wallet, `false`
@ -95,6 +138,42 @@ public:
const libzcash::OrchardIncomingViewingKey& ivk) {
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

View File

@ -2313,7 +2313,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
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::SaplingPaymentAddress, uint256>> saplingNullifiers;
@ -2340,7 +2340,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
sourceAddrs.push_back(zaddr.value());
}
noteFilter = AddrSet::ForPaymentAddresses(sourceAddrs);
noteFilter = NoteFilter::ForPaymentAddresses(sourceAddrs);
sproutNullifiers = pwalletMain->GetSproutNullifiers(noteFilter.value().GetSproutAddresses());
saplingNullifiers = pwalletMain->GetSaplingNullifiers(noteFilter.value().GetSaplingAddresses());
@ -2365,7 +2365,8 @@ UniValue z_listunspent(const UniValue& params, bool fHelp)
std::vector<SproutNoteEntry> sproutEntries;
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) {
UniValue obj(UniValue::VOBJ);
@ -3382,14 +3383,15 @@ CAmount getBalanceZaddr(std::optional<libzcash::PaymentAddress> address, int min
CAmount balance = 0;
std::vector<SproutNoteEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
std::vector<OrchardNoteMetadata> orchardEntries;
LOCK2(cs_main, pwalletMain->cs_wallet);
std::optional<AddrSet> noteFilter = std::nullopt;
std::optional<NoteFilter> noteFilter = std::nullopt;
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) {
balance += CAmount(entry.note.value());
}
@ -3477,8 +3479,10 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp)
std::vector<SproutNoteEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
auto noteFilter = AddrSet::ForPaymentAddresses(std::vector({decoded.value()}));
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, noteFilter, nMinDepth, INT_MAX, false, false);
std::vector<OrchardNoteMetadata> orchardEntries;
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 {
const CScript scriptPubKey{GetScriptForDestination(dest)};
@ -4698,9 +4702,10 @@ UniValue z_getmigrationstatus(const UniValue& params, bool fHelp) {
{
std::vector<SproutNoteEntry> sproutEntries;
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
// 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;
for (const auto& sproutEntry : sproutEntries) {
unmigratedAmount += sproutEntry.note.value();
@ -5304,11 +5309,12 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
// Get available notes
std::vector<SproutNoteEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
std::optional<AddrSet> noteFilter =
std::vector<OrchardNoteMetadata> orchardEntries;
std::optional<NoteFilter> noteFilter =
useAnySprout || useAnySapling ?
std::nullopt :
std::optional(AddrSet::ForPaymentAddresses(zaddrs));
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, noteFilter);
std::optional(NoteFilter::ForPaymentAddresses(zaddrs));
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, orchardEntries, noteFilter);
// If Sapling is not active, do not allow sending from a sapling addresses.
if (!saplingActive && saplingEntries.size() > 0) {

View File

@ -27,6 +27,7 @@
#include "timedata.h"
#include "utilmoneystr.h"
#include "util/match.h"
#include "zcash/Address.hpp"
#include "zcash/JoinSplit.hpp"
#include "zcash/Note.hpp"
#include "crypter.h"
@ -6014,8 +6015,8 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
return ::AcceptToMemoryPool(Params(), mempool, state, *this, fLimitFree, NULL, fRejectAbsurdFee);
}
AddrSet AddrSet::ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>& paymentAddrs) {
AddrSet addrs;
NoteFilter NoteFilter::ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>& paymentAddrs) {
NoteFilter addrs;
for (const auto& addr: paymentAddrs) {
std::visit(match {
[&](const CKeyID& keyId) { },
@ -6041,12 +6042,13 @@ AddrSet AddrSet::ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>
return addrs;
}
bool CWallet::HasSpendingKeys(const AddrSet& addrSet) const {
bool CWallet::HasSpendingKeys(const NoteFilter& addrSet) const {
for (const auto& zaddr : addrSet.GetSproutAddresses()) {
if (!HaveSproutSpendingKey(zaddr)) {
return false;
}
}
for (const auto& zaddr : addrSet.GetSaplingAddresses()) {
if (!HaveSaplingSpendingKeyForAddress(zaddr)) {
return false;
@ -6068,9 +6070,10 @@ bool CWallet::HasSpendingKeys(const AddrSet& addrSet) const {
* will be unmodified.
*/
void CWallet::GetFilteredNotes(
std::vector<SproutNoteEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
const std::optional<AddrSet>& noteFilter,
std::vector<SproutNoteEntry>& sproutEntriesRet,
std::vector<SaplingNoteEntry>& saplingEntriesRet,
std::vector<OrchardNoteMetadata>& orchardNotesRet,
const std::optional<NoteFilter>& noteFilter,
int minDepth,
int maxDepth,
bool ignoreSpent,
@ -6095,7 +6098,7 @@ void CWallet::GetFilteredNotes(
}
// 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;
}
@ -6147,7 +6150,7 @@ void CWallet::GetFilteredNotes(
hSig,
(unsigned char) j);
sproutEntries.push_back(SproutNoteEntry {
sproutEntriesRet.push_back(SproutNoteEntry {
jsop, pa, plaintext.note(pa), plaintext.memo(), wtx.GetDepthInMainChain() });
} catch (const note_decryption_failed &err) {
@ -6194,10 +6197,41 @@ void CWallet::GetFilteredNotes(
}
auto note = notePt.note(nd.ivk).value();
saplingEntries.push_back(SaplingNoteEntry {
saplingEntriesRet.push_back(SaplingNoteEntry {
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 {

View File

@ -645,15 +645,16 @@ public:
std::set<uint256> GetConflicts() const;
};
class AddrSet {
class NoteFilter {
private:
std::set<libzcash::SproutPaymentAddress> sproutAddresses;
std::set<libzcash::SaplingPaymentAddress> saplingAddresses;
std::set<libzcash::OrchardRawAddress> orchardAddresses;
AddrSet() {}
NoteFilter() {}
public:
static AddrSet Empty() { return AddrSet(); }
static AddrSet ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>& addrs);
static NoteFilter Empty() { return NoteFilter(); }
static NoteFilter ForPaymentAddresses(const std::vector<libzcash::PaymentAddress>& addrs);
const std::set<libzcash::SproutPaymentAddress>& GetSproutAddresses() const {
return sproutAddresses;
@ -663,8 +664,15 @@ public:
return saplingAddresses;
}
const std::set<libzcash::OrchardRawAddress>& GetOrchardAddresses() const {
return orchardAddresses;
}
bool IsEmpty() const {
return sproutAddresses.empty() && saplingAddresses.empty();
return
sproutAddresses.empty() &&
saplingAddresses.empty() &&
orchardAddresses.empty();
}
bool HasSproutAddress(libzcash::SproutPaymentAddress addr) const {
@ -674,6 +682,10 @@ public:
bool HasSaplingAddress(libzcash::SaplingPaymentAddress addr) const {
return saplingAddresses.count(addr) > 0;
}
bool HasOrchardAddress(libzcash::OrchardRawAddress addr) const {
return orchardAddresses.count(addr) > 0;
}
};
class COutput
@ -1751,13 +1763,14 @@ public:
* Check whether the wallet contains spending keys for all the addresses
* 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,
if a spending key is required, and if they are locked */
void GetFilteredNotes(std::vector<SproutNoteEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
const std::optional<AddrSet>& noteFilter,
void GetFilteredNotes(std::vector<SproutNoteEntry>& sproutEntriesRet,
std::vector<SaplingNoteEntry>& saplingEntriesRet,
std::vector<OrchardNoteMetadata>& orchardNotesRet,
const std::optional<NoteFilter>& noteFilter,
int minDepth=1,
int maxDepth=INT_MAX,
bool ignoreSpent=true,

View File

@ -51,6 +51,10 @@ public:
}
return *this;
}
friend bool operator<(const OrchardRawAddress& c1, const OrchardRawAddress& c2) {
return orchard_address_lt(c1.inner.get(), c2.inner.get());
}
};
class OrchardIncomingViewingKey