Skip to content

Commit 4db44ac

Browse files
committed
Merge #18202: refactor: consolidate sendmany and sendtoaddress code
08fc6f6 [rpc] refactor: consolidate sendmany and sendtoaddress code (Sjors Provoost) Pull request description: I consolidated code between these two RPC calls, since `sendtoaddress` is essentially `sendmany` with 1 destination. Unless I overlooked something, the only behaviour change is that some `sendtoaddress` error codes changed from `-4` to `-6`. The release note mentions this. Salvaged from #18201. ACKs for top commit: fjahr: Code review ACK 08fc6f6 jonatack: ACK 08fc6f6 meshcollider: Code review & functional test run ACK 08fc6f6 Tree-SHA512: 7b66c52fa0444a4d02fc3f81d9c2a386794d447616026a30111eda35fb46510475eea6506a9ceda00bb4e0230ebb758da5d236b3ac05c954c044fa68a1e3e909
2 parents 32302e5 + 08fc6f6 commit 4db44ac

File tree

4 files changed

+68
-81
lines changed

4 files changed

+68
-81
lines changed

doc/release-notes-18202.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Low-level RPC Changes
2+
---------------------
3+
4+
- To make RPC `sendtoaddress` more consistent with `sendmany` the following error
5+
`sendtoaddress` codes were changed from `-4` to `-6`:
6+
- Insufficient funds
7+
- Fee estimation failed
8+
- Transaction has too long of a mempool chain

src/wallet/rpcwallet.cpp

Lines changed: 54 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -359,36 +359,54 @@ static UniValue setlabel(const JSONRPCRequest& request)
359359
return NullUniValue;
360360
}
361361

362+
void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient> &recipients) {
363+
std::set<CTxDestination> destinations;
364+
int i = 0;
365+
for (const std::string& address: address_amounts.getKeys()) {
366+
CTxDestination dest = DecodeDestination(address);
367+
if (!IsValidDestination(dest)) {
368+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + address);
369+
}
362370

363-
static CTransactionRef SendMoney(CWallet* const pwallet, const CTxDestination& address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue)
364-
{
365-
CAmount curBalance = pwallet->GetBalance(0, coin_control.m_avoid_address_reuse).m_mine_trusted;
371+
if (destinations.count(dest)) {
372+
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + address);
373+
}
374+
destinations.insert(dest);
366375

367-
// Check amount
368-
if (nValue <= 0)
369-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");
376+
CScript script_pub_key = GetScriptForDestination(dest);
377+
CAmount amount = AmountFromValue(address_amounts[i++]);
370378

371-
if (nValue > curBalance)
372-
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
379+
bool subtract_fee = false;
380+
for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) {
381+
const UniValue& addr = subtract_fee_outputs[idx];
382+
if (addr.get_str() == address) {
383+
subtract_fee = true;
384+
}
385+
}
373386

374-
// Parse Bitcoin address
375-
CScript scriptPubKey = GetScriptForDestination(address);
387+
CRecipient recipient = {script_pub_key, amount, subtract_fee};
388+
recipients.push_back(recipient);
389+
}
390+
}
391+
392+
UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value)
393+
{
394+
EnsureWalletIsUnlocked(pwallet);
376395

377-
// Create and send the transaction
396+
// Shuffle recipient list
397+
std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());
398+
399+
// Send
378400
CAmount nFeeRequired = 0;
379-
bilingual_str error;
380-
std::vector<CRecipient> vecSend;
381401
int nChangePosRet = -1;
382-
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
383-
vecSend.push_back(recipient);
402+
bilingual_str error;
384403
CTransactionRef tx;
385-
if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control)) {
386-
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
387-
error = strprintf(Untranslated("Error: This transaction requires a transaction fee of at least %s"), FormatMoney(nFeeRequired));
388-
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
404+
bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
405+
if (!fCreated) {
406+
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
389407
}
390-
pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
391-
return tx;
408+
pwallet->CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
409+
return tx->GetHash().GetHex();
392410
}
393411

394412
static UniValue sendtoaddress(const JSONRPCRequest& request)
@@ -436,16 +454,6 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
436454

437455
LOCK(pwallet->cs_wallet);
438456

439-
CTxDestination dest = DecodeDestination(request.params[0].get_str());
440-
if (!IsValidDestination(dest)) {
441-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
442-
}
443-
444-
// Amount
445-
CAmount nAmount = AmountFromValue(request.params[1]);
446-
if (nAmount <= 0)
447-
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
448-
449457
// Wallet comments
450458
mapValue_t mapValue;
451459
if (!request.params[2].isNull() && !request.params[2].get_str().empty())
@@ -471,8 +479,18 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
471479

472480
EnsureWalletIsUnlocked(pwallet);
473481

474-
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
475-
return tx->GetHash().GetHex();
482+
UniValue address_amounts(UniValue::VOBJ);
483+
const std::string address = request.params[0].get_str();
484+
address_amounts.pushKV(address, request.params[1]);
485+
UniValue subtractFeeFromAmount(UniValue::VARR);
486+
if (fSubtractFeeFromAmount) {
487+
subtractFeeFromAmount.push_back(address);
488+
}
489+
490+
std::vector<CRecipient> recipients;
491+
ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
492+
493+
return SendMoney(pwallet, coin_control, recipients, mapValue);
476494
}
477495

478496
static UniValue listaddressgroupings(const JSONRPCRequest& request)
@@ -860,52 +878,10 @@ static UniValue sendmany(const JSONRPCRequest& request)
860878

861879
SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
862880

863-
std::set<CTxDestination> destinations;
864-
std::vector<CRecipient> vecSend;
881+
std::vector<CRecipient> recipients;
882+
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
865883

866-
std::vector<std::string> keys = sendTo.getKeys();
867-
for (const std::string& name_ : keys) {
868-
CTxDestination dest = DecodeDestination(name_);
869-
if (!IsValidDestination(dest)) {
870-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_);
871-
}
872-
873-
if (destinations.count(dest)) {
874-
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
875-
}
876-
destinations.insert(dest);
877-
878-
CScript scriptPubKey = GetScriptForDestination(dest);
879-
CAmount nAmount = AmountFromValue(sendTo[name_]);
880-
if (nAmount <= 0)
881-
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
882-
883-
bool fSubtractFeeFromAmount = false;
884-
for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) {
885-
const UniValue& addr = subtractFeeFromAmount[idx];
886-
if (addr.get_str() == name_)
887-
fSubtractFeeFromAmount = true;
888-
}
889-
890-
CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
891-
vecSend.push_back(recipient);
892-
}
893-
894-
EnsureWalletIsUnlocked(pwallet);
895-
896-
// Shuffle recipient list
897-
std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext());
898-
899-
// Send
900-
CAmount nFeeRequired = 0;
901-
int nChangePosRet = -1;
902-
bilingual_str error;
903-
CTransactionRef tx;
904-
bool fCreated = pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control);
905-
if (!fCreated)
906-
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
907-
pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
908-
return tx->GetHash().GetHex();
884+
return SendMoney(pwallet, coin_control, recipients, std::move(mapValue));
909885
}
910886

911887
static UniValue addmultisigaddress(const JSONRPCRequest& request)

test/functional/wallet_basic.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def run_test(self):
119119
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])
120120
self.nodes[2].lockunspent(False, [unspent_0])
121121
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
122-
assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
122+
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
123123
assert_equal([unspent_0], self.nodes[2].listlockunspent())
124124
self.nodes[2].lockunspent(True, [unspent_0])
125125
assert_equal(len(self.nodes[2].listlockunspent()), 0)
@@ -363,6 +363,9 @@ def run_test(self):
363363
assert_equal(tx_obj['amount'], Decimal('-0.0001'))
364364

365365
# General checks for errors from incorrect inputs
366+
# This will raise an exception because the amount is negative
367+
assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
368+
366369
# This will raise an exception because the amount type is wrong
367370
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
368371

@@ -590,7 +593,7 @@ def run_test(self):
590593

591594
node0_balance = self.nodes[0].getbalance()
592595
# With walletrejectlongchains we will not create the tx and store it in our wallet.
593-
assert_raises_rpc_error(-4, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
596+
assert_raises_rpc_error(-6, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
594597

595598
# Verify nothing new in wallet
596599
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))

test/functional/wallet_fallbackfee.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def run_test(self):
2222

2323
# test sending a tx with disabled fallback fee (must fail)
2424
self.restart_node(0, extra_args=["-fallbackfee=0"])
25-
assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
25+
assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
2626
assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].fundrawtransaction(self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})))
2727
assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendmany("", {self.nodes[0].getnewaddress(): 1}))
2828

0 commit comments

Comments
 (0)