wallet: extract ParseSendManyRecipients from z_sendmany

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) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 16:28:46 -05:00
parent 4a2e531cb6
commit e55c67b0eb

View File

@@ -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."); 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<SendManyRecipient>& taddrRecipients,
std::vector<SendManyRecipient>& 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) UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
{ {
if (!EnsureWalletIsAvailable(fHelp)) 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()); opret << OP_RETURN << ParseHex(opretValue.get_str().c_str());
} }
for (const UniValue& o : outputs.getValues()) { ParseSendManyRecipients(outputs, branchId, taddrRecipients, zaddrRecipients, nTotalOut);
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;
}
std::vector<SaplingNoteEntry> saplingEntries; std::vector<SaplingNoteEntry> saplingEntries;
// find all unspent and unlocked notes in this zaddr // find all unspent and unlocked notes in this zaddr