From e55c67b0eb9c3029d82606561540801050b43086 Mon Sep 17 00:00:00 2001 From: DanS Date: Thu, 27 Aug 2026 16:28:46 -0500 Subject: [PATCH] wallet: extract ParseSendManyRecipients from z_sendmany MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second slice of the z_sendmany split. The ~60-line loop that parses and validates the "outputs" array — rejecting unknown keys, bad addresses, memos on taddrs, oversize memos and negative amounts, and sorting recipients into taddr/zaddr lists while accumulating nTotalOut — is lifted verbatim into ParseSendManyRecipients(outputs, branchId, taddrRecipients, zaddrRecipients, nTotalOut). Verbatim extract-method (the loop text was moved unchanged, not retyped); the helper reads only outputs + branchId and writes the three by-reference outputs the loop already produced, which the clean compile proves self-contained (a reference to any other z_sendmany local would fail to link). Built clean; verifychain=true. Together with the SelectAnyZaddrSource slice, z_sendmany is now ~135 lines shorter, with source-address resolution and recipient parsing separated out. Remaining: note selection, Sietch padding, and tx assembly (later slices, best validated against a synced node since z_sendmany's linkability guard blocks the send path on a peerless node). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wallet/rpcwallet.cpp | 133 +++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 90f0d45a6..0ea4fa1df 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5189,6 +5189,77 @@ static std::string SelectAnyZaddrSource(const UniValue& outputs, const UniValue& throw JSONRPCError(RPC_INVALID_PARAMETER, "No single zaddr currently has enough funds to make that transaction, you may need to wait for confirmations."); } +// Parse and validate the z_sendmany "outputs" array into taddr/zaddr recipient lists +// (accumulating nTotalOut). Extracted verbatim from z_sendmany; throws JSONRPCError on +// any malformed entry (unknown key, bad address, memo misuse/oversize, negative amount). +static void ParseSendManyRecipients(const UniValue& outputs, uint32_t branchId, + std::vector& taddrRecipients, + std::vector& zaddrRecipients, + CAmount& nTotalOut) +{ + for (const UniValue& o : outputs.getValues()) { + if (!o.isObject()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); + + // sanity check, report error if unknown key-value pairs + for (const string& name_ : o.getKeys()) { + std::string s = name_; + if (s != "address" && s != "amount" && s!="memo") { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); + } + } + + string address = find_value(o, "address").get_str(); + bool isZaddr = false; + CTxDestination taddr = DecodeDestination(address); + if (!IsValidDestination(taddr)) { + auto res = DecodePaymentAddress(address); + if (IsValidPaymentAddress(res, branchId)) { + isZaddr = true; + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address ); + } + }// else if ( ASSETCHAINS_PRIVATE != 0 && hush_isnotaryvout((char *)address.c_str()) == 0 ) { + // throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extreme Privacy! You must send to a zaddr"); + //} + + UniValue memoValue = find_value(o, "memo"); + string memo; + if (!memoValue.isNull()) { + memo = memoValue.get_str(); + if (!isZaddr) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr."); + } else if(memo.substr(0,5) == "utf8:") { + // Support a prefix "utf8:" which allows giving utf8 text instead of hex + auto str = memo.substr(5); + if (utf8::is_valid(str)) { + memo = HexStr(str); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in memo"); + } + } else if (!IsHex(memo)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format or to use 'utf8:' prefix."); + } + if (memo.length() > HUSH_MEMO_SIZE*2) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", HUSH_MEMO_SIZE )); + } + } + + UniValue av = find_value(o, "amount"); + CAmount nAmount = AmountFromValue( av ); + if (nAmount < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); + + if (isZaddr) { + zaddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); + } else { + taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); + } + + nTotalOut += nAmount; + } +} + UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (!EnsureWalletIsAvailable(fHelp)) @@ -5296,67 +5367,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk) opret << OP_RETURN << ParseHex(opretValue.get_str().c_str()); } - for (const UniValue& o : outputs.getValues()) { - if (!o.isObject()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); - - // sanity check, report error if unknown key-value pairs - for (const string& name_ : o.getKeys()) { - std::string s = name_; - if (s != "address" && s != "amount" && s!="memo") { - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); - } - } - - string address = find_value(o, "address").get_str(); - bool isZaddr = false; - CTxDestination taddr = DecodeDestination(address); - if (!IsValidDestination(taddr)) { - auto res = DecodePaymentAddress(address); - if (IsValidPaymentAddress(res, branchId)) { - isZaddr = true; - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address ); - } - }// else if ( ASSETCHAINS_PRIVATE != 0 && hush_isnotaryvout((char *)address.c_str()) == 0 ) { - // throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Extreme Privacy! You must send to a zaddr"); - //} - - UniValue memoValue = find_value(o, "memo"); - string memo; - if (!memoValue.isNull()) { - memo = memoValue.get_str(); - if (!isZaddr) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr."); - } else if(memo.substr(0,5) == "utf8:") { - // Support a prefix "utf8:" which allows giving utf8 text instead of hex - auto str = memo.substr(5); - if (utf8::is_valid(str)) { - memo = HexStr(str); - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid utf8 in memo"); - } - } else if (!IsHex(memo)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format or to use 'utf8:' prefix."); - } - if (memo.length() > HUSH_MEMO_SIZE*2) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", HUSH_MEMO_SIZE )); - } - } - - UniValue av = find_value(o, "amount"); - CAmount nAmount = AmountFromValue( av ); - if (nAmount < 0) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); - - if (isZaddr) { - zaddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); - } else { - taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); - } - - nTotalOut += nAmount; - } + ParseSendManyRecipients(outputs, branchId, taddrRecipients, zaddrRecipients, nTotalOut); std::vector saplingEntries; // find all unspent and unlocked notes in this zaddr