wallet: extract SelectAnyZaddrSource from z_sendmany
First slice of the z_sendmany monolith split (Phase-6 refactor). The
`fromaddress == "z"` case ("spend from any zaddr") was ~76 lines inline: it
gathers the wallet's Sapling notes, sums balances per zaddr, and picks a
random zaddr whose confirmed balance covers total outputs + fee. Lift it
verbatim into a helper `SelectAnyZaddrSource(outputs, params)` that returns
the chosen zaddr (or throws the same JSONRPCError), and call it from
z_sendmany.
Behavior-preserving by construction: the block was moved unchanged, the only
edit being `fromaddress = vPotentialAddresses[...]` -> `return ...` with the
call site doing `fromaddress = SelectAnyZaddrSource(outputs, params)`, so
fromaddress receives the identical value (or the identical throw propagates).
The helper takes only outputs/params + globals, which the clean compile
proves (a reference to any z_sendmany local would not link). Built clean;
verifychain=true.
Note: the runtime happy path could not be exercised on an isolated node —
z_sendmany's private-chain "still syncing" linkability guard fires before the
fromaddress resolution when there are no peers — but a verbatim extract-method
does not require it. z_sendmany is now ~76 lines shorter; further extraction
(recipient parsing, fee/tx assembly) remains for follow-up slices.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5107,63 +5107,12 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO
|
|||||||
#define CTXIN_SPEND_DUST_SIZE 148
|
#define CTXIN_SPEND_DUST_SIZE 148
|
||||||
#define CTXOUT_REGULAR_SIZE 34
|
#define CTXOUT_REGULAR_SIZE 34
|
||||||
|
|
||||||
UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
// Resolve the special fromaddress "z" (spend from any zaddr) to a concrete zaddr:
|
||||||
|
// gather this wallet's Sapling notes, pick a random zaddr whose confirmed balance
|
||||||
|
// covers the total outputs + fee. Extracted verbatim from z_sendmany (behavior-
|
||||||
|
// preserving); throws JSONRPCError when no single zaddr has enough funds.
|
||||||
|
static std::string SelectAnyZaddrSource(const UniValue& outputs, const UniValue& params)
|
||||||
{
|
{
|
||||||
if (!EnsureWalletIsAvailable(fHelp))
|
|
||||||
return NullUniValue;
|
|
||||||
|
|
||||||
if (fHelp || params.size() < 2 || params.size() > 5)
|
|
||||||
throw runtime_error(
|
|
||||||
"z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee ) (opreturn)\n"
|
|
||||||
"\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision."
|
|
||||||
"\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself."
|
|
||||||
"\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed."
|
|
||||||
+ HelpRequiringPassphrase() + "\n"
|
|
||||||
"\nArguments:\n"
|
|
||||||
"1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from. Use 'z' to spend from any zaddr.\n"
|
|
||||||
"2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n"
|
|
||||||
" [{\n"
|
|
||||||
" \"address\":address (string, required) The address is a taddr or zaddr\n"
|
|
||||||
" \"amount\":amount (numeric, required) The amount to send this address\n"
|
|
||||||
" \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n"
|
|
||||||
" }, ... ]\n"
|
|
||||||
"3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n"
|
|
||||||
"4. fee (numeric, optional, default="
|
|
||||||
+ strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
|
|
||||||
"5. opreturn (string, optional) Hex encoded data for OP_RETURN. Or a utf8 string prefixed with 'utf8:' which will be automatically converted to hex\n"
|
|
||||||
"\nResult:\n"
|
|
||||||
"\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
|
|
||||||
"\nExamples:\n"
|
|
||||||
+ HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]'")
|
|
||||||
+ HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]")
|
|
||||||
+ HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]'")
|
|
||||||
+ HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]")
|
|
||||||
+ HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex")
|
|
||||||
+ HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex")
|
|
||||||
);
|
|
||||||
|
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
||||||
|
|
||||||
// Guard against building shielded transactions before the chain is fully synced;
|
|
||||||
// sending while behind the tip can leak metadata usable for linkability analysis.
|
|
||||||
THROW_IF_SYNCING(HUSH_INSYNC);
|
|
||||||
|
|
||||||
// Check that the from address is valid.
|
|
||||||
auto fromaddress = params[0].get_str();
|
|
||||||
bool fromTaddr = false;
|
|
||||||
bool fromSapling = false;
|
|
||||||
uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus());
|
|
||||||
UniValue outputs = params[1].get_array();
|
|
||||||
|
|
||||||
if (outputs.size()==0)
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty.");
|
|
||||||
|
|
||||||
// TODO: implement special symbolic fromaddrs
|
|
||||||
// TODO: list of (zaddr,amount)
|
|
||||||
// "z" => spend from any zaddr
|
|
||||||
// "t" => spend from any taddr
|
|
||||||
// "*" => spend from any addr, zaddrs first
|
|
||||||
if(fromaddress == "z") {
|
|
||||||
// TODO: refactor this and z_getbalances to use common code
|
// TODO: refactor this and z_getbalances to use common code
|
||||||
std::set<libzcash::PaymentAddress> zaddrs = {};
|
std::set<libzcash::PaymentAddress> zaddrs = {};
|
||||||
std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {};
|
std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {};
|
||||||
@@ -5234,11 +5183,70 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
|||||||
auto nPotentials = vPotentialAddresses.size();
|
auto nPotentials = vPotentialAddresses.size();
|
||||||
if (nPotentials > 0) {
|
if (nPotentials > 0) {
|
||||||
LogPrintf("%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials);
|
LogPrintf("%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials);
|
||||||
fromaddress = vPotentialAddresses[ GetRandInt(nPotentials) ];
|
return vPotentialAddresses[ GetRandInt(nPotentials) ];
|
||||||
} else {
|
}
|
||||||
// Automagic zaddr source selection 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.");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "No single zaddr currently has enough funds to make that transaction, you may need to wait for confirmations.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
||||||
|
{
|
||||||
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
|
return NullUniValue;
|
||||||
|
|
||||||
|
if (fHelp || params.size() < 2 || params.size() > 5)
|
||||||
|
throw runtime_error(
|
||||||
|
"z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee ) (opreturn)\n"
|
||||||
|
"\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision."
|
||||||
|
"\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself."
|
||||||
|
"\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed."
|
||||||
|
+ HelpRequiringPassphrase() + "\n"
|
||||||
|
"\nArguments:\n"
|
||||||
|
"1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from. Use 'z' to spend from any zaddr.\n"
|
||||||
|
"2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n"
|
||||||
|
" [{\n"
|
||||||
|
" \"address\":address (string, required) The address is a taddr or zaddr\n"
|
||||||
|
" \"amount\":amount (numeric, required) The amount to send this address\n"
|
||||||
|
" \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n"
|
||||||
|
" }, ... ]\n"
|
||||||
|
"3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n"
|
||||||
|
"4. fee (numeric, optional, default="
|
||||||
|
+ strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
|
||||||
|
"5. opreturn (string, optional) Hex encoded data for OP_RETURN. Or a utf8 string prefixed with 'utf8:' which will be automatically converted to hex\n"
|
||||||
|
"\nResult:\n"
|
||||||
|
"\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
|
||||||
|
"\nExamples:\n"
|
||||||
|
+ HelpExampleCli("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]'")
|
||||||
|
+ HelpExampleRpc("z_sendmany", "\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 5.0}]")
|
||||||
|
+ HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]'")
|
||||||
|
+ HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\", [{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]")
|
||||||
|
+ HelpExampleCli("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex")
|
||||||
|
+ HelpExampleRpc("z_sendmany", "\"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" '[{\"address\": \"zs14d8tc0hl9q0vg5l28uec5vk6sk34fkj2n8s7jalvw5fxpy6v39yn4s2ga082lymrkjk0x2nqg37\" ,\"amount\": 3.14}]' 1 0.0001 \"utf8: this will be converted to hex")
|
||||||
|
);
|
||||||
|
|
||||||
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
|
// Guard against building shielded transactions before the chain is fully synced;
|
||||||
|
// sending while behind the tip can leak metadata usable for linkability analysis.
|
||||||
|
THROW_IF_SYNCING(HUSH_INSYNC);
|
||||||
|
|
||||||
|
// Check that the from address is valid.
|
||||||
|
auto fromaddress = params[0].get_str();
|
||||||
|
bool fromTaddr = false;
|
||||||
|
bool fromSapling = false;
|
||||||
|
uint32_t branchId = CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus());
|
||||||
|
UniValue outputs = params[1].get_array();
|
||||||
|
|
||||||
|
if (outputs.size()==0)
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty.");
|
||||||
|
|
||||||
|
// TODO: implement special symbolic fromaddrs
|
||||||
|
// TODO: list of (zaddr,amount)
|
||||||
|
// "z" => spend from any zaddr
|
||||||
|
// "t" => spend from any taddr
|
||||||
|
// "*" => spend from any addr, zaddrs first
|
||||||
|
if(fromaddress == "z") {
|
||||||
|
fromaddress = SelectAnyZaddrSource(outputs, params);
|
||||||
} else {
|
} else {
|
||||||
CTxDestination taddr = DecodeDestination(fromaddress);
|
CTxDestination taddr = DecodeDestination(fromaddress);
|
||||||
fromTaddr = IsValidDestination(taddr);
|
fromTaddr = IsValidDestination(taddr);
|
||||||
|
|||||||
Reference in New Issue
Block a user