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,6 +5107,88 @@ 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
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
// TODO: refactor this and z_getbalances to use common code
|
||||||
|
std::set<libzcash::PaymentAddress> zaddrs = {};
|
||||||
|
std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {};
|
||||||
|
pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs);
|
||||||
|
|
||||||
|
zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end());
|
||||||
|
|
||||||
|
int nMinDepth = 1;
|
||||||
|
std::vector<SaplingNoteEntry> saplingEntries;
|
||||||
|
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth);
|
||||||
|
|
||||||
|
std::map<std::string, CAmount> mapBalances;
|
||||||
|
for (auto & entry : saplingEntries) {
|
||||||
|
auto zaddr = EncodePaymentAddress(entry.address);
|
||||||
|
CAmount nBalance = CAmount(entry.note.value());
|
||||||
|
if(mapBalances.count(zaddr)) {
|
||||||
|
mapBalances[zaddr] += nBalance;
|
||||||
|
} else {
|
||||||
|
mapBalances[zaddr] = nBalance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<std::pair<std::string,CAmount>> vec;
|
||||||
|
std::copy(mapBalances.begin(), mapBalances.end(), std::back_inserter<std::vector<std::pair<std::string,CAmount>>>(vec));
|
||||||
|
|
||||||
|
std::sort(vec.begin(), vec.end(), [](const std::pair<std::string, CAmount> &l, const std::pair<std::string,CAmount> &r)
|
||||||
|
{
|
||||||
|
if (l.second != r.second) {
|
||||||
|
return l.second > r.second;
|
||||||
|
}
|
||||||
|
return l.first > r.first;
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO: avoid calculating nTotalOut twice
|
||||||
|
CAmount nTotalOut = 0;
|
||||||
|
for (const UniValue& o : outputs.getValues()) {
|
||||||
|
if (!o.isObject())
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object");
|
||||||
|
|
||||||
|
UniValue av = find_value(o, "amount");
|
||||||
|
CAmount nAmount = AmountFromValue( av );
|
||||||
|
if (nAmount < 0)
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive");
|
||||||
|
|
||||||
|
nTotalOut += nAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
//GOAL: choose one random zaddress with enough funds
|
||||||
|
CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE; // default when params.size()<=3 (was uninitialized)
|
||||||
|
if (params.size() > 3) {
|
||||||
|
if (params[3].get_real() == 0.0) {
|
||||||
|
nFee = 0;
|
||||||
|
} else {
|
||||||
|
nFee = AmountFromValue( params[3] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the total amount needed in a single zaddr to use as fromaddress
|
||||||
|
CAmount nMinBal = nTotalOut + nFee;
|
||||||
|
|
||||||
|
std::vector<std::string> vPotentialAddresses;
|
||||||
|
for (auto & entry : vec) {
|
||||||
|
if(entry.second >= nMinBal) {
|
||||||
|
vPotentialAddresses.push_back(entry.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// select a random address with enough confirmed balance
|
||||||
|
auto nPotentials = vPotentialAddresses.size();
|
||||||
|
if (nPotentials > 0) {
|
||||||
|
LogPrintf("%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials);
|
||||||
|
return vPotentialAddresses[ GetRandInt(nPotentials) ];
|
||||||
|
}
|
||||||
|
// 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.");
|
||||||
|
}
|
||||||
|
|
||||||
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))
|
||||||
@@ -5164,81 +5246,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
|||||||
// "t" => spend from any taddr
|
// "t" => spend from any taddr
|
||||||
// "*" => spend from any addr, zaddrs first
|
// "*" => spend from any addr, zaddrs first
|
||||||
if(fromaddress == "z") {
|
if(fromaddress == "z") {
|
||||||
// TODO: refactor this and z_getbalances to use common code
|
fromaddress = SelectAnyZaddrSource(outputs, params);
|
||||||
std::set<libzcash::PaymentAddress> zaddrs = {};
|
|
||||||
std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {};
|
|
||||||
pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs);
|
|
||||||
|
|
||||||
zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end());
|
|
||||||
|
|
||||||
int nMinDepth = 1;
|
|
||||||
std::vector<SaplingNoteEntry> saplingEntries;
|
|
||||||
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth);
|
|
||||||
|
|
||||||
std::map<std::string, CAmount> mapBalances;
|
|
||||||
for (auto & entry : saplingEntries) {
|
|
||||||
auto zaddr = EncodePaymentAddress(entry.address);
|
|
||||||
CAmount nBalance = CAmount(entry.note.value());
|
|
||||||
if(mapBalances.count(zaddr)) {
|
|
||||||
mapBalances[zaddr] += nBalance;
|
|
||||||
} else {
|
|
||||||
mapBalances[zaddr] = nBalance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::vector<std::pair<std::string,CAmount>> vec;
|
|
||||||
std::copy(mapBalances.begin(), mapBalances.end(), std::back_inserter<std::vector<std::pair<std::string,CAmount>>>(vec));
|
|
||||||
|
|
||||||
std::sort(vec.begin(), vec.end(), [](const std::pair<std::string, CAmount> &l, const std::pair<std::string,CAmount> &r)
|
|
||||||
{
|
|
||||||
if (l.second != r.second) {
|
|
||||||
return l.second > r.second;
|
|
||||||
}
|
|
||||||
return l.first > r.first;
|
|
||||||
});
|
|
||||||
|
|
||||||
//TODO: avoid calculating nTotalOut twice
|
|
||||||
CAmount nTotalOut = 0;
|
|
||||||
for (const UniValue& o : outputs.getValues()) {
|
|
||||||
if (!o.isObject())
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object");
|
|
||||||
|
|
||||||
UniValue av = find_value(o, "amount");
|
|
||||||
CAmount nAmount = AmountFromValue( av );
|
|
||||||
if (nAmount < 0)
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive");
|
|
||||||
|
|
||||||
nTotalOut += nAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
//GOAL: choose one random zaddress with enough funds
|
|
||||||
CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE; // default when params.size()<=3 (was uninitialized)
|
|
||||||
if (params.size() > 3) {
|
|
||||||
if (params[3].get_real() == 0.0) {
|
|
||||||
nFee = 0;
|
|
||||||
} else {
|
|
||||||
nFee = AmountFromValue( params[3] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the total amount needed in a single zaddr to use as fromaddress
|
|
||||||
CAmount nMinBal = nTotalOut + nFee;
|
|
||||||
|
|
||||||
std::vector<std::string> vPotentialAddresses;
|
|
||||||
for (auto & entry : vec) {
|
|
||||||
if(entry.second >= nMinBal) {
|
|
||||||
vPotentialAddresses.push_back(entry.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// select a random address with enough confirmed balance
|
|
||||||
auto nPotentials = vPotentialAddresses.size();
|
|
||||||
if (nPotentials > 0) {
|
|
||||||
LogPrintf("%s: Selecting one of %lu potential source zaddrs\n", __func__, nPotentials);
|
|
||||||
fromaddress = vPotentialAddresses[ GetRandInt(nPotentials) ];
|
|
||||||
} else {
|
|
||||||
// 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 {
|
} else {
|
||||||
CTxDestination taddr = DecodeDestination(fromaddress);
|
CTxDestination taddr = DecodeDestination(fromaddress);
|
||||||
fromTaddr = IsValidDestination(taddr);
|
fromTaddr = IsValidDestination(taddr);
|
||||||
|
|||||||
Reference in New Issue
Block a user