Merge #15596: rpc: Ignore sendmany::minconf as dummy value

fabfb79673 doc: Add release notes for 15596 (MarcoFalke)
fac1a0fe54 wallet: Remove unused GetLegacyBalance (MarcoFalke)
faa3a246e8 scripted-diff: wallet: Rename pcoin to wtx (MarcoFalke)
fae5f874d5 rpc: Document that minconf is an ignored dummy value (MarcoFalke)

Pull request description:

  Other RPCs such as `sendtoaddress` don't have this option at all and `sendmany` should by default spend from (lets say) our change.

ACKs for commit fabfb7:
  jnewbery:
    utACK fabfb79673
  ryanofsky:
    utACK fabfb79673. Nice writeup! Release notes are only change since previous review.

Tree-SHA512: 2526ead2330be7c2beb78b96bc5e55440566c4a3a809bbbd66f5c9fc517f6890affa5d14005dc102644d49679a374510f9507255e870cf88aaa63e429beef658
This commit is contained in:
MarcoFalke 2019-04-04 13:17:23 -04:00
commit daef20fb50
No known key found for this signature in database
GPG key ID: D2EA4850E7528B25
4 changed files with 65 additions and 102 deletions

View file

@ -66,9 +66,21 @@ platform.
Notable changes
===============
Example item
Updated RPCs
------------
Note: some low-level RPC changes mainly useful for testing are described in the
Low-level Changes section below.
* The `sendmany` RPC had an argument `minconf` that was not well specified and
would lead to RPC errors even when the wallet's coin selection would succeed.
The `sendtoaddress` RPC never had this check, so to normalize the behavior,
`minconf` is now ignored in `sendmany`. If the coin selection does not
succeed due to missing coins, it will still throw an RPC error. Be reminded
that coin selection is influenced by the `-spendzeroconfchange`,
`-limitancestorcount`, `-limitdescendantcount` and `-walletrejectlongchains`
command line arguments.
Low-level changes
=================

View file

@ -807,9 +807,7 @@ static UniValue sendmany(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() < 2 || request.params.size() > 8)
throw std::runtime_error(
RPCHelpMan{"sendmany",
const RPCHelpMan help{"sendmany",
"\nSend multiple times. Amounts are double-precision floating point numbers." +
HelpRequiringPassphrase(pwallet) + "\n",
{
@ -819,7 +817,7 @@ static UniValue sendmany(const JSONRPCRequest& request)
{"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"},
},
},
{"minconf", RPCArg::Type::NUM, /* default */ "1", "Only use the balance confirmed at least this many times."},
{"minconf", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "Ignored dummy value"},
{"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment"},
{"subtractfeefrom", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array with addresses.\n"
" The fee will be equally deducted from the amount of each selected address.\n"
@ -850,7 +848,11 @@ static UniValue sendmany(const JSONRPCRequest& request)
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"")
},
}.ToString());
};
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
throw std::runtime_error(help.ToString());
}
// Make sure the results are valid at least up to the most recent block
// the user could have gotten from another RPC command prior to now
@ -867,9 +869,6 @@ static UniValue sendmany(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\"");
}
UniValue sendTo = request.params[1].get_obj();
int nMinDepth = 1;
if (!request.params[2].isNull())
nMinDepth = request.params[2].get_int();
mapValue_t mapValue;
if (!request.params[3].isNull() && !request.params[3].get_str().empty())
@ -897,7 +896,6 @@ static UniValue sendmany(const JSONRPCRequest& request)
std::set<CTxDestination> destinations;
std::vector<CRecipient> vecSend;
CAmount totalAmount = 0;
std::vector<std::string> keys = sendTo.getKeys();
for (const std::string& name_ : keys) {
CTxDestination dest = DecodeDestination(name_);
@ -914,7 +912,6 @@ static UniValue sendmany(const JSONRPCRequest& request)
CAmount nAmount = AmountFromValue(sendTo[name_]);
if (nAmount <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
totalAmount += nAmount;
bool fSubtractFeeFromAmount = false;
for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) {
@ -929,11 +926,6 @@ static UniValue sendmany(const JSONRPCRequest& request)
EnsureWalletIsUnlocked(pwallet);
// Check funds
if (totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth)) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Wallet has insufficient funds");
}
// Shuffle recipient list
std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext());

View file

