From d18e246a92db14b5dc704a46484d3b229c78ef65 Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 14:21:20 -0400 Subject: [PATCH 01/45] Remove unused sprout datastructures --- src/wallet/asyncrpcoperation_sendmany.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index 340c0c4cb..f71c6d2e2 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -96,16 +96,9 @@ private: SpendingKey spendingkey_; CScript opret_ = CScript(); - uint256 joinSplitPubKey_; - unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES]; - - // The key is the result string from calling JSOutPoint::ToString() - std::unordered_map jsopWitnessAnchorMap; - std::vector t_outputs_; std::vector z_outputs_; std::vector t_inputs_; - //std::vector z_sprout_inputs_; std::vector z_sapling_inputs_; TransactionBuilder builder_; From bd730cb92780d4aa3712e6bef84b07b93d30b593 Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 14:47:43 -0400 Subject: [PATCH 02/45] Lock zins inside z_sendmany async operation --- src/wallet/asyncrpcoperation_sendmany.cpp | 46 +++++++++++++++++++++++ src/wallet/asyncrpcoperation_sendmany.h | 5 +++ 2 files changed, 51 insertions(+) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 311fc217c..f46a458dd 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -119,6 +119,11 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( } else { LogPrint("zrpc", "%s: z_sendmany initialized\n", getId()); } + + // Lock UTXOs + //lock_utxos(); + // Lock shielded input notes + lock_notes(); } AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() { @@ -185,6 +190,9 @@ void AsyncRPCOperation_sendmany::main() { s += strprintf(", error=%s)\n", getErrorMessage()); } LogPrintf("%s",s); + + //unlock_utxos(); // clean up + unlock_notes(); // clean up } // Notes: @@ -781,3 +789,41 @@ UniValue AsyncRPCOperation_sendmany::getStatus() const { obj.push_back(Pair("params", contextinfo_ )); return obj; } + +/* TODO: support locking taddr utxo inputs + +// Lock input utxos + void AsyncRPCOperation_sendmany::lock_utxos() { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto utxo : t_inputs_) { + COutPoint outpt(utxo.txid, utxo.vout); + pwalletMain->LockCoin(outpt); + } +} + +// Unlock input utxos +void AsyncRPCOperation_sendmany::unlock_utxos() { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto utxo : t_inputs_) { + COutPoint outpt(utxo.txid, utxo.vout); + pwalletMain->UnlockCoin(outpt); + } +} + +*/ + +// Lock input notes +void AsyncRPCOperation_sendmany::lock_notes() { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto note : z_sapling_inputs_) { + pwalletMain->LockNote(note.op); + } +} + +// Unlock input notes +void AsyncRPCOperation_sendmany::unlock_notes() { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto note : z_sapling_inputs_) { + pwalletMain->UnlockNote(note.op); + } +} diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index f71c6d2e2..8684cf97c 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -112,6 +112,11 @@ private: bool main_impl(); void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error + + void lock_utxos(); + void unlock_utxos(); + void lock_notes(); + void unlock_notes(); }; From 8980fceadf79c9158a572448a63355165682a74a Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 16:07:10 -0400 Subject: [PATCH 03/45] Add logging for the locking+unlocking of zins --- src/wallet/asyncrpcoperation_sendmany.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index f46a458dd..77c090d3a 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -124,6 +124,7 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( //lock_utxos(); // Lock shielded input notes lock_notes(); + LogPrint("zrpc", "%s: z_sendmany input notes locked\n", getId()); } AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() { @@ -193,6 +194,7 @@ void AsyncRPCOperation_sendmany::main() { //unlock_utxos(); // clean up unlock_notes(); // clean up + LogPrint("zrpc", "%s: z_sendmany input notes unlocked\n", getId()); } // Notes: From 1b527bc8389a9907d959e6f1ac3dead3a77ce1fe Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 16:07:47 -0400 Subject: [PATCH 04/45] Add 'locked' key to the output of z_listunspent --- src/wallet/rpcwallet.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ef1be630a..5433df6af 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4218,8 +4218,12 @@ UniValue z_listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) libzcash::SaplingFullViewingKey fvk; pwalletMain->GetSaplingIncomingViewingKey(boost::get(entry.address), ivk); pwalletMain->GetSaplingFullViewingKey(ivk, fvk); - bool hasSaplingSpendingKey = pwalletMain->HaveSaplingSpendingKey(fvk); + const bool hasSaplingSpendingKey = pwalletMain->HaveSaplingSpendingKey(fvk); obj.push_back(Pair("spendable", hasSaplingSpendingKey)); + + const bool isLocked = pwalletMain->IsLockedNote(entry.op); + obj.push_back(Pair("locked", isLocked)); + obj.push_back(Pair("address", EncodePaymentAddress(entry.address))); obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.note.value())))); // note.value() is equivalent to plaintext.value() obj.push_back(Pair("memo", HexStr(entry.memo))); From 8d93e5dfb42c8b9c0fcaab0e8680873928af6ecc Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 16:11:09 -0400 Subject: [PATCH 05/45] Add locked key to z_listunspent rpc docs --- src/wallet/rpcwallet.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5433df6af..d66922437 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4113,6 +4113,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) " \"outindex\" (sapling) : n (numeric) the output index\n" " \"confirmations\" : n (numeric) the number of confirmations\n" " \"spendable\" : true|false (boolean) true if note can be spent by wallet, false if note has zero confirmations, false if address is watchonly\n" + " \"locked\" : true|false (boolean) true if note is part of an in-process async operation to spend it, such as z_mergetoaddress/z_sendmany/etc\n" " \"address\" : \"address\", (string) the shielded address\n" " \"amount\": xxxxx, (numeric) the amount of value in the note\n" " \"memo\": xxxxx, (string) hexademical string representation of memo field\n" From 40337a04b3bfbb4eb318cb1cb11ae486db758e42 Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 16:57:29 -0400 Subject: [PATCH 06/45] Remove unused joinsplit variables --- src/wallet/asyncrpcoperation_shieldcoinbase.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/wallet/asyncrpcoperation_shieldcoinbase.h b/src/wallet/asyncrpcoperation_shieldcoinbase.h index bd606e23e..b9a49611d 100644 --- a/src/wallet/asyncrpcoperation_shieldcoinbase.h +++ b/src/wallet/asyncrpcoperation_shieldcoinbase.h @@ -74,9 +74,6 @@ private: CAmount fee_; PaymentAddress tozaddr_; - uint256 joinSplitPubKey_; - unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES]; - std::vector inputs_; TransactionBuilder builder_; @@ -87,7 +84,6 @@ private: void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error void lock_utxos(); - void unlock_utxos(); }; From 982986bb707d0046361b1c4358d3e338e49cbd69 Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 17:00:28 -0400 Subject: [PATCH 07/45] Remove unused joinsplit variables from z_mergetoaddress --- src/wallet/asyncrpcoperation_mergetoaddress.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wallet/asyncrpcoperation_mergetoaddress.h b/src/wallet/asyncrpcoperation_mergetoaddress.h index f4360e13c..ab4a949eb 100644 --- a/src/wallet/asyncrpcoperation_mergetoaddress.h +++ b/src/wallet/asyncrpcoperation_mergetoaddress.h @@ -90,9 +90,6 @@ private: CTxDestination toTaddr_; PaymentAddress toPaymentAddress_; - uint256 joinSplitPubKey_; - unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES]; - std::vector utxoInputs_; std::vector saplingNoteInputs_; From dcdc9b4d693367885e8bb5f2145676789d8e03fc Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 17:37:25 -0400 Subject: [PATCH 08/45] We no longer need to include this joinsplit header --- src/wallet/asyncrpcoperation_sendmany.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index 8684cf97c..316b7ac32 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -25,7 +25,6 @@ #include "amount.h" #include "primitives/transaction.h" #include "transaction_builder.h" -#include "zcash/JoinSplit.hpp" #include "zcash/Address.hpp" #include "wallet.h" From 532b15b45e0c5ed27bdfab2f2b595659704f2a6a Mon Sep 17 00:00:00 2001 From: Duke Date: Sun, 18 May 2025 17:38:03 -0400 Subject: [PATCH 09/45] Lock and unlock sapling notes during consolidations --- ...asyncrpcoperation_saplingconsolidation.cpp | 32 +++++++++++++++++++ .../asyncrpcoperation_saplingconsolidation.h | 5 +++ 2 files changed, 37 insertions(+) diff --git a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp index 41455d360..755564492 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.cpp +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.cpp @@ -74,6 +74,8 @@ void AsyncRPCOperation_saplingconsolidation::main() { } LogPrintf("%s", s); + unlock_notes(); // clean up + LogPrint("zrpc", "%s: consolidation input notes unlocked\n", getId()); } bool AsyncRPCOperation_saplingconsolidation::main_impl() { @@ -102,6 +104,11 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() { return true; } + // store sapling inputs so we can correctly lock and unlock them + for (auto entry : saplingEntries) { + z_sapling_inputs_.push_back(entry); + } + if (fConsolidationMapUsed) { const vector& v = mapMultiArgs["-consolidatesaplingaddress"]; for(int i = 0; i < v.size(); i++) { @@ -233,6 +240,10 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() { break; } + // Lock shielded input notes + lock_notes(); + LogPrint("zrpc", "%s: consolidation input notes locked\n", getId()); + if(pwalletMain->CommitAutomatedTx(tx)) { LogPrintf("%s: Committed consolidation transaction with txid=%s\n",opid, tx.GetHash().ToString()); amountConsolidated += actualAmountToSend; @@ -240,10 +251,15 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() { numTxCreated++; } else { LogPrintf("%s: Consolidation transaction FAILED in CommitTransaction, txid=%s\n",opid , tx.GetHash().ToString()); + + unlock_notes(); + LogPrint("zrpc", "%s: consolidatoin input notes unlocked\n", getId()); + setConsolidationResult(numTxCreated, amountConsolidated, consolidationTxIds); status = false; break; } + } } @@ -275,3 +291,19 @@ UniValue AsyncRPCOperation_saplingconsolidation::getStatus() const { obj.push_back(Pair("target_height", targetHeight_)); return obj; } + +// Lock input notes +void AsyncRPCOperation_saplingconsolidation::lock_notes() { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto note : z_sapling_inputs_) { + pwalletMain->LockNote(note.op); + } +} + +// Unlock input notes +void AsyncRPCOperation_saplingconsolidation::unlock_notes() { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto note : z_sapling_inputs_) { + pwalletMain->UnlockNote(note.op); + } +} diff --git a/src/wallet/asyncrpcoperation_saplingconsolidation.h b/src/wallet/asyncrpcoperation_saplingconsolidation.h index 4cc370431..675a56ad7 100644 --- a/src/wallet/asyncrpcoperation_saplingconsolidation.h +++ b/src/wallet/asyncrpcoperation_saplingconsolidation.h @@ -8,6 +8,7 @@ #include "univalue.h" #include "zcash/Address.hpp" #include "zcash/zip32.h" +#include "wallet.h" // for SaplingNoteEntry //Default fee used for consolidation transactions, in puposhis static const CAmount DEFAULT_CONSOLIDATION_FEE = 10000; @@ -32,10 +33,14 @@ public: virtual UniValue getStatus() const; + void lock_notes(); + void unlock_notes(); + private: int targetHeight_; bool main_impl(); + std::vector z_sapling_inputs_; void setConsolidationResult(int numTxCreated, const CAmount& amountConsolidated, const std::vector& consolidationTxIds); From 2bd3b510ed6596036be0a49aee6170b47382f65a Mon Sep 17 00:00:00 2001 From: Duke Date: Mon, 19 May 2025 08:56:53 -0400 Subject: [PATCH 10/45] Also lock transparent input utxos in z_sendmany, like z_mergetoaddress already does --- src/wallet/asyncrpcoperation_sendmany.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 77c090d3a..f5809c58d 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -121,7 +121,7 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( } // Lock UTXOs - //lock_utxos(); + lock_utxos(); // Lock shielded input notes lock_notes(); LogPrint("zrpc", "%s: z_sendmany input notes locked\n", getId()); @@ -192,7 +192,7 @@ void AsyncRPCOperation_sendmany::main() { } LogPrintf("%s",s); - //unlock_utxos(); // clean up + unlock_utxos(); // clean up unlock_notes(); // clean up LogPrint("zrpc", "%s: z_sendmany input notes unlocked\n", getId()); } @@ -792,13 +792,14 @@ UniValue AsyncRPCOperation_sendmany::getStatus() const { return obj; } -/* TODO: support locking taddr utxo inputs - // Lock input utxos void AsyncRPCOperation_sendmany::lock_utxos() { LOCK2(cs_main, pwalletMain->cs_wallet); for (auto utxo : t_inputs_) { - COutPoint outpt(utxo.txid, utxo.vout); + uint256 txid = std::get<0>(utxo); + int vout = std::get<1>(utxo); + + COutPoint outpt(txid, vout); pwalletMain->LockCoin(outpt); } } @@ -807,13 +808,14 @@ UniValue AsyncRPCOperation_sendmany::getStatus() const { void AsyncRPCOperation_sendmany::unlock_utxos() { LOCK2(cs_main, pwalletMain->cs_wallet); for (auto utxo : t_inputs_) { - COutPoint outpt(utxo.txid, utxo.vout); + uint256 txid = std::get<0>(utxo); + int vout = std::get<1>(utxo); + + COutPoint outpt(txid, vout); pwalletMain->UnlockCoin(outpt); } } -*/ - // Lock input notes void AsyncRPCOperation_sendmany::lock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); From cb1b3df55301309e37b13a4c6d8560f5e28ca201 Mon Sep 17 00:00:00 2001 From: Duke Date: Mon, 19 May 2025 09:20:54 -0400 Subject: [PATCH 11/45] Remove unused sprout junk --- src/wallet/asyncrpcoperation_sendmany.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index 316b7ac32..42ae2e505 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -45,12 +45,6 @@ typedef std::tuple SendManyRecipient; // Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase) typedef std::tuple SendManyInputUTXO; -// A struct to help us track the witness and anchor for a given JSOutPoint -struct WitnessAnchorData { - boost::optional witness; - uint256 anchor; -}; - class AsyncRPCOperation_sendmany : public AsyncRPCOperation { public: AsyncRPCOperation_sendmany( @@ -169,6 +163,4 @@ public: } }; - #endif /* ASYNCRPCOPERATION_SENDMANY_H */ - From d7fe1d43cad5794d8560b8a049fb982578dcbb48 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 29 May 2025 18:43:30 -0400 Subject: [PATCH 12/45] Debug logs when locking/unlocking a note --- src/wallet/wallet.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b4bb6ded9..30f3fe714 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4587,12 +4587,14 @@ void CWallet::LockNote(const SaplingOutPoint& output) { AssertLockHeld(cs_wallet); setLockedSaplingNotes.insert(output); + fprintf(stderr,"%s: locking note\n", __func__); } void CWallet::UnlockNote(const SaplingOutPoint& output) { AssertLockHeld(cs_wallet); setLockedSaplingNotes.erase(output); + fprintf(stderr,"%s: unlocking note\n", __func__); } void CWallet::UnlockAllSaplingNotes() From b43a4f26531a27ee47fdad4a1c415c5afdafb102 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 30 May 2025 08:22:16 -0400 Subject: [PATCH 13/45] Remove unused code and formatting --- .../asyncrpcoperation_mergetoaddress.cpp | 30 +++++-------------- src/wallet/asyncrpcoperation_sendmany.cpp | 24 +-------------- 2 files changed, 8 insertions(+), 46 deletions(-) diff --git a/src/wallet/asyncrpcoperation_mergetoaddress.cpp b/src/wallet/asyncrpcoperation_mergetoaddress.cpp index e09068ac4..c2784d5a2 100644 --- a/src/wallet/asyncrpcoperation_mergetoaddress.cpp +++ b/src/wallet/asyncrpcoperation_mergetoaddress.cpp @@ -53,13 +53,13 @@ using namespace libzcash; extern UniValue sendrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress( - boost::optional builder, - CMutableTransaction contextualTx, - std::vector utxoInputs, - std::vector saplingNoteInputs, - MergeToAddressRecipient recipient, - CAmount fee, - UniValue contextInfo) : + boost::optional builder, + CMutableTransaction contextualTx, + std::vector utxoInputs, + std::vector saplingNoteInputs, + MergeToAddressRecipient recipient, + CAmount fee, + UniValue contextInfo) : tx_(contextualTx), utxoInputs_(utxoInputs), saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo) { if (fee < 0 || fee > MAX_MONEY) { @@ -192,22 +192,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl() size_t numInputs = utxoInputs_.size(); - /* - // Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects - size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0); - { - LOCK(cs_main); - if (NetworkUpgradeActive(chainActive.Height() + 1, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { - limit = 0; - } - } - if (limit > 0 && numInputs > limit) { - throw JSONRPCError(RPC_WALLET_ERROR, - strprintf("Number of transparent inputs %d is greater than mempooltxinputlimit of %d", - numInputs, limit)); - } - */ - CAmount t_inputs_total = 0; for (MergeToAddressInputUTXO& t : utxoInputs_) { t_inputs_total += std::get<1>(t); diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index f5809c58d..b796b101f 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -205,12 +205,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { assert(isfromtaddr_ != isfromzaddr_); - /* TODO: this needs to allow DPoW addresses. Consensus-time checks do it correctly. - if(t_outputs_.size() > 0) { - throw JSONRPCError(RPC_WALLET_ERROR, "Extreme Privacy! You cannot send to a transparent address."); - } - */ - bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1); bool isMultipleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()>=1); bool isPureTaddrOnlyTx = (isfromtaddr_ && z_outputs_.size() == 0); @@ -319,23 +313,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { t_inputs_ = selectedTInputs; t_inputs_total = selectedUTXOAmount; - /* - // Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects - const size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0); - { - LOCK(cs_main); - if (NetworkUpgradeActive(chainActive.Height() + 1, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { - limit = 0; - } - } - if (limit > 0) { - size_t n = t_inputs_.size(); - if (n > limit) { - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Too many transparent inputs %zu > limit %zu", n, limit)); - } - } - */ - // update the transaction with these inputs if (isUsingBuilder_) { CScript scriptPubKey; @@ -680,6 +657,7 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() { pwalletMain->GetFilteredNotes(saplingEntries, fromaddress_, mindepth_); } + // store sapling inputs so we can correctly lock and unlock them for (auto entry : saplingEntries) { z_sapling_inputs_.push_back(entry); std::string data(entry.memo.begin(), entry.memo.end()); From 88143a87fc659851ca191709b2e5fb68bb9888dc Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 30 May 2025 08:22:31 -0400 Subject: [PATCH 14/45] Clean up locks if z_sendmany is cancelled --- src/wallet/asyncrpcoperation_sendmany.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index b796b101f..a78fefb51 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -131,8 +131,13 @@ AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() { } void AsyncRPCOperation_sendmany::main() { - if (isCancelled()) + + // clean up locks if we are cancelled + if (isCancelled()) { + unlock_utxos(); + unlock_notes(); return; + } set_state(OperationStatus::EXECUTING); start_execution_clock(); @@ -797,6 +802,7 @@ void AsyncRPCOperation_sendmany::unlock_utxos() { // Lock input notes void AsyncRPCOperation_sendmany::lock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); + fprintf(stderr,"%s: found %d notes to lock\n", z_sapling_inputs_.count() ); for (auto note : z_sapling_inputs_) { pwalletMain->LockNote(note.op); } @@ -805,6 +811,7 @@ void AsyncRPCOperation_sendmany::lock_notes() { // Unlock input notes void AsyncRPCOperation_sendmany::unlock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); + fprintf(stderr,"%s: found %d notes to unlock\n", z_sapling_inputs_.count() ); for (auto note : z_sapling_inputs_) { pwalletMain->UnlockNote(note.op); } From dba46b2ba63a728fba2195bf2ac12e775d5d47c9 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 30 May 2025 08:40:42 -0400 Subject: [PATCH 15/45] Wait until after find_unspent_notes() to lock notes in z_sendmany --- src/wallet/asyncrpcoperation_sendmany.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index a78fefb51..03507c7d5 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -120,11 +120,6 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( LogPrint("zrpc", "%s: z_sendmany initialized\n", getId()); } - // Lock UTXOs - lock_utxos(); - // Lock shielded input notes - lock_notes(); - LogPrint("zrpc", "%s: z_sendmany input notes locked\n", getId()); } AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() { @@ -240,6 +235,15 @@ bool AsyncRPCOperation_sendmany::main_impl() { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address."); } + // Now that find_unspent_notes() has run, we now know which + // notes need to be locked + + // Lock UTXOs + lock_utxos(); + // Lock shielded input notes + lock_notes(); + LogPrint("zrpc", "%s: z_sendmany input notes locked\n", getId()); + CAmount t_inputs_total = 0; for (SendManyInputUTXO & t : t_inputs_) { t_inputs_total += std::get<2>(t); @@ -802,7 +806,7 @@ void AsyncRPCOperation_sendmany::unlock_utxos() { // Lock input notes void AsyncRPCOperation_sendmany::lock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); - fprintf(stderr,"%s: found %d notes to lock\n", z_sapling_inputs_.count() ); + fprintf(stderr,"%s: found %lu notes to lock\n", __func__, z_sapling_inputs_.size() ); for (auto note : z_sapling_inputs_) { pwalletMain->LockNote(note.op); } @@ -811,7 +815,7 @@ void AsyncRPCOperation_sendmany::lock_notes() { // Unlock input notes void AsyncRPCOperation_sendmany::unlock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); - fprintf(stderr,"%s: found %d notes to unlock\n", z_sapling_inputs_.count() ); + fprintf(stderr,"%s: found %lu notes to unlock\n", __func__, z_sapling_inputs_.size() ); for (auto note : z_sapling_inputs_) { pwalletMain->UnlockNote(note.op); } From e38e10acddb573f1dbe4d2079c7b511d22bdaf54 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 30 May 2025 09:54:44 -0400 Subject: [PATCH 16/45] Show a prefix of the first 8 chars of the hash for note debug lines --- src/wallet/wallet.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 30f3fe714..1ab4447cc 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1840,7 +1840,7 @@ void CWallet::GetSaplingNoteWitnesses(std::vector notes, auto noteData = mapWallet[note.hash].mapSaplingNoteData; auto nWitnesses = noteData[note].witnesses.size(); if (mapWallet.count(note.hash) && noteData.count(note) && nWitnesses > 0) { - fprintf(stderr,"%s: Found %lu witnesses for note %s\n", __func__, nWitnesses, note.hash.ToString().c_str() ); + fprintf(stderr,"%s: Found %lu witnesses for note %s...\n", __func__, nWitnesses, note.hash.ToString().substr(0,8).c_str() ); witnesses[i] = noteData[note].witnesses.front(); if (!rt) { //fprintf(stderr,"%s: Setting witness root\n",__func__); @@ -4587,14 +4587,14 @@ void CWallet::LockNote(const SaplingOutPoint& output) { AssertLockHeld(cs_wallet); setLockedSaplingNotes.insert(output); - fprintf(stderr,"%s: locking note\n", __func__); + fprintf(stderr,"%s: locking note %s...\n", __func__, output.hash.ToString().substr(0,8).c_str() ); } void CWallet::UnlockNote(const SaplingOutPoint& output) { AssertLockHeld(cs_wallet); setLockedSaplingNotes.erase(output); - fprintf(stderr,"%s: unlocking note\n", __func__); + fprintf(stderr,"%s: unlocking note %s...\n", __func__, output.hash.ToString().substr(0,8).c_str() ); } void CWallet::UnlockAllSaplingNotes() From 46070c65fd94fca4118b7f6ec9251dda1722de04 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 30 May 2025 13:56:38 -0400 Subject: [PATCH 17/45] Remove comment which is no longer correct, z_mergetoaddress has locked notes for a long time now --- src/wallet/asyncrpcoperation_mergetoaddress.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wallet/asyncrpcoperation_mergetoaddress.cpp b/src/wallet/asyncrpcoperation_mergetoaddress.cpp index c2784d5a2..6c8975b23 100644 --- a/src/wallet/asyncrpcoperation_mergetoaddress.cpp +++ b/src/wallet/asyncrpcoperation_mergetoaddress.cpp @@ -182,7 +182,6 @@ void AsyncRPCOperation_mergetoaddress::main() // Notes: // 1. #1359 Currently there is no limit set on the number of inputs+outputs, so size of tx could be invalid. -// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them. bool AsyncRPCOperation_mergetoaddress::main_impl() { assert(isToTaddr_ != isToZaddr_); From 4fde93960ba833eca0bdf3e17d64131edb79b87c Mon Sep 17 00:00:00 2001 From: Duke Date: Sat, 31 May 2025 10:09:24 -0400 Subject: [PATCH 18/45] Upstream zec issue 1360 is mostly about sprout and sending to taddrs, which is not relevant --- src/wallet/asyncrpcoperation_sendmany.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 03507c7d5..349e2e010 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -199,8 +199,7 @@ void AsyncRPCOperation_sendmany::main() { // Notes: // 1. #1159 Currently there is no limit set on the number of shielded spends, so size of tx could be invalid. -// 2. #1360 Note selection is not optimal -// 3. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them +// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them bool AsyncRPCOperation_sendmany::main_impl() { assert(isfromtaddr_ != isfromzaddr_); From b5db300aed06112a4060ba14a5edd3512778ff47 Mon Sep 17 00:00:00 2001 From: Duke Date: Sat, 31 May 2025 11:07:52 -0400 Subject: [PATCH 19/45] Clean up some comments --- src/wallet/rpcwallet.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index fab9db288..4db196f7d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5131,7 +5131,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) fprintf(stderr,"%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials); fromaddress = vPotentialAddresses[ GetRandInt(nPotentials) ]; } else { - // Automagic zaddr source election failed, exit honorably + // Automagic zaddr source selection failed, exit honorably throw JSONRPCError(RPC_INVALID_PARAMETER, "No single zaddr currently has enough funds to make that transaction, you may need to wait for confirmations."); } } else { @@ -5295,18 +5295,6 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; mtx.nVersion = SAPLING_TX_VERSION; unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; - /* - const bool sapling = true; //NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { - if (!sapling) { - if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { - mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; - mtx.nVersion = OVERWINTER_TX_VERSION; - } else { - mtx.fOverwintered = false; - mtx.nVersion = 2; - } - } - */ // As a sanity check, estimate and verify that the size of the transaction will be valid. // Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid. @@ -5956,7 +5944,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp // Contextual transaction we will build on CMutableTransaction contextualTx; //= CreateNewContextualCMutableTransaction( Params().GetConsensus(), nextBlockHeight); - // Builder (used if Sapling addresses are involved) + // Builder (used if zaddrs are involved) boost::optional builder; if (isToSaplingZaddr || saplingNoteInputs.size() > 0) { builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain); From 007f2b31af9a15fd497d0bc932ff719dca2a0809 Mon Sep 17 00:00:00 2001 From: Duke Date: Sat, 31 May 2025 11:50:38 -0400 Subject: [PATCH 20/45] Keep a list of notes we are spending and must lock which is different from the list of all notes we might spend --- src/wallet/asyncrpcoperation_sendmany.cpp | 33 +++++++++++++---------- src/wallet/asyncrpcoperation_sendmany.h | 4 +++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 349e2e010..f97795ab6 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -234,14 +234,8 @@ bool AsyncRPCOperation_sendmany::main_impl() { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address."); } - // Now that find_unspent_notes() has run, we now know which - // notes need to be locked - // Lock UTXOs lock_utxos(); - // Lock shielded input notes - lock_notes(); - LogPrint("zrpc", "%s: z_sendmany input notes locked\n", getId()); CAmount t_inputs_total = 0; for (SendManyInputUTXO & t : t_inputs_) { @@ -412,7 +406,14 @@ bool AsyncRPCOperation_sendmany::main_impl() { std::vector ops; std::vector notes; CAmount sum = 0; - for (auto t : z_sapling_inputs_) { + + //NOTE: z_sapling_inputs_ is a list of all potential notes to spend + // saplingNoteInputs_ is a list of notes we will actually spend + // and need to lock. It is a subset of z_sapling_inputs_ + for (const auto t : z_sapling_inputs_) { + // keep track of notes to lock later on in lock_notes() + saplingNoteInputs_.emplace_back(t.op, t.note, t.note.value() ); + ops.push_back(t.op); notes.push_back(t.note); sum += t.note.value(); @@ -421,6 +422,9 @@ bool AsyncRPCOperation_sendmany::main_impl() { } } + // Lock shielded input notes (zins) stored in saplingNoteInputs + lock_notes(); + // Fetch Sapling anchor and witnesses //LogPrintf("%s: Gathering anchors and witnesses\n", __FUNCTION__); uint256 anchor; @@ -665,9 +669,10 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() { pwalletMain->GetFilteredNotes(saplingEntries, fromaddress_, mindepth_); } - // store sapling inputs so we can correctly lock and unlock them + for (auto entry : saplingEntries) { z_sapling_inputs_.push_back(entry); + std::string data(entry.memo.begin(), entry.memo.end()); LogPrint("zrpcunsafe", "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n", getId(), @@ -805,17 +810,17 @@ void AsyncRPCOperation_sendmany::unlock_utxos() { // Lock input notes void AsyncRPCOperation_sendmany::lock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); - fprintf(stderr,"%s: found %lu notes to lock\n", __func__, z_sapling_inputs_.size() ); - for (auto note : z_sapling_inputs_) { - pwalletMain->LockNote(note.op); + fprintf(stderr,"%s: found %lu notes to lock\n", __func__, saplingNoteInputs_.size() ); + for (auto note : saplingNoteInputs_) { + pwalletMain->LockNote(std::get<0>(note)); } } // Unlock input notes void AsyncRPCOperation_sendmany::unlock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); - fprintf(stderr,"%s: found %lu notes to unlock\n", __func__, z_sapling_inputs_.size() ); - for (auto note : z_sapling_inputs_) { - pwalletMain->UnlockNote(note.op); + fprintf(stderr,"%s: found %lu notes to unlock\n", __func__, saplingNoteInputs_.size() ); + for (auto note : saplingNoteInputs_) { + pwalletMain->UnlockNote(std::get<0>(note)); } } diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index 42ae2e505..271daba3d 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -45,6 +45,9 @@ typedef std::tuple SendManyRecipient; // Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase) typedef std::tuple SendManyInputUTXO; +// Input note is a tuple of output, note, amount +typedef std::tuple SendManyInputSaplingNote; + class AsyncRPCOperation_sendmany : public AsyncRPCOperation { public: AsyncRPCOperation_sendmany( @@ -93,6 +96,7 @@ private: std::vector z_outputs_; std::vector t_inputs_; std::vector z_sapling_inputs_; + std::vector saplingNoteInputs_; TransactionBuilder builder_; CTransaction tx_; From bd52fc5ee61d1490e36a0aa98f2ebaf48b25908a Mon Sep 17 00:00:00 2001 From: Duke Date: Mon, 11 Aug 2025 09:36:45 -0400 Subject: [PATCH 21/45] Try harder to avoid selecting locked notes to spend --- src/wallet/asyncrpcoperation_sendmany.cpp | 28 ++++++++++++++++++----- src/wallet/wallet.cpp | 2 +- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index f97795ab6..a6d708e53 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -129,8 +129,9 @@ void AsyncRPCOperation_sendmany::main() { // clean up locks if we are cancelled if (isCancelled()) { - unlock_utxos(); + // We are more likely to be spending notes, so unlock them first unlock_notes(); + unlock_utxos(); return; } @@ -192,9 +193,9 @@ void AsyncRPCOperation_sendmany::main() { } LogPrintf("%s",s); - unlock_utxos(); // clean up unlock_notes(); // clean up - LogPrint("zrpc", "%s: z_sendmany input notes unlocked\n", getId()); + unlock_utxos(); // clean up + LogPrint("zrpc", "%s: z_sendmany input notes+utxos unlocked\n", getId()); } // Notes: @@ -411,7 +412,14 @@ bool AsyncRPCOperation_sendmany::main_impl() { // saplingNoteInputs_ is a list of notes we will actually spend // and need to lock. It is a subset of z_sapling_inputs_ for (const auto t : z_sapling_inputs_) { - // keep track of notes to lock later on in lock_notes() + // locked status of these inputs may have changed, check again + const bool isLocked = pwalletMain->IsLockedNote(t.op); + if (isLocked) { + LogPrintf("%s: skipping locked note %s\n", __func__, t.op.hash.ToString().substr(0,10).c_str()); + continue; + } + + // keep track of currently unlocked notes to lock later on in lock_notes() saplingNoteInputs_.emplace_back(t.op, t.note, t.note.value() ); ops.push_back(t.op); @@ -658,7 +666,7 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) { return t_inputs_.size() > 0; } - +// find unspent notes which are also unlocked bool AsyncRPCOperation_sendmany::find_unspent_notes() { if(fZdebug) LogPrintf("%s: For address %s depth=%d\n", __FUNCTION__, fromaddress_.c_str(), mindepth_); @@ -666,11 +674,19 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() { std::vector saplingEntries; { LOCK2(cs_main, pwalletMain->cs_wallet); + // GetFilteredNotes ignores locked notes by default pwalletMain->GetFilteredNotes(saplingEntries, fromaddress_, mindepth_); } - for (auto entry : saplingEntries) { + // locked status of note may have changed since GetFilteredNotes() + // returned data, so we check again + const bool isLocked = pwalletMain->IsLockedNote(entry.op); + if (isLocked) { + LogPrintf("%s: skipping locked note %s:%d\n", __func__, entry.op.hash.ToString().substr(0,10).c_str(), entry.op.n); + continue; + } + z_sapling_inputs_.push_back(entry); std::string data(entry.memo.begin(), entry.memo.end()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cceef53e2..75445e8e1 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4942,8 +4942,8 @@ void CWallet::GetFilteredNotes( } // skip locked notes - // TODO: Add locking for Sapling notes -> done if (ignoreLocked && IsLockedNote(op)) { + LogPrintf("%s: skipping locked note %s\n", __func__, op.hash.ToString().substr(0,10).c_str()); continue; } From cae942a5c94a2af2d099da0b226b4f2f2952a1ec Mon Sep 17 00:00:00 2001 From: Duke Date: Wed, 20 Aug 2025 15:15:36 -0400 Subject: [PATCH 22/45] z_listlockunspent --- src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + src/wallet/rpcwallet.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 6373d554d..a16dde687 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -460,6 +460,7 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "walletlock", &walletlock, true }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, true }, { "wallet", "walletpassphrase", &walletpassphrase, true }, + { "wallet", "z_listlockunspent", &z_listlockunspent, false }, { "wallet", "z_listreceivedbyaddress",&z_listreceivedbyaddress,false }, { "wallet", "z_listreceivedaddress", &z_listreceivedaddress, false }, { "wallet", "z_getbalance", &z_getbalance, false }, diff --git a/src/rpc/server.h b/src/rpc/server.h index 9a66fea43..eff98f9c2 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -295,6 +295,7 @@ extern UniValue getrawtransaction(const UniValue& params, bool fHelp, const CPub extern UniValue listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue lockunspent(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue listlockunspent(const UniValue& params, bool fHelp, const CPubKey& mypk); +extern UniValue z_listlockunspent(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue createrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue decoderawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue decodescript(const UniValue& params, bool fHelp, const CPubKey& mypk); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f6583bd17..e1ff70cfd 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2645,6 +2645,45 @@ UniValue lockunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) return true; } +UniValue z_listlockunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() > 0) + throw runtime_error( + "z_listlockunspent\n" + "\nReturns list of temporarily unspendable outputs.\n" + "\nResult:\n" + "[\n" + " {\n" + " \"txid\" : \"transactionid\", (string) The transaction id locked\n" + " \"vout\" : n (numeric) The vout value\n" + " }\n" + " ,...\n" + "]\n" + "\nExamples:\n" + "\nList the locked Sapling notes\n" + + HelpExampleCli("z_listlockunspent", "") + + "\nAs a json rpc call\n" + + HelpExampleRpc("z_listlockunspent", "") + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + vector ops = pwalletMain->ListLockedSaplingNotes(); + + UniValue ret(UniValue::VARR); + + BOOST_FOREACH(SaplingOutPoint &op, ops) { + UniValue o(UniValue::VOBJ); + + o.push_back(Pair("txid", op.hash.GetHex())); + ret.push_back(o); + } + + return ret; +} + UniValue listlockunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (!EnsureWalletIsAvailable(fHelp)) From 80f48a9338773c2514d34093f045d19262e05b31 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 01:56:24 -0400 Subject: [PATCH 23/45] Run z_listlockunspent in tests so we can see which notes are locked --- qa/rpc-tests/lockzins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/lockzins.py b/qa/rpc-tests/lockzins.py index 232667ab5..a073beca8 100755 --- a/qa/rpc-tests/lockzins.py +++ b/qa/rpc-tests/lockzins.py @@ -75,7 +75,7 @@ class LockZinsTest (BitcoinTestFramework): rpc.generate(11) self.sync_all() - rpc.z_listunspent() + # rpc.z_listunspent() rpc.z_getbalances() recipients = [] @@ -84,8 +84,11 @@ class LockZinsTest (BitcoinTestFramework): # queue 4 ztxs, which will try to spend the same funds multiple times # without correct locking of zins opid1 = rpc.z_sendmany(zaddr1,recipients,1,0) + rpc.z_listlockunspent() opid2 = rpc.z_sendmany(zaddr1,recipients,1,0) + rpc.z_listlockunspent() opid3 = rpc.z_sendmany(zaddr1,recipients,1,0) + rpc.z_listlockunspent() opid4 = rpc.z_sendmany(zaddr1,recipients,1,0) rpc.generate(1) From bf55e6daa55f9493427dd950bfed4f5e8bdd9356 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 01:56:53 -0400 Subject: [PATCH 24/45] Avoid useless warnings in tests --- src/hush_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hush_utils.h b/src/hush_utils.h index 39ffd3e97..03c1af053 100644 --- a/src/hush_utils.h +++ b/src/hush_utils.h @@ -1339,7 +1339,7 @@ void hush_statefname(char *fname,char *symbol,char *str) fname[len - n] = 0; else { - if ( strcmp(symbol,"REGTEST") != 0 ) + if ( strcmp(symbol,"ZZZ") != 0 ) printf("unexpected fname.(%s) vs %s [%s] n.%d len.%d (%s)\n",fname,symbol,SMART_CHAIN_SYMBOL,n,len,&fname[len - n]); return; } From b0cbcc6152886d7554e2b373b894f0fdb84e9993 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 01:57:42 -0400 Subject: [PATCH 25/45] Unlock notes+utxos earlier --- src/wallet/asyncrpcoperation_sendmany.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index a6d708e53..ba09c09e7 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -169,6 +169,10 @@ void AsyncRPCOperation_sendmany::main() { set_error_message("unknown error"); } + unlock_notes(); + unlock_utxos(); + LogPrintf("%s: z_sendmany input notes+utxos unlocked\n", __func__, getId()); + #ifdef ENABLE_MINING #ifdef ENABLE_WALLET GenerateBitcoins(GetBoolArg("-gen",false), pwalletMain, GetArg("-genproclimit", 1)); @@ -179,6 +183,7 @@ void AsyncRPCOperation_sendmany::main() { stop_execution_clock(); + if (success) { set_state(OperationStatus::SUCCESS); } else { @@ -193,9 +198,6 @@ void AsyncRPCOperation_sendmany::main() { } LogPrintf("%s",s); - unlock_notes(); // clean up - unlock_utxos(); // clean up - LogPrint("zrpc", "%s: z_sendmany input notes+utxos unlocked\n", getId()); } // Notes: From 7c434ba03078241e49eaf41927bafd60551a6715 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 01:59:20 -0400 Subject: [PATCH 26/45] Log calls to GetFilteredNotes and only warn when witness root is actually invalid --- src/wallet/wallet.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index db5cebc9b..da52b8c57 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1846,12 +1846,10 @@ void CWallet::GetSaplingNoteWitnesses(std::vector notes, rt = witnesses[i]->root(); } else { if(*rt == witnesses[i]->root()) { - //fprintf(stderr,"%s: rt=%s\n",__func__,rt.GetHash().ToString().c_str()); - //fprintf(stderr,"%s: witnesses[%d]->root()=%s\n",__func__,i,witnesses[i]->root().GetHash().ToString().c_str()); + } else { // Something is fucky std::string err = string("CWallet::GetSaplingNoteWitnesses: Invalid witness root! rt=") + rt.get().ToString(); err += string("\n!= witness[i]->root()=") + witnesses[i]->root().ToString(); - //throw std::logic_error(err); fprintf(stderr,"%s: IGNORING %s\n", __func__,err.c_str()); } @@ -4880,6 +4878,8 @@ void CWallet::GetFilteredNotes( { LOCK2(cs_main, cs_wallet); + LogPrintf("%s ignoreLocked=%d\n", __func__, ignoreLocked); + for (auto & p : mapWallet) { CWalletTx wtx = p.second; From a719e05be4144b51512e8f98a94efc92b756713e Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 02:00:19 -0400 Subject: [PATCH 27/45] Add output index to z_listlockunspent --- src/wallet/rpcwallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e1ff70cfd..30cacabe3 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2678,6 +2678,7 @@ UniValue z_listlockunspent(const UniValue& params, bool fHelp, const CPubKey& my UniValue o(UniValue::VOBJ); o.push_back(Pair("txid", op.hash.GetHex())); + o.push_back(Pair("n", (int) op.n)); ret.push_back(o); } @@ -4120,7 +4121,6 @@ UniValue z_getbalances(const UniValue& params, bool fHelp, const CPubKey& mypk) return results; } - UniValue z_listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (!EnsureWalletIsAvailable(fHelp)) @@ -5994,7 +5994,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp // Create operation and add to global queue std::shared_ptr q = getAsyncRPCQueue(); std::shared_ptr operation( - new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, saplingNoteInputs, recipient, nFee, contextInfo) ); + new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, saplingNoteInputs, recipient, nFee, contextInfo) ); q->addOperation(operation); AsyncRPCOperationId operationId = operation->getId(); From 564ff0cb3a45a20bf305394bba09076589b8d925 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 16:13:52 -0400 Subject: [PATCH 28/45] Update test_framework.py from upstream which fixes #476 --- qa/rpc-tests/test_framework/test_framework.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index d04bd8b95..1728c8dba 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -123,11 +123,16 @@ class BitcoinTestFramework(object): print("JSONRPC error: "+e.error['message']) traceback.print_tb(sys.exc_info()[2]) except AssertionError as e: - print("Assertion failed: "+e.message) + print("Assertion failed: " + str(e)) + traceback.print_tb(sys.exc_info()[2]) + except KeyError as e: + print("key not found: "+ str(e)) traceback.print_tb(sys.exc_info()[2]) except Exception as e: print("Unexpected exception caught during testing: "+str(e)) traceback.print_tb(sys.exc_info()[2]) + except KeyboardInterrupt as e: + print("Exiting after " + repr(e)) if not self.options.noshutdown: print("Stopping nodes") From 6e029a62ac195896f4521e1b929afb44a38cce22 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 16:14:23 -0400 Subject: [PATCH 29/45] Remove unused header inclusion --- src/wallet/asyncrpcoperation_mergetoaddress.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/wallet/asyncrpcoperation_mergetoaddress.h b/src/wallet/asyncrpcoperation_mergetoaddress.h index ab4a949eb..b70eeb98f 100644 --- a/src/wallet/asyncrpcoperation_mergetoaddress.h +++ b/src/wallet/asyncrpcoperation_mergetoaddress.h @@ -1,6 +1,5 @@ // Copyright (c) 2017 The Zcash developers // Copyright (c) 2016-2024 The Hush developers - // Distributed under the GPLv3 software license, see the accompanying // file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html @@ -28,12 +27,9 @@ #include "transaction_builder.h" #include "wallet.h" #include "zcash/Address.hpp" -#include "zcash/JoinSplit.hpp" - #include #include #include - #include // Default transaction fee if caller does not specify one. From d73c1b86e4d8146e4a9b0c49efe14da32b78ba77 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 16:14:47 -0400 Subject: [PATCH 30/45] Log amount if negative change happens --- src/transaction_builder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 76ba42302..893c292c7 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -150,7 +150,7 @@ boost::optional TransactionBuilder::Build() change -= tOut.nValue; } if (change < 0) { - LogPrintf("%s: negative change!\n", __func__); + LogPrintf("%s: negative change=%lu!\n", __func__, change); return boost::none; } From fcd939978f6e81e59077db826b18587173599052 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 16:15:11 -0400 Subject: [PATCH 31/45] More verbose logging in lockzins test --- qa/rpc-tests/lockzins.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qa/rpc-tests/lockzins.py b/qa/rpc-tests/lockzins.py index a073beca8..33f07a336 100755 --- a/qa/rpc-tests/lockzins.py +++ b/qa/rpc-tests/lockzins.py @@ -46,6 +46,8 @@ class LockZinsTest (BitcoinTestFramework): #'-debug', '-regtest', '--daemon', + '-zrpc', + '-zrpcunsafe' #'-rpcuser=hush', #'-rpcpassword=puppy' ]] From 8148c4f625c8e4b55405d1261a2e970f7f1d7e82 Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 16:26:55 -0400 Subject: [PATCH 32/45] More test logging --- qa/rpc-tests/lockzins.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qa/rpc-tests/lockzins.py b/qa/rpc-tests/lockzins.py index 33f07a336..314bcdb3f 100755 --- a/qa/rpc-tests/lockzins.py +++ b/qa/rpc-tests/lockzins.py @@ -47,9 +47,8 @@ class LockZinsTest (BitcoinTestFramework): '-regtest', '--daemon', '-zrpc', + '-zdebug', '-zrpcunsafe' - #'-rpcuser=hush', - #'-rpcpassword=puppy' ]] ) self.is_network_split = False From eb4fc522731df11d2e5055a34b8dbe2d2f6735fc Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 16:59:33 -0400 Subject: [PATCH 33/45] lockzins test finally passes because z_sendmany correctly locks notes now --- src/transaction_builder.cpp | 2 +- src/wallet/asyncrpcoperation_sendmany.cpp | 72 ++++++++++++++++++----- src/wallet/asyncrpcoperation_sendmany.h | 9 ++- src/wallet/rpcwallet.cpp | 31 +++++++++- 4 files changed, 91 insertions(+), 23 deletions(-) diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 893c292c7..f942296a8 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -150,7 +150,7 @@ boost::optional TransactionBuilder::Build() change -= tOut.nValue; } if (change < 0) { - LogPrintf("%s: negative change=%lu!\n", __func__, change); + LogPrintf("%s: negative change=%lu mtx.valueBalance=%lu fee=%lu!\n", __func__, change, mtx.valueBalance, fee); return boost::none; } diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index ba09c09e7..8e47993f7 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -63,11 +63,12 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( std::string fromAddress, std::vector tOutputs, std::vector zOutputs, + std::vector saplingNoteInputs, int minDepth, CAmount fee, UniValue contextInfo, CScript opret) : - tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo), opret_(opret) + tx_(contextualTx), fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), saplingNoteInputs_(saplingNoteInputs), mindepth_(minDepth), fee_(fee), contextinfo_(contextInfo), opret_(opret) { assert(fee_ >= 0); @@ -120,6 +121,10 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany( LogPrint("zrpc", "%s: z_sendmany initialized\n", getId()); } + //TODO: lock_utxos() ? + // Lock shielded input notes (zins) stored in saplingNoteInputs + lock_notes(); + LogPrintf("%s: %s z_sendmany input notes locked\n", __func__, getId()); } AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() { @@ -171,7 +176,7 @@ void AsyncRPCOperation_sendmany::main() { unlock_notes(); unlock_utxos(); - LogPrintf("%s: z_sendmany input notes+utxos unlocked\n", __func__, getId()); + LogPrintf("%s: %s z_sendmany input notes+utxos unlocked\n", __func__, getId()); #ifdef ENABLE_MINING #ifdef ENABLE_WALLET @@ -204,7 +209,6 @@ void AsyncRPCOperation_sendmany::main() { // 1. #1159 Currently there is no limit set on the number of shielded spends, so size of tx could be invalid. // 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them bool AsyncRPCOperation_sendmany::main_impl() { - assert(isfromtaddr_ != isfromzaddr_); bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1); @@ -233,9 +237,11 @@ bool AsyncRPCOperation_sendmany::main_impl() { } } + /* if (isfromzaddr_ && !find_unspent_notes()) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address."); } + */ // Lock UTXOs lock_utxos(); @@ -246,8 +252,9 @@ bool AsyncRPCOperation_sendmany::main_impl() { } CAmount z_inputs_total = 0; - for (auto t : z_sapling_inputs_) { - z_inputs_total += t.note.value(); + for (auto t : saplingNoteInputs_) { + //z_inputs_total += t.note.value(); + z_inputs_total += std::get<1>(t).value(); } CAmount t_outputs_total = 0; @@ -261,8 +268,11 @@ bool AsyncRPCOperation_sendmany::main_impl() { z_outputs_total += std::get<1>(t); } + LogPrintf("%s: z_inputs_total=%s z_outputs_total=%s\n", __func__, FormatMoney(z_inputs_total), FormatMoney(z_outputs_total) ); + CAmount sendAmount = z_outputs_total + t_outputs_total; CAmount targetAmount = sendAmount + minersFee; + LogPrintf("%s: targetAmount=%s sendAmount=%s minersFee=%s\n", __func__, FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee) ); assert(!isfromtaddr_ || z_inputs_total == 0); assert(!isfromzaddr_ || t_inputs_total == 0); @@ -410,6 +420,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { std::vector notes; CAmount sum = 0; + /* //NOTE: z_sapling_inputs_ is a list of all potential notes to spend // saplingNoteInputs_ is a list of notes we will actually spend // and need to lock. It is a subset of z_sapling_inputs_ @@ -431,9 +442,11 @@ bool AsyncRPCOperation_sendmany::main_impl() { break; } } + */ - // Lock shielded input notes (zins) stored in saplingNoteInputs - lock_notes(); + for(const auto t : saplingNoteInputs_) { + ops.push_back(std::get<0>(t)); + } // Fetch Sapling anchor and witnesses //LogPrintf("%s: Gathering anchors and witnesses\n", __FUNCTION__); @@ -444,14 +457,30 @@ bool AsyncRPCOperation_sendmany::main_impl() { pwalletMain->GetSaplingNoteWitnesses(ops, witnesses, anchor); } + LogPrintf("%s: ops.size=%d witnesses.size=%d\n", __func__, ops.size(), witnesses.size() ); + // Add Sapling spends - for (size_t i = 0; i < notes.size(); i++) { + //TODO: should be using saplingNoteInputs_ + for (size_t i = 0; i < saplingNoteInputs_.size(); i++) { + //LOCK2(cs_main, pwalletMain->cs_wallet); + if (!witnesses[i]) { throw JSONRPCError(RPC_WALLET_ERROR, - strprintf( "Missing witness for Sapling note at outpoint %s", z_sapling_inputs_[i].op.ToString()) + //strprintf( "Missing witness for Sapling note at outpoint %s", saplingNoteInputs_[i].op.ToString()) + strprintf( "Missing witness for Sapling note at outpoint %s", std::get<0>(saplingNoteInputs_[i]).ToString()) ); } - assert(builder_.AddSaplingSpend(expsk, notes[i], anchor, witnesses[i].get())); + if(fZdebug) + LogPrintf("%s: Adding Sapling spend\n", __func__); + assert(builder_.AddSaplingSpend(expsk, std::get<1>(saplingNoteInputs_[i]), anchor, witnesses[i].get())); + + /* + // notes we are currently spending should be locked + if(pwalletMain->IsLockedNote(ops[i])) { + } else { + throw JSONRPCError(RPC_WALLET_ERROR, "Note we are spending is not locked!" ); + } + */ } // Add Sapling outputs @@ -463,7 +492,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { assert(boost::get(&addr) != nullptr); auto to = boost::get(addr); if(fZdebug) - LogPrintf("%s: Adding Sapling output to address %s\n", __FUNCTION__, address.c_str()); + LogPrintf("%s: Adding Sapling output with value=%s to address %s\n", __func__, FormatMoney(value), address.c_str()); auto memo = get_memo_from_hex_string(hexMemo); @@ -497,6 +526,7 @@ bool AsyncRPCOperation_sendmany::main_impl() { // Send the transaction // TODO: Use CWallet::CommitTransaction instead of sendrawtransaction auto signedtxn = EncodeHexTx(tx_); + if (!testmode) { UniValue params = UniValue(UniValue::VARR); params.push_back(signedtxn); @@ -668,6 +698,7 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) { return t_inputs_.size() > 0; } +/* // find unspent notes which are also unlocked bool AsyncRPCOperation_sendmany::find_unspent_notes() { if(fZdebug) @@ -708,6 +739,7 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() { return true; } +*/ void AsyncRPCOperation_sendmany::add_taddr_outputs_to_tx() { @@ -828,17 +860,27 @@ void AsyncRPCOperation_sendmany::unlock_utxos() { // Lock input notes void AsyncRPCOperation_sendmany::lock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); - fprintf(stderr,"%s: found %lu notes to lock\n", __func__, saplingNoteInputs_.size() ); + LogPrintf("%s: found %lu notes to lock\n", __func__, saplingNoteInputs_.size() ); for (auto note : saplingNoteInputs_) { - pwalletMain->LockNote(std::get<0>(note)); + if(pwalletMain->IsLockedNote(std::get<0>(note))) { + //TODO: deal with this + LogPrintf("%s: note already locked!\n", __func__); + } else { + pwalletMain->LockNote(std::get<0>(note)); + } } } // Unlock input notes void AsyncRPCOperation_sendmany::unlock_notes() { LOCK2(cs_main, pwalletMain->cs_wallet); - fprintf(stderr,"%s: found %lu notes to unlock\n", __func__, saplingNoteInputs_.size() ); + LogPrintf("%s: found %lu notes to unlock\n", __func__, saplingNoteInputs_.size() ); for (auto note : saplingNoteInputs_) { - pwalletMain->UnlockNote(std::get<0>(note)); + if(pwalletMain->IsLockedNote(std::get<0>(note))) { + //TODO: deal with this + pwalletMain->UnlockNote(std::get<0>(note)); + } else { + LogPrintf("%s: note already unlocked!\n", __func__); + } } } diff --git a/src/wallet/asyncrpcoperation_sendmany.h b/src/wallet/asyncrpcoperation_sendmany.h index 271daba3d..2d06b0c48 100644 --- a/src/wallet/asyncrpcoperation_sendmany.h +++ b/src/wallet/asyncrpcoperation_sendmany.h @@ -27,11 +27,9 @@ #include "transaction_builder.h" #include "zcash/Address.hpp" #include "wallet.h" - #include #include #include - #include // Default transaction fee if caller does not specify one. @@ -45,8 +43,8 @@ typedef std::tuple SendManyRecipient; // Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase) typedef std::tuple SendManyInputUTXO; -// Input note is a tuple of output, note, amount -typedef std::tuple SendManyInputSaplingNote; +// Input note is a tuple of output, note, amount, spending key +typedef std::tuple SendManyInputSaplingNote; class AsyncRPCOperation_sendmany : public AsyncRPCOperation { public: @@ -56,6 +54,7 @@ public: std::string fromAddress, std::vector tOutputs, std::vector zOutputs, + std::vector saplingNoteInputs, int minDepth, CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE, UniValue contextInfo = NullUniValue, @@ -95,7 +94,7 @@ private: std::vector t_outputs_; std::vector z_outputs_; std::vector t_inputs_; - std::vector z_sapling_inputs_; + //std::vector z_sapling_inputs_; std::vector saplingNoteInputs_; TransactionBuilder builder_; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 30cacabe3..482d2997b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5191,6 +5191,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) } } + // Recipients std::vector taddrRecipients; std::vector zaddrRecipients; @@ -5280,6 +5281,31 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) nTotalOut += nAmount; } + + std::vector saplingEntries; + // find all unspent and unlocked notes in this zaddr + pwalletMain->GetFilteredNotes(saplingEntries, fromaddress); + + CAmount total_value = 0; + + std::vector saplingNoteInputs; + // Decide which sapling notes will be spent + for (const SaplingNoteEntry& entry : saplingEntries) { + CAmount nValue = entry.note.value(); + libzcash::SaplingExtendedSpendingKey extsk; + if (!pwalletMain->GetSaplingExtendedSpendingKey(entry.address, extsk)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find spending key for payment address."); + } + saplingNoteInputs.emplace_back(entry.op, entry.note, nValue, extsk.expsk); + total_value += nValue; + LogPrintf("%s: adding note to spend with value=%s, total_value=%s\n", __func__, FormatMoney(nValue), FormatMoney(total_value) ); + if (total_value >= nTotalOut) { + // we have enough note value to make the tx + LogPrintf("%s: found enough notes, nTotalOut=%s total_value=%s\n", __func__, FormatMoney(nTotalOut), FormatMoney(total_value) ); + break; + } + } + // SIETCH: Sprinkle our cave with some magic privacy zdust // End goal is to have this be as large as possible without slowing xtns down too much // A value of 7 will provide much stronger linkability privacy versus pre-Sietch operations @@ -5400,7 +5426,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the sum of outputs %s and also greater than the default fee", FormatMoney(nFee), FormatMoney(nTotalOut))); } } - } + } } // Use input parameters as the optional context info to be returned by z_getoperationstatus and z_getoperationresult. @@ -5423,8 +5449,9 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // Create operation and add to global queue std::shared_ptr q = getAsyncRPCQueue(); - std::shared_ptr operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo, opret) ); + std::shared_ptr operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, saplingNoteInputs, nMinDepth, nFee, contextInfo, opret) ); q->addOperation(operation); + if(fZdebug) LogPrintf("%s: Submitted to async queue\n", __FUNCTION__); AsyncRPCOperationId operationId = operation->getId(); From 90f00ac8a4d56b2aeb5687f15997cfdf7389533d Mon Sep 17 00:00:00 2001 From: Duke Date: Thu, 21 Aug 2025 17:05:16 -0400 Subject: [PATCH 34/45] cleanup --- src/wallet/asyncrpcoperation_sendmany.cpp | 59 ----------------------- 1 file changed, 59 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 8e47993f7..2eb089c66 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -237,12 +237,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { } } - /* - if (isfromzaddr_ && !find_unspent_notes()) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address."); - } - */ - // Lock UTXOs lock_utxos(); @@ -253,7 +247,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { CAmount z_inputs_total = 0; for (auto t : saplingNoteInputs_) { - //z_inputs_total += t.note.value(); z_inputs_total += std::get<1>(t).value(); } @@ -460,7 +453,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { LogPrintf("%s: ops.size=%d witnesses.size=%d\n", __func__, ops.size(), witnesses.size() ); // Add Sapling spends - //TODO: should be using saplingNoteInputs_ for (size_t i = 0; i < saplingNoteInputs_.size(); i++) { //LOCK2(cs_main, pwalletMain->cs_wallet); @@ -473,14 +465,6 @@ bool AsyncRPCOperation_sendmany::main_impl() { if(fZdebug) LogPrintf("%s: Adding Sapling spend\n", __func__); assert(builder_.AddSaplingSpend(expsk, std::get<1>(saplingNoteInputs_[i]), anchor, witnesses[i].get())); - - /* - // notes we are currently spending should be locked - if(pwalletMain->IsLockedNote(ops[i])) { - } else { - throw JSONRPCError(RPC_WALLET_ERROR, "Note we are spending is not locked!" ); - } - */ } // Add Sapling outputs @@ -698,49 +682,6 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) { return t_inputs_.size() > 0; } -/* -// find unspent notes which are also unlocked -bool AsyncRPCOperation_sendmany::find_unspent_notes() { - if(fZdebug) - LogPrintf("%s: For address %s depth=%d\n", __FUNCTION__, fromaddress_.c_str(), mindepth_); - - std::vector saplingEntries; - { - LOCK2(cs_main, pwalletMain->cs_wallet); - // GetFilteredNotes ignores locked notes by default - pwalletMain->GetFilteredNotes(saplingEntries, fromaddress_, mindepth_); - } - - for (auto entry : saplingEntries) { - // locked status of note may have changed since GetFilteredNotes() - // returned data, so we check again - const bool isLocked = pwalletMain->IsLockedNote(entry.op); - if (isLocked) { - LogPrintf("%s: skipping locked note %s:%d\n", __func__, entry.op.hash.ToString().substr(0,10).c_str(), entry.op.n); - continue; - } - - z_sapling_inputs_.push_back(entry); - - std::string data(entry.memo.begin(), entry.memo.end()); - LogPrint("zrpcunsafe", "%s: found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n", - getId(), - entry.op.hash.ToString().substr(0, 10), - entry.op.n, - FormatMoney(entry.note.value()), - HexStr(data).substr(0, 10)); - } - - // sort in descending order, so big notes appear first - std::sort(z_sapling_inputs_.begin(), z_sapling_inputs_.end(), - [](SaplingNoteEntry i, SaplingNoteEntry j) -> bool { - return i.note.value() > j.note.value(); - }); - - return true; -} -*/ - void AsyncRPCOperation_sendmany::add_taddr_outputs_to_tx() { CMutableTransaction rawTx(tx_); From ae170e9899d209cbef50123b69a3d2166ef58911 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 05:43:21 -0400 Subject: [PATCH 35/45] Spendable notes are now locked and 1159 seems to be an irrelevant upstream issue --- src/wallet/asyncrpcoperation_sendmany.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 2eb089c66..5d23d75f6 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -206,8 +206,7 @@ void AsyncRPCOperation_sendmany::main() { } // Notes: -// 1. #1159 Currently there is no limit set on the number of shielded spends, so size of tx could be invalid. -// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them +// 1. Currently there is no limit set on the number of shielded spends, so size of tx could be invalid. bool AsyncRPCOperation_sendmany::main_impl() { assert(isfromtaddr_ != isfromzaddr_); From 7e3ce02d876705228ec95d728e63772babbcba60 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 06:16:25 -0400 Subject: [PATCH 36/45] Bring back sorting notes descending by value which was in find_unspent_notes() --- src/wallet/rpcwallet.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 482d2997b..859349052 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5286,6 +5286,13 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) // find all unspent and unlocked notes in this zaddr pwalletMain->GetFilteredNotes(saplingEntries, fromaddress); + // sort notes from largest to smallest, which means + // we will spend the largest first + std::sort(saplingEntries.begin(), saplingEntries.end(), + [](SaplingNoteEntry i, SaplingNoteEntry j) -> bool { + return i.note.value() > j.note.value(); + }); + CAmount total_value = 0; std::vector saplingNoteInputs; From 4ef3554307ed5e657df12ead112c48cfeea54ed8 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 06:47:27 -0400 Subject: [PATCH 37/45] Update authproxy --- qa/rpc-tests/test_framework/authproxy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qa/rpc-tests/test_framework/authproxy.py b/qa/rpc-tests/test_framework/authproxy.py index e55570636..d88d98bc3 100644 --- a/qa/rpc-tests/test_framework/authproxy.py +++ b/qa/rpc-tests/test_framework/authproxy.py @@ -1,3 +1,4 @@ +# Copyright (c) 2016-2025 The Hush developers """ Copyright 2011 Jeff Garzik @@ -40,11 +41,11 @@ import logging from http.client import HTTPConnection, HTTPSConnection, BadStatusLine from urllib.parse import urlparse -USER_AGENT = "AuthServiceProxy/0.1" +USER_AGENT = "HushAuthServiceProxy/0.1" HTTP_TIMEOUT = 600 -log = logging.getLogger("BitcoinRPC") +log = logging.getLogger("RPC") class JSONRPCException(Exception): def __init__(self, rpc_error): From fb7d669f1422e47e012a699a1966f60ba351917b Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 07:09:15 -0400 Subject: [PATCH 38/45] Remove commented out code --- src/wallet/asyncrpcoperation_sendmany.cpp | 27 +---------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 5d23d75f6..448a4bcf8 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -409,33 +409,8 @@ bool AsyncRPCOperation_sendmany::main_impl() { if(fZdebug) LogPrintf("%s: Selecting Sapling notes\n", __FUNCTION__); std::vector ops; - std::vector notes; CAmount sum = 0; - /* - //NOTE: z_sapling_inputs_ is a list of all potential notes to spend - // saplingNoteInputs_ is a list of notes we will actually spend - // and need to lock. It is a subset of z_sapling_inputs_ - for (const auto t : z_sapling_inputs_) { - // locked status of these inputs may have changed, check again - const bool isLocked = pwalletMain->IsLockedNote(t.op); - if (isLocked) { - LogPrintf("%s: skipping locked note %s\n", __func__, t.op.hash.ToString().substr(0,10).c_str()); - continue; - } - - // keep track of currently unlocked notes to lock later on in lock_notes() - saplingNoteInputs_.emplace_back(t.op, t.note, t.note.value() ); - - ops.push_back(t.op); - notes.push_back(t.note); - sum += t.note.value(); - if (sum >= targetAmount) { - break; - } - } - */ - for(const auto t : saplingNoteInputs_) { ops.push_back(std::get<0>(t)); } @@ -455,9 +430,9 @@ bool AsyncRPCOperation_sendmany::main_impl() { for (size_t i = 0; i < saplingNoteInputs_.size(); i++) { //LOCK2(cs_main, pwalletMain->cs_wallet); + //TODO: avoid coredump here if this index does not exist if (!witnesses[i]) { throw JSONRPCError(RPC_WALLET_ERROR, - //strprintf( "Missing witness for Sapling note at outpoint %s", saplingNoteInputs_[i].op.ToString()) strprintf( "Missing witness for Sapling note at outpoint %s", std::get<0>(saplingNoteInputs_[i]).ToString()) ); } From 34829af017dbbe744f02b33c4740433fbf58d47c Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 07:34:11 -0400 Subject: [PATCH 39/45] Avoid coredump if witness index does not exist --- src/wallet/asyncrpcoperation_sendmany.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 448a4bcf8..0c58cb25e 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -428,16 +428,14 @@ bool AsyncRPCOperation_sendmany::main_impl() { // Add Sapling spends for (size_t i = 0; i < saplingNoteInputs_.size(); i++) { - //LOCK2(cs_main, pwalletMain->cs_wallet); - - //TODO: avoid coredump here if this index does not exist - if (!witnesses[i]) { + if (!witnesses.at(i)) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf( "Missing witness for Sapling note at outpoint %s", std::get<0>(saplingNoteInputs_[i]).ToString()) ); } if(fZdebug) LogPrintf("%s: Adding Sapling spend\n", __func__); + assert(builder_.AddSaplingSpend(expsk, std::get<1>(saplingNoteInputs_[i]), anchor, witnesses[i].get())); } From 727f4d9a29fb5afebc1807360476b66eb1be6978 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 11:33:13 -0400 Subject: [PATCH 40/45] Sleep as a hack for lacking fullyNotified in some places --- qa/rpc-tests/lockzins.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qa/rpc-tests/lockzins.py b/qa/rpc-tests/lockzins.py index 314bcdb3f..e71c8cfcd 100755 --- a/qa/rpc-tests/lockzins.py +++ b/qa/rpc-tests/lockzins.py @@ -94,14 +94,15 @@ class LockZinsTest (BitcoinTestFramework): rpc.generate(1) self.sync_all() + + # syncing is not perfect, oh well + time.sleep(3) + wait_and_assert_operationid_status(self.nodes[0], opid1) wait_and_assert_operationid_status(self.nodes[0], opid2) wait_and_assert_operationid_status(self.nodes[0], opid3) wait_and_assert_operationid_status(self.nodes[0], opid4) - # give time for all z_sendmany's to run - #time.sleep(10) - rpc.z_getoperationstatus() if __name__ == '__main__': From 04ec2be8c882e44fdee17cfd4caa1512512dd74a Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 13:17:36 -0400 Subject: [PATCH 41/45] Add fullyNotified to getblockchaininfo and use it in tests --- qa/rpc-tests/test_framework/util.py | 2 -- src/rpc/blockchain.cpp | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index ff1115b88..98d045442 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -57,8 +57,6 @@ def sync_blocks(rpc_connections, wait=1): break time.sleep(wait) - return - # Now that the block counts are in sync, wait for the internal # notifications to finish while True: diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index abcc49c45..5f1394a80 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1335,6 +1335,12 @@ UniValue getblockchaininfo(const UniValue& params, bool fHelp, const CPubKey& my obj.push_back(Pair("pruneheight", block->GetHeight())); } + + // this helps our tests work correctly + if (Params().NetworkIDString() == "regtest") { + obj.pushKV("fullyNotified", ChainIsFullyNotified()); + } + return obj; } From aa69b87505f8da3f38596a9c86996db95600ca03 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 21:21:15 -0400 Subject: [PATCH 42/45] These are unrelated to ztxs and should be -debug --- src/txdb.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/txdb.cpp b/src/txdb.cpp index 6a6f7c086..4d402d072 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -271,13 +271,13 @@ bool CCoinsViewDB::GetStats(CCoinsStats &stats) const { bool CBlockTreeDB::WriteBatchSync(const std::vector >& fileInfo, int nLastFile, const std::vector& blockinfo) { CDBBatch batch(*this); - if (fZdebug) + if (fDebug) fprintf(stderr, "%s: Writing block files\n", __FUNCTION__); for (const auto& it : fileInfo) { batch.Write(make_pair(DB_BLOCK_FILES, it.first), *it.second); } batch.Write(DB_LAST_BLOCK, nLastFile); - if (fZdebug) + if (fDebug) fprintf(stderr, "%s: Writing block index\n", __FUNCTION__); for (const auto& it : blockinfo) { std::pair key = make_pair(DB_BLOCK_INDEX, it->GetBlockHash()); From a555f64ad99fb9549a71314e60fa4080eff076d1 Mon Sep 17 00:00:00 2001 From: Duke Date: Fri, 22 Aug 2025 21:26:05 -0400 Subject: [PATCH 43/45] Has the sleeper awakened? --- qa/rpc-tests/lockzins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/rpc-tests/lockzins.py b/qa/rpc-tests/lockzins.py index e71c8cfcd..4c46fea0f 100755 --- a/qa/rpc-tests/lockzins.py +++ b/qa/rpc-tests/lockzins.py @@ -95,8 +95,8 @@ class LockZinsTest (BitcoinTestFramework): rpc.generate(1) self.sync_all() - # syncing is not perfect, oh well - time.sleep(3) + # now that we have fullyNotified, this may not be needed? + #time.sleep(3) wait_and_assert_operationid_status(self.nodes[0], opid1) wait_and_assert_operationid_status(self.nodes[0], opid2) From e421dfc6a564fabec437da4261493277a762afda Mon Sep 17 00:00:00 2001 From: Duke Date: Sat, 23 Aug 2025 06:15:33 -0400 Subject: [PATCH 44/45] Improve rpc docs of z_listlockunspent --- src/wallet/rpcwallet.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 859349052..6eeafd012 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2653,12 +2653,15 @@ UniValue z_listlockunspent(const UniValue& params, bool fHelp, const CPubKey& my if (fHelp || params.size() > 0) throw runtime_error( "z_listlockunspent\n" - "\nReturns list of temporarily unspendable outputs.\n" + "\nReturns list of temporarily locked shielded outputs which are currently unspendable. They are locked\n" + "\nbecause they are currently in the process of being spent by an operation such as z_sendmany/z_mergetoaddress/etc.\n" + "\nIf that operation succeeds, they will become spent. If it fails they will be unlocked and become\n" + "\nspendable again.\n" "\nResult:\n" "[\n" " {\n" - " \"txid\" : \"transactionid\", (string) The transaction id locked\n" - " \"vout\" : n (numeric) The vout value\n" + " \"txid\" : \"transactionid\", (string) The transaction id locked\n" + " \"outindex\" : n (integer) The shielded output index\n" " }\n" " ,...\n" "]\n" @@ -2678,7 +2681,7 @@ UniValue z_listlockunspent(const UniValue& params, bool fHelp, const CPubKey& my UniValue o(UniValue::VOBJ); o.push_back(Pair("txid", op.hash.GetHex())); - o.push_back(Pair("n", (int) op.n)); + o.push_back(Pair("outindex", (int) op.n)); ret.push_back(o); } From cb81fc3b9589ebeb7e0bd58f174683756908b725 Mon Sep 17 00:00:00 2001 From: Duke Date: Wed, 24 Sep 2025 09:30:33 -0400 Subject: [PATCH 45/45] Less noise unless -debug is used --- qa/rpc-tests/test_framework/util.py | 4 ++-- src/init.cpp | 12 +++++++++--- src/wallet/asyncrpcoperation_sendmany.cpp | 2 -- test.sh | 3 +++ 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index 98d045442..e8a16251d 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -274,7 +274,7 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary= devnull.close() port = extra_args[3] #port = rpc_port(i) - print("port=%s" % port) + #print("port=%s" % port) username = rpc_username() password = rpc_password() url = "http://%s:%s@%s:%s" % (username, password, rpchost or '127.0.0.1', port[9:]) @@ -283,7 +283,7 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary= proxy = AuthServiceProxy(url, timeout=timewait) else: proxy = AuthServiceProxy(url) - print("created proxy") + #print("created proxy") proxy.url = url # store URL on proxy for info return proxy diff --git a/src/init.cpp b/src/init.cpp index 94bb2d977..4d5b7b3b0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -146,7 +146,9 @@ std::atomic fRequestShutdown(false); void StartShutdown() { - fprintf(stderr,"%s: fRequestShudown=true\n", __FUNCTION__); + if(fDebug) { + fprintf(stderr,"%s: fRequestShudown=true\n", __FUNCTION__); + } fRequestShutdown = true; } bool ShutdownRequested() @@ -208,7 +210,9 @@ void Shutdown() RenameThread(shutoffstr); mempool.AddTransactionsUpdated(1); - fprintf(stderr,"%s: stopping HUSH HTTP/REST/RPC\n", __FUNCTION__); + if(fDebug) { + fprintf(stderr,"%s: stopping HUSH HTTP/REST/RPC\n", __FUNCTION__); + } StopHTTPRPC(); StopREST(); StopRPC(); @@ -225,7 +229,9 @@ void Shutdown() GenerateBitcoins(false, 0); #endif #endif - fprintf(stderr,"%s: stopping node\n", __FUNCTION__); + if(fDebug) { + fprintf(stderr,"%s: stopping node\n", __FUNCTION__); + } StopNode(); StopTorControl(); UnregisterNodeSignals(GetNodeSignals()); diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 0c58cb25e..f2a698d05 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -776,7 +776,6 @@ void AsyncRPCOperation_sendmany::lock_notes() { LogPrintf("%s: found %lu notes to lock\n", __func__, saplingNoteInputs_.size() ); for (auto note : saplingNoteInputs_) { if(pwalletMain->IsLockedNote(std::get<0>(note))) { - //TODO: deal with this LogPrintf("%s: note already locked!\n", __func__); } else { pwalletMain->LockNote(std::get<0>(note)); @@ -790,7 +789,6 @@ void AsyncRPCOperation_sendmany::unlock_notes() { LogPrintf("%s: found %lu notes to unlock\n", __func__, saplingNoteInputs_.size() ); for (auto note : saplingNoteInputs_) { if(pwalletMain->IsLockedNote(std::get<0>(note))) { - //TODO: deal with this pwalletMain->UnlockNote(std::get<0>(note)); } else { LogPrintf("%s: note already unlocked!\n", __func__); diff --git a/test.sh b/test.sh index 5d81d4850..a48d5f3f8 100755 --- a/test.sh +++ b/test.sh @@ -1,4 +1,7 @@ #!/usr/bin/env bash +# Copyright 2016-2025 The Hush developers +# Distributed under the GPLv3 software license, see the accompanying +# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html #export PYTHON_DEBUG=1 export PYTHONPATH=./qa/rpc-tests/test_framework/