Ensure that Orchard spentness information is repopulated by LoadUnifiedCaches.

This commit is contained in:
Kris Nuttycombe 2022-03-07 09:36:05 -07:00
parent 0f59c5b926
commit 1e1521f101
6 changed files with 254 additions and 97 deletions

View File

@ -92,24 +92,32 @@ static_assert(
"RawOrchardActionIVK struct should have exactly a 128-bit in-memory representation.");
static_assert(alignof(RawOrchardActionIVK) == 8, "RawOrchardActionIVK struct alignment is not 64 bits.");
typedef void (*push_action_ivk_callback_t)(void* resultCollection, const RawOrchardActionIVK actionIvk);
typedef void (*push_action_ivk_callback_t)(void* rec, const RawOrchardActionIVK actionIvk);
typedef void (*push_spend_action_idx_callback_t)(void* rec, uint32_t actionIdx);
/**
* Searches the provided bundle for notes that are visible to the specified wallet's
* incoming viewing keys, and adds those notes to the wallet. For each note decryptable
* by one of the wallet's keys, this method will insert a `RawOrchardActionIVK` value into
* the provided `resultCollection` using the `push_cb` callback. Note that this callback
* can perform transformations on the provided RawOrchardActionIVK in this process.
* the provided `decryptedActionIVKs` referent using the `push_cb` callback. Note that
* this callback can perform transformations on the provided RawOrchardActionIVK in this
* process. For each action spending one of the wallet's notes, this method will insert
* a `uint32_t` action index into the `spendingActionIdxs` referent.
*
* The provided bundle must be a component of the transaction from which `txid` was
* derived.
*
* Returns `true` if the bundle is involved with the wallet; i.e. if it contains
* notes spendable by the wallet, or spends any of the wallet's notes.
*/
void orchard_wallet_add_notes_from_bundle(
bool orchard_wallet_add_notes_from_bundle(
OrchardWalletPtr* wallet,
const unsigned char txid[32],
const OrchardBundlePtr* bundle,
void* resultCollection,
push_action_ivk_callback_t push_cb
void* callbackReceiver,
push_action_ivk_callback_t push_cb,
push_spend_action_idx_callback_t spend_cb
);
/**
@ -122,13 +130,15 @@ void orchard_wallet_add_notes_from_bundle(
* The value the `blockHeight` pointer points to be set to the height at which the
* transaction was mined, or `nullptr` if the transaction is not in the main chain.
*/
bool orchard_wallet_restore_notes(
bool orchard_wallet_load_bundle(
OrchardWalletPtr* wallet,
const uint32_t* blockHeight,
const unsigned char txid[32],
const OrchardBundlePtr* bundle,
const RawOrchardActionIVK* actionIvks,
size_t actionIvksLen
size_t actionIvksLen,
const uint32_t* actionsSpendingWalletNotes,
size_t actionsSpendingWalletNotesLen
);
/**
@ -157,10 +167,10 @@ void orchard_wallet_commitment_tree_root(
unsigned char* root_ret);
/**
* Returns whether the specified transaction contains any Orchard notes that belong to
* Returns whether the specified transaction involves any Orchard notes that belong to
* this wallet.
*/
bool orchard_wallet_tx_contains_my_notes(
bool orchard_wallet_tx_involves_my_notes(
const OrchardWalletPtr* wallet,
const unsigned char txid[32]);

View File

@ -168,7 +168,7 @@ pub enum RewindError {
}
#[derive(Debug, Clone)]
pub enum BundleDecryptionError {
pub enum BundleLoadError {
/// The action at the specified index failed to decrypt with
/// the provided IVK.
ActionDecryptionFailed(usize),
@ -176,6 +176,26 @@ pub enum BundleDecryptionError {
/// to the incoming viewing key that successfullly decrypted a
/// note.
FvkNotFound(IncomingViewingKey),
/// An action index identified as potentially spending one of our
/// notes is not a valid action index for the bundle.
InvalidActionIndex(usize),
}
/// A struct used to return metadata about how a bundle was determined
/// to be involved with the wallet.
#[derive(Debug, Clone)]
pub struct BundleWalletInvolvement {
receive_action_metadata: BTreeMap<usize, IncomingViewingKey>,
spend_action_metadata: Vec<usize>,
}
impl BundleWalletInvolvement {
pub fn new() -> Self {
BundleWalletInvolvement {
receive_action_metadata: BTreeMap::new(),
spend_action_metadata: Vec::new(),
}
}
}
impl Wallet {
@ -320,24 +340,11 @@ impl Wallet {
&mut self,
txid: &TxId,
bundle: &Bundle<Authorized, Amount>,
) -> BTreeMap<usize, IncomingViewingKey> {
// Check for spends of our notes by matching against the nullifiers
// we're tracking, and when we detect one, associate the current
// txid and action as spending the note.
for (action_idx, action) in bundle.actions().iter().enumerate() {
let nf = action.nullifier();
// If a nullifier corresponds to one of our notes, add its inpoint as a
// potential spend (the transaction may not end up being mined).
if self.nullifiers.contains_key(nf) {
self.potential_spends
.entry(*nf)
.or_insert_with(BTreeSet::new)
.insert(InPoint {
txid: *txid,
action_idx,
});
}
}
) -> BundleWalletInvolvement {
let mut involvement = BundleWalletInvolvement::new();
// If we recognize any of our notes as being consumed as inputs to actions
// in this bundle, record them as potential spends.
involvement.spend_action_metadata = self.add_potential_spends(txid, bundle);
let keys = self
.key_store
@ -345,7 +352,6 @@ impl Wallet {
.keys()
.cloned()
.collect::<Vec<_>>();
let mut result = BTreeMap::new();
for (action_idx, ivk, note, recipient, memo) in bundle.decrypt_outputs_for_keys(&keys) {
assert!(self.add_decrypted_note(
@ -357,22 +363,38 @@ impl Wallet {
recipient,
memo
));
result.insert(action_idx, ivk);
involvement.receive_action_metadata.insert(action_idx, ivk);
}
result
involvement
}
/// Add note data to the wallet by attempting to
/// incoming viewing keys to the wallet, and return a map from incoming viewing
/// key to the vector of action indices that that key decrypts.
pub fn add_notes_from_bundle_with_hints(
pub fn load_bundle(
&mut self,
tx_height: Option<BlockHeight>,
txid: &TxId,
bundle: &Bundle<Authorized, Amount>,
hints: BTreeMap<usize, &IncomingViewingKey>,
) -> Result<(), BundleDecryptionError> {
potential_spend_idxs: &[u32],
) -> Result<(), BundleLoadError> {
for action_idx in potential_spend_idxs {
let action_idx: usize = (*action_idx).try_into().unwrap();
if action_idx < bundle.actions().len() {
self.add_potential_spend(
bundle.actions()[action_idx].nullifier(),
InPoint {
txid: *txid,
action_idx,
},
);
} else {
return Err(BundleLoadError::InvalidActionIndex(action_idx));
}
}
for (action_idx, ivk) in hints.into_iter() {
if let Some((note, recipient, memo)) = bundle.decrypt_output_with_key(action_idx, ivk) {
if !self.add_decrypted_note(
@ -384,15 +406,16 @@ impl Wallet {
recipient,
memo,
) {
return Err(BundleDecryptionError::FvkNotFound(ivk.clone()));
return Err(BundleLoadError::FvkNotFound(ivk.clone()));
}
} else {
return Err(BundleDecryptionError::ActionDecryptionFailed(action_idx));
return Err(BundleLoadError::ActionDecryptionFailed(action_idx));
}
}
Ok(())
}
// Common functionality for add_notes_from_bundle and load_bundle
#[allow(clippy::too_many_arguments)]
fn add_decrypted_note(
&mut self,
@ -444,6 +467,43 @@ impl Wallet {
}
}
/// For each Orchard action in the provided bundle, if the wallet
/// is tracking a note corresponding to the action's revealed nullifer,
/// mark that note as potentially spent.
pub fn add_potential_spends(
&mut self,
txid: &TxId,
bundle: &Bundle<Authorized, Amount>,
) -> Vec<usize> {
// Check for spends of our notes by matching against the nullifiers
// we're tracking, and when we detect one, associate the current
// txid and action as spending the note.
let mut spend_action_idxs = vec![];
for (action_idx, action) in bundle.actions().iter().enumerate() {
let nf = action.nullifier();
// If a nullifier corresponds to one of our notes, add its inpoint as a
// potential spend (the transaction may not end up being mined).
if self.nullifiers.contains_key(nf) {
self.add_potential_spend(
nf,
InPoint {
txid: *txid,
action_idx,
},
);
spend_action_idxs.push(action_idx);
}
}
spend_action_idxs
}
fn add_potential_spend(&mut self, nf: &Nullifier, inpoint: InPoint) {
self.potential_spends
.entry(*nf)
.or_insert_with(BTreeSet::new)
.insert(inpoint);
}
/// Add note commitments for the Orchard components of a transaction to the note
/// commitment tree, and mark the tree at the notes decryptable by this wallet so that
/// in the future we can produce authentication paths to those notes.
@ -525,8 +585,8 @@ impl Wallet {
/// Returns whether the transaction contains any notes either sent to or spent by this
/// wallet.
pub fn tx_contains_my_notes(&self, txid: &TxId) -> bool {
self.wallet_received_notes.get(txid).is_some()
pub fn tx_involves_my_notes(&self, txid: &TxId) -> bool {
self.wallet_received_notes.contains_key(txid)
|| self.nullifiers.values().any(|v| v.txid == *txid)
}
@ -680,41 +740,57 @@ pub struct FFIActionIvk {
ivk_ptr: *mut IncomingViewingKey,
}
/// A C++-allocated function pointer that can push a FFIActionIVK value
/// onto a C++ vector.
/// A C++-allocated function pointer that can pass a FFIActionIVK value
/// to a C++ callback receiver.
pub type ActionIvkPushCb =
unsafe extern "C" fn(obj: Option<FFICallbackReceiver>, value: FFIActionIvk);
/// A C++-allocated function pointer that can pass a FFIActionIVK value
/// to a C++ callback receiver.
pub type SpendIndexPushCb = unsafe extern "C" fn(obj: Option<FFICallbackReceiver>, value: u32);
#[no_mangle]
pub extern "C" fn orchard_wallet_add_notes_from_bundle(
wallet: *mut Wallet,
txid: *const [c_uchar; 32],
bundle: *const Bundle<Authorized, Amount>,
result: Option<FFICallbackReceiver>,
push_cb: Option<ActionIvkPushCb>,
) {
cb_receiver: Option<FFICallbackReceiver>,
action_ivk_push_cb: Option<ActionIvkPushCb>,
spend_idx_push_cb: Option<SpendIndexPushCb>,
) -> bool {
let wallet = unsafe { wallet.as_mut() }.expect("Wallet pointer may not be null");
let txid = TxId::from_bytes(*unsafe { txid.as_ref() }.expect("txid may not be null."));
if let Some(bundle) = unsafe { bundle.as_ref() } {
let added = wallet.add_notes_from_bundle(&txid, bundle);
for (action_idx, ivk) in added.into_iter() {
let mut involved = false;
for (action_idx, ivk) in added.receive_action_metadata.into_iter() {
let action_ivk = FFIActionIvk {
action_idx: action_idx.try_into().unwrap(),
ivk_ptr: Box::into_raw(Box::new(ivk.clone())),
};
unsafe { (push_cb.unwrap())(result, action_ivk) };
unsafe { (action_ivk_push_cb.unwrap())(cb_receiver, action_ivk) };
involved = true;
}
for action_idx in added.spend_action_metadata {
unsafe { (spend_idx_push_cb.unwrap())(cb_receiver, action_idx.try_into().unwrap()) };
involved = true;
}
involved
} else {
false
}
}
#[no_mangle]
pub extern "C" fn orchard_wallet_restore_notes(
pub extern "C" fn orchard_wallet_load_bundle(
wallet: *mut Wallet,
block_height: *const u32,
txid: *const [c_uchar; 32],
bundle: *const Bundle<Authorized, Amount>,
hints: *const FFIActionIvk,
hints_len: usize,
potential_spend_idxs: *const u32,
potential_spend_idxs_len: usize,
) -> bool {
let wallet = unsafe { wallet.as_mut() }.expect("Wallet pointer may not be null");
let block_height = unsafe { block_height.as_ref() }.map(|h| BlockHeight::from(*h));
@ -722,6 +798,8 @@ pub extern "C" fn orchard_wallet_restore_notes(
let bundle = unsafe { bundle.as_ref() }.expect("bundle pointer may not be null.");
let hints_data = unsafe { slice::from_raw_parts(hints, hints_len) };
let potential_spend_idxs =
unsafe { slice::from_raw_parts(potential_spend_idxs, potential_spend_idxs_len) };
let mut hints = BTreeMap::new();
for action_ivk in hints_data {
@ -731,7 +809,7 @@ pub extern "C" fn orchard_wallet_restore_notes(
);
}
match wallet.add_notes_from_bundle_with_hints(block_height, &txid, bundle, hints) {
match wallet.load_bundle(block_height, &txid, bundle, hints, potential_spend_idxs) {
Ok(_) => true,
Err(e) => {
error!("Failed to restore decrypted notes to wallet: {:?}", e);
@ -837,14 +915,14 @@ pub extern "C" fn orchard_wallet_get_ivk_for_address(
}
#[no_mangle]
pub extern "C" fn orchard_wallet_tx_contains_my_notes(
pub extern "C" fn orchard_wallet_tx_involves_my_notes(
wallet: *const Wallet,
txid: *const [c_uchar; 32],
) -> bool {
let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null.");
let txid = TxId::from_bytes(*unsafe { txid.as_ref() }.expect("txid may not be null."));
wallet.tx_contains_my_notes(&txid)
wallet.tx_involves_my_notes(&txid)
}
/// A type used to pass note metadata across the FFI boundary.

View File

@ -44,7 +44,7 @@ CTransaction FakeOrchardTx(const OrchardSpendingKey& sk, libzcash::diversifier_i
return maybeTx.GetTxOrThrow();
}
TEST(OrchardWalletTests, TxContainsMyNotes) {
TEST(OrchardWalletTests, TxInvolvesMyNotes) {
auto consensusParams = RegtestActivateNU5();
OrchardWallet wallet;
@ -58,21 +58,21 @@ TEST(OrchardWalletTests, TxContainsMyNotes) {
wallet.AddNotesIfInvolvingMe(tx);
// Check that we detect the transaction as ours
EXPECT_TRUE(wallet.TxContainsMyNotes(tx.GetHash()));
EXPECT_TRUE(wallet.TxInvolvesMyNotes(tx.GetHash()));
// Create a transaction sending to a different diversified address
auto tx1 = FakeOrchardTx(sk, libzcash::diversifier_index_t(0xffffffffffffffff));
wallet.AddNotesIfInvolvingMe(tx1);
// Check that we also detect this transaction as ours
EXPECT_TRUE(wallet.TxContainsMyNotes(tx1.GetHash()));
EXPECT_TRUE(wallet.TxInvolvesMyNotes(tx1.GetHash()));
// Now generate a new key, and send a transaction to it without adding
// the key to the wallet; it should not be detected as ours.
auto skNotOurs = RandomOrchardSpendingKey();
auto tx2 = FakeOrchardTx(skNotOurs, libzcash::diversifier_index_t(0));
wallet.AddNotesIfInvolvingMe(tx2);
EXPECT_FALSE(wallet.TxContainsMyNotes(tx2.GetHash()));
EXPECT_FALSE(wallet.TxInvolvesMyNotes(tx2.GetHash()));
RegtestDeactivateNU5();
}

View File

@ -14,6 +14,8 @@
#include "rust/orchard/wallet.h"
#include "zcash/address/orchard.hpp"
class OrchardWallet;
class OrchardNoteMetadata
{
private:
@ -51,6 +53,58 @@ public:
}
};
/**
* A container and serialization wrapper for storing information derived from
* a transaction that is relevant to restoring Orchard wallet caches.
*/
class OrchardWalletTxMeta
{
private:
// A map from action index to the IVK belonging to our wallet that decrypts
// that action
std::map<uint32_t, libzcash::OrchardIncomingViewingKey> mapOrchardActionData;
// A vector of the action indices that spend notes belonging to our wallet
std::vector<uint32_t> vActionsSpendingMyNotes;
friend class OrchardWallet;
public:
OrchardWalletTxMeta() {}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
int nVersion = s.GetVersion();
if (!(s.GetType() & SER_GETHASH)) {
READWRITE(nVersion);
}
READWRITE(mapOrchardActionData);
READWRITE(vActionsSpendingMyNotes);
}
const std::map<uint32_t, libzcash::OrchardIncomingViewingKey>& GetMyActionIVKs() const {
return mapOrchardActionData;
}
const std::vector<uint32_t>& GetActionsSpendingMyNotes() const {
return vActionsSpendingMyNotes;
}
bool empty() const {
return (mapOrchardActionData.empty() && vActionsSpendingMyNotes.empty());
}
friend bool operator==(const OrchardWalletTxMeta& a, const OrchardWalletTxMeta& b) {
return (a.mapOrchardActionData == b.mapOrchardActionData &&
a.vActionsSpendingMyNotes == b.vActionsSpendingMyNotes);
}
friend bool operator!=(const OrchardWalletTxMeta& a, const OrchardWalletTxMeta& b) {
return !(a == b);
}
};
class OrchardWallet
{
private:
@ -111,28 +165,36 @@ public:
return orchard_wallet_rewind(inner.get(), (uint32_t) nBlockHeight, &blocksRewoundRet);
}
static void PushOrchardActionIVK(void* orchardActionsIVKRet, RawOrchardActionIVK actionIVK) {
reinterpret_cast<std::map<uint32_t, libzcash::OrchardIncomingViewingKey>*>(orchardActionsIVKRet)->insert_or_assign(
static void PushOrchardActionIVK(void* rec, RawOrchardActionIVK actionIVK) {
reinterpret_cast<OrchardWalletTxMeta*>(rec)->mapOrchardActionData.insert_or_assign(
actionIVK.actionIdx, libzcash::OrchardIncomingViewingKey(actionIVK.ivk)
);
}
static void PushSpendActionIdx(void* rec, uint32_t actionIdx) {
reinterpret_cast<OrchardWalletTxMeta*>(rec)->vActionsSpendingMyNotes.push_back(actionIdx);
}
/**
* Add notes that are decryptable with IVKs for which the wallet
* contains the full viewing key to the wallet, and return the
* mapping from each decrypted Orchard action index to the IVK
* that was used to decrypt that action's note.
* metadata describing the wallet's involvement with this action,
* or std::nullopt if the transaction does not involve the wallet.
*/
std::map<uint32_t, libzcash::OrchardIncomingViewingKey> AddNotesIfInvolvingMe(const CTransaction& tx) {
std::map<uint32_t, libzcash::OrchardIncomingViewingKey> result;
orchard_wallet_add_notes_from_bundle(
std::optional<OrchardWalletTxMeta> AddNotesIfInvolvingMe(const CTransaction& tx) {
OrchardWalletTxMeta txMeta;
if (orchard_wallet_add_notes_from_bundle(
inner.get(),
tx.GetHash().begin(),
tx.GetOrchardBundle().inner.get(),
&result,
PushOrchardActionIVK
);
return result;
&txMeta,
PushOrchardActionIVK,
PushSpendActionIdx
)) {
return txMeta;
} else {
return std::nullopt;
}
}
/**
@ -140,24 +202,25 @@ public:
* Orchard bundle with provided incoming viewing keys, and adds those
* notes to the wallet.
*/
bool RestoreDecryptedNotes(
bool LoadWalletTx(
const std::optional<int> nBlockHeight,
const CTransaction& tx,
std::map<uint32_t, libzcash::OrchardIncomingViewingKey> hints
const OrchardWalletTxMeta& txMeta
) {
std::vector<RawOrchardActionIVK> rawHints;
for (const auto& [action_idx, ivk] : hints) {
for (const auto& [action_idx, ivk] : txMeta.mapOrchardActionData) {
rawHints.push_back({ action_idx, ivk.inner.get() });
}
uint32_t blockHeight = nBlockHeight.has_value() ? (uint32_t) nBlockHeight.value() : 0;
return orchard_wallet_restore_notes(
return orchard_wallet_load_bundle(
inner.get(),
nBlockHeight.has_value() ? &blockHeight : nullptr,
tx.GetHash().begin(),
tx.GetOrchardBundle().inner.get(),
rawHints.data(),
rawHints.size()
);
rawHints.size(),
txMeta.vActionsSpendingMyNotes.data(),
txMeta.vActionsSpendingMyNotes.size());
}
/**
@ -190,8 +253,8 @@ public:
return value;
}
bool TxContainsMyNotes(const uint256& txid) {
return orchard_wallet_tx_contains_my_notes(
bool TxInvolvesMyNotes(const uint256& txid) {
return orchard_wallet_tx_involves_my_notes(
inner.get(),
txid.begin());
}

View File

@ -946,13 +946,13 @@ bool CWallet::LoadCaches()
// Restore decrypted Orchard notes.
for (const auto& [_, walletTx] : mapWallet) {
if (!walletTx.mapOrchardActionData.empty()) {
if (!walletTx.orchardTxMeta.empty()) {
const CBlockIndex* pTxIndex;
std::optional<int> blockHeight;
if (walletTx.GetDepthInMainChain(pTxIndex) > 0) {
blockHeight = pTxIndex->nHeight;
}
if (!orchardWallet.RestoreDecryptedNotes(blockHeight, walletTx, walletTx.mapOrchardActionData)) {
if (!orchardWallet.LoadWalletTx(blockHeight, walletTx, walletTx.orchardTxMeta)) {
LogPrintf("%s: Error: Failed to decrypt previously decrypted notes for txid %s.\n",
__func__, walletTx.GetHash().GetHex());
return false;
@ -2177,6 +2177,9 @@ void CWallet::AddToSpends(const uint256& wtxid)
for (const SpendDescription &spend : thisTx.vShieldedSpend) {
AddToSaplingSpends(spend.nullifier, wtxid);
}
// for Orchard, the effects of this operation are performed by
// AddNotesIfInvolvingMe and LoadUnifiedCaches
}
void CWallet::ClearNoteWitnessCache()
@ -2895,9 +2898,9 @@ bool CWallet::UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx)
wtx.mapSaplingNoteData = tmp;
}
bool unchangedOrchardFlag = (wtxIn.mapOrchardActionData.empty() || wtxIn.mapOrchardActionData == wtx.mapOrchardActionData);
bool unchangedOrchardFlag = (wtxIn.orchardTxMeta.empty() || wtxIn.orchardTxMeta == wtx.orchardTxMeta);
if (!unchangedOrchardFlag) {
wtx.mapOrchardActionData = wtxIn.mapOrchardActionData;
wtx.orchardTxMeta = wtxIn.orchardTxMeta;
}
return !unchangedSproutFlag || !unchangedSaplingFlag || !unchangedOrchardFlag;
@ -2945,12 +2948,15 @@ bool CWallet::AddToWalletIfInvolvingMe(
}
// Orchard
mapOrchardActionData_t orchardActionData;
std::optional<OrchardWalletTxMeta> orchardTxMeta;
if (consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_NU5)) {
orchardActionData = orchardWallet.AddNotesIfInvolvingMe(tx);
orchardTxMeta = orchardWallet.AddNotesIfInvolvingMe(tx);
}
if (fExisted || IsMine(tx) || IsFromMe(tx) || sproutNoteData.size() > 0 || saplingNoteData.size() > 0 || orchardWallet.TxContainsMyNotes(tx.GetHash()))
if (fExisted || IsMine(tx) || IsFromMe(tx) ||
sproutNoteData.size() > 0 ||
saplingNoteData.size() > 0 ||
orchardTxMeta.has_value())
{
CWalletTx wtx(this, tx);
@ -2962,8 +2968,8 @@ bool CWallet::AddToWalletIfInvolvingMe(
wtx.SetSaplingNoteData(saplingNoteData);
}
if (orchardActionData.size() > 0) {
wtx.SetOrchardActionData(orchardActionData);
if (orchardTxMeta.has_value()) {
wtx.SetOrchardTxMeta(orchardTxMeta.value());
}
// Get merkle branch if transaction was found in a block
@ -3563,16 +3569,20 @@ void CWalletTx::SetSaplingNoteData(const mapSaplingNoteData_t& noteData)
}
}
void CWalletTx::SetOrchardActionData(const mapOrchardActionData_t& actionData)
void CWalletTx::SetOrchardTxMeta(OrchardWalletTxMeta txMeta)
{
mapOrchardActionData.clear();
for (const auto& [action_idx, ivk] : actionData) {
if (action_idx < GetOrchardBundle().GetNumActions()) {
mapOrchardActionData.insert_or_assign(action_idx, ivk);
} else {
throw std::logic_error("CWalletTx::SetOrchardActionData(): Invalid action index");
auto numActions = GetOrchardBundle().GetNumActions();
for (const auto& [action_idx, ivk] : txMeta.GetMyActionIVKs()) {
if (action_idx >= numActions) {
throw std::logic_error("CWalletTx::SetOrchardTxMeta(): Invalid action index");
}
}
for (uint32_t action_idx : txMeta.GetActionsSpendingMyNotes()) {
if (action_idx >= numActions) {
throw std::logic_error("CWalletTx::SetOrchardTxMeta(): Invalid action index");
}
}
orchardTxMeta = txMeta;
}
std::pair<SproutNotePlaintext, SproutPaymentAddress> CWalletTx::DecryptSproutNote(
@ -4092,7 +4102,7 @@ int CWallet::ScanForWalletTransactions(
CWalletDB walletdb(strWalletFile, "r+", false);
for (auto hash : myTxHashes) {
CWalletTx wtx = mapWallet[hash];
if (!wtx.mapSaplingNoteData.empty() || !wtx.mapOrchardActionData.empty()) {
if (!wtx.mapSaplingNoteData.empty() || !wtx.orchardTxMeta.empty()) {
if (!walletdb.WriteTx(wtx)) {
LogPrintf("Rescanning... WriteToDisk failed to update Sapling note data for: %s\n", hash.ToString());
}

View File

@ -335,7 +335,6 @@ public:
typedef std::map<JSOutPoint, SproutNoteData> mapSproutNoteData_t;
typedef std::map<SaplingOutPoint, SaplingNoteData> mapSaplingNoteData_t;
typedef std::map<uint32_t, libzcash::OrchardIncomingViewingKey> mapOrchardActionData_t;
/** Sprout note, its location in a transaction, and number of confirmations. */
struct SproutNoteEntry
@ -464,11 +463,8 @@ public:
mapValue_t mapValue;
mapSproutNoteData_t mapSproutNoteData;
mapSaplingNoteData_t mapSaplingNoteData;
mapOrchardActionData_t mapOrchardActionData;
// ORCHARD note data is not stored with the CMerkleTx directly, but is
// accessible via pwallet->orchardWallet. Here we just store the indices
// of the actions that belong to this wallet.
std::vector<size_t> vOrchardActionIndices;
OrchardWalletTxMeta orchardTxMeta;
std::vector<std::pair<std::string, std::string> > vOrderForm;
unsigned int fTimeReceivedIsTxTime;
unsigned int nTimeReceived; //!< time received by this node
@ -582,7 +578,7 @@ public:
}
if (fOverwintered && nVersion >= ZIP225_TX_VERSION) {
READWRITE(mapOrchardActionData);
READWRITE(orchardTxMeta);
}
if (ser_action.ForRead())
@ -619,7 +615,7 @@ public:
void SetSproutNoteData(const mapSproutNoteData_t& noteData);
void SetSaplingNoteData(const mapSaplingNoteData_t& noteData);
void SetOrchardActionData(const mapOrchardActionData_t& actionData);
void SetOrchardTxMeta(OrchardWalletTxMeta actionData);
std::pair<libzcash::SproutNotePlaintext, libzcash::SproutPaymentAddress> DecryptSproutNote(
JSOutPoint jsop) const;