@ -2162,9 +2162,9 @@ CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) con
LOCK(cs_wallet);
for (const auto& entry : mapWallet)
{
const CWalletTx* pcoin = &entry.second;
if (pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) >= min_depth) {
nTotal += pcoin->GetAvailableCredit(*locked_chain, true, filter);
const CWalletTx& wtx = entry.second;
if (wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) >= min_depth) {
nTotal += wtx.GetAvailableCredit(*locked_chain, true, filter);
}
}
}
@ -2180,9 +2180,9 @@ CAmount CWallet::GetUnconfirmedBalance() const
LOCK(cs_wallet);
for (const auto& entry : mapWallet)
{
const CWalletTx* pcoin = &entry.second;
if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool())
nTotal += pcoin->GetAvailableCredit(*locked_chain);
const CWalletTx& wtx = entry.second;
if (!wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) == 0 && wtx.InMempool())
nTotal += wtx.GetAvailableCredit(*locked_chain);
}
}
return nTotal;
@ -2196,8 +2196,8 @@ CAmount CWallet::GetImmatureBalance() const
LOCK(cs_wallet);
for (const auto& entry : mapWallet)
{
const CWalletTx* pcoin = &entry.second;
nTotal += pcoin->GetImmatureCredit(*locked_chain);
const CWalletTx& wtx = entry.second;
nTotal += wtx.GetImmatureCredit(*locked_chain);
}
}
return nTotal;
@ -2211,9 +2211,9 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
LOCK(cs_wallet);
for (const auto& entry : mapWallet)
{
const CWalletTx* pcoin = &entry.second;
if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool())
nTotal += pcoin->GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY);
const CWalletTx& wtx = entry.second;
if (!wtx.IsTrusted(*locked_chain) && wtx.GetDepthInMainChain(*locked_chain) == 0 && wtx.InMempool())
nTotal += wtx.GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY);
}
}
return nTotal;
@ -2227,53 +2227,13 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
LOCK(cs_wallet);
for (const auto& entry : mapWallet)
{
const CWalletTx* pcoin = &entry.second;
nTotal += pcoin->GetImmatureWatchOnlyCredit(*locked_chain);
const CWalletTx& wtx = entry.second;
nTotal += wtx.GetImmatureWatchOnlyCredit(*locked_chain);
}
}
return nTotal;
}
// Calculate total balance in a different way from GetBalance. The biggest
// difference is that GetBalance sums up all unspent TxOuts paying to the
// wallet, while this sums up both spent and unspent TxOuts paying to the
// wallet, and then subtracts the values of TxIns spending from the wallet. This
// also has fewer restrictions on which unconfirmed transactions are considered
// trusted.
CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) const
{
auto locked_chain = chain().lock();
LOCK(cs_wallet);
CAmount balance = 0;
for (const auto& entry : mapWallet) {
const CWalletTx& wtx = entry.second;
const int depth = wtx.GetDepthInMainChain(*locked_chain);
if (depth < 0 || !locked_chain->checkFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase(*locked_chain)) {
continue;
}
// Loop through tx outputs and add incoming payments. For outgoing txs,
// treat change outputs specially, as part of the amount debited.
CAmount debit = wtx.GetDebit(filter);
const bool outgoing = debit > 0;
for (const CTxOut& out : wtx.tx->vout) {
if (outgoing && IsChange(out)) {
debit -= out.nValue;
} else if (IsMine(out) & filter && depth >= minDepth) {
balance += out.nValue;
}
}
// For outgoing txs, subtract amount debited.
if (outgoing) {
balance -= debit;
}
}
return balance;
}
CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
{
auto locked_chain = chain().lock();
@ -2300,25 +2260,25 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
for (const auto& entry : mapWallet)
{
const uint256& wtxid = entry.first;
const CWalletTx* pcoin = &entry.second;
const CWalletTx& wtx = entry.second;
if (!locked_chain.checkFinalTx(*pcoin->tx)) {
if (!locked_chain.checkFinalTx(*wtx.tx)) {
continue;
}
if (pcoin->IsImmatureCoinBase(locked_chain))
if (wtx.IsImmatureCoinBase(locked_chain))
continue;
int nDepth = pcoin->GetDepthInMainChain(locked_chain);
int nDepth = wtx.GetDepthInMainChain(locked_chain);
if (nDepth < 0)
continue;
// We should not consider coins which aren't at least in our mempool
// It's possible for these to be conflicted via ancestors which we may never be able to detect
if (nDepth == 0 && !pcoin->InMempool())
if (nDepth == 0 && !wtx.InMempool())
continue;
bool safeTx = pcoin->IsTrusted(locked_chain);
bool safeTx = wtx.IsTrusted(locked_chain);
// We should not consider coins from transactions that are replacing
// other transactions.
@ -2335,7 +2295,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
// be a 1-block reorg away from the chain where transactions A and C
// were accepted to another chain where B, B', and C were all
// accepted.
if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) {
safeTx = false;
}
@ -2347,7 +2307,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
// intending to replace A', but potentially resulting in a scenario
// where A, A', and D could all be accepted (instead of just B and
// D, or just A and A' like the user would want).
if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) {
safeTx = false;
}
@ -2358,8 +2318,8 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
if (nDepth < nMinDepth || nDepth > nMaxDepth)
continue;
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount)
continue;
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
@ -2371,20 +2331,20 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
if (IsSpent(locked_chain, wtxid, i))
continue;
isminetype mine = IsMine(pcoin->tx->vout[i]);
isminetype mine = IsMine(wtx.tx->vout[i]);
if (mine == ISMINE_NO) {
continue;
}
bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey);
bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey);
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
// Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) {
nTotal += pcoin->tx->vout[i].nValue;
nTotal += wtx.tx->vout[i].nValue;
if (nTotal >= nMinimumSumAmount) {
return;
@ -2542,13 +2502,13 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
if (it != mapWallet.end())
{
const CWalletTx* pcoin = &it->second;
const CWalletTx& wtx = it->second;
// Clearly invalid input, fail
if (pcoin->tx->vout.size() <= outpoint.n)
if (wtx.tx->vout.size() <= outpoint.n)
return false;
// Just to calculate the marginal byte size
nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue;
setPresetCoins.insert(CInputCoin(pcoin->tx, outpoint.n));
nValueFromPresetInputs += wtx.tx->vout[outpoint.n].nValue;
setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
}
@ -3586,27 +3546,27 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain:
LOCK(cs_wallet);
for (const auto& walletEntry : mapWallet)
{
const CWalletTx *pcoin = &walletEntry.second;
const CWalletTx& wtx = walletEntry.second;
if (!pcoin->IsTrusted(locked_chain))
if (!wtx.IsTrusted(locked_chain))
continue;
if (pcoin->IsImmatureCoinBase(locked_chain))
if (wtx.IsImmatureCoinBase(locked_chain))
continue;
int nDepth = pcoin->GetDepthInMainChain(locked_chain);
if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1))
int nDepth = wtx.GetDepthInMainChain(locked_chain);
if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1))
continue;
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++)
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
{
CTxDestination addr;
if (!IsMine(pcoin->tx->vout[i]))
if (!IsMine(wtx.tx->vout[i]))
continue;
if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr))
if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
continue;
CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue;
CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
if (!balances.count(addr))
balances[addr] = 0;
@ -3626,13 +3586,13 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings()
for (const auto& walletEntry : mapWallet)
{
const CWalletTx *pcoin = &walletEntry.second;
const CWalletTx& wtx = walletEntry.second;
if (pcoin->tx->vin.size() > 0)
if (wtx.tx->vin.size() > 0)
{
bool any_mine = false;
// group all input addresses with each other
for (const CTxIn& txin : pcoin->tx->vin)
for (const CTxIn& txin : wtx.tx->vin)
{
CTxDestination address;
if(!IsMine(txin)) /* If this input isn't mine, ignore it */
@ -3646,7 +3606,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings()
// group change with input addresses
if (any_mine)
{
for (const CTxOut& txout : pcoin->tx->vout)
for (const CTxOut& txout : wtx.tx->vout)
if (IsChange(txout))
{
CTxDestination txoutAddr;
@ -3663,7 +3623,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings()
}
// group lone addrs by themselves
for (const auto& txout : pcoin->tx->vout)
for (const auto& txout : wtx.tx->vout)
if (IsMine(txout))
{
CTxDestination address;

View file

@ -952,7 +952,6 @@ public:
CAmount GetImmatureBalance() const;
CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const;
CAmount GetLegacyBalance(const isminefilter& filter, int minDepth) const;
CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const;
OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend);