Add GetFilteredNotes to Orchard wallet.
This commit is contained in:
parent
6587b2ed86
commit
4c53499f11
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue