Skip to content

Commit f86263b

Browse files
meshcolliderknst
authored andcommitted
Merge bitcoin#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 bitcoin#18201. ACKs for top commit: fjahr: Code review ACK 08fc6f6 jonatack: ACK 08fc6f6 meshcollider: Code review & functional test run ACK 08fc6f6 Tree-SHA512: 7b66c52fa0444a4d02fc3f81d9c2a386794d447616026a30111eda35fb46510475eea6506a9ceda00bb4e0230ebb758da5d236b3ac05c954c044fa68a1e3e909
1 parent fab41fd commit f86263b

File tree

4 files changed

+69
-86
lines changed

4 files changed

+69
-86
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: 55 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -352,40 +352,55 @@ static RPCHelpMan setlabel()
352352
};
353353
}
354354

355+
void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient> &recipients) {
356+
std::set<CTxDestination> destinations;
357+
int i = 0;
358+
for (const std::string& address: address_amounts.getKeys()) {
359+
CTxDestination dest = DecodeDestination(address);
360+
if (!IsValidDestination(dest)) {
361+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + address);
362+
}
355363

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

360-
// Check amount
361-
if (nValue <= 0)
362-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");
369+
CScript script_pub_key = GetScriptForDestination(dest);
370+
CAmount amount = AmountFromValue(address_amounts[i++]);
363371

364-
if (nValue > curBalance)
365-
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
372+
bool subtract_fee = false;
373+
for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) {
374+
const UniValue& addr = subtract_fee_outputs[idx];
375+
if (addr.get_str() == address) {
376+
subtract_fee = true;
377+
}
378+
}
366379

367-
if (coin_control.IsUsingCoinJoin()) {
368-
mapValue["DS"] = "1";
380+
CRecipient recipient = {script_pub_key, amount, subtract_fee};
381+
recipients.push_back(recipient);
369382
}
383+
}
370384

371-
// Parse Dash address
372-
CScript scriptPubKey = GetScriptForDestination(address);
385+
UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value)
386+
{
387+
EnsureWalletIsUnlocked(pwallet);
373388

374-
// Create and send the transaction
389+
if (coin_control.IsUsingCoinJoin()) {
390+
map_value["DS"] = "1";
391+
}
392+
393+
// Send
375394
CAmount nFeeRequired = 0;
376-
bilingual_str error;
377-
std::vector<CRecipient> vecSend;
378395
int nChangePosRet = -1;
379-
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
380-
vecSend.push_back(recipient);
396+
bilingual_str error;
381397
CTransactionRef tx;
382-
if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control)) {
383-
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
384-
error = strprintf(Untranslated("Error: This transaction requires a transaction fee of at least %s"), FormatMoney(nFeeRequired));
385-
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
398+
bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
399+
if (!fCreated) {
400+
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
386401
}
387-
pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
388-
return tx;
402+
pwallet->CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
403+
return tx->GetHash().GetHex();
389404
}
390405

391406
static RPCHelpMan sendtoaddress()
@@ -434,16 +449,6 @@ static RPCHelpMan sendtoaddress()
434449

435450
LOCK(pwallet->cs_wallet);
436451

437-
CTxDestination dest = DecodeDestination(request.params[0].get_str());
438-
if (!IsValidDestination(dest)) {
439-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
440-
}
441-
442-
// Amount
443-
CAmount nAmount = AmountFromValue(request.params[1]);
444-
if (nAmount <= 0)
445-
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
446-
447452
// Wallet comments
448453
mapValue_t mapValue;
449454
if (!request.params[2].isNull() && !request.params[2].get_str().empty())
@@ -471,8 +476,18 @@ static RPCHelpMan sendtoaddress()
471476

472477
EnsureWalletIsUnlocked(pwallet);
473478

474-
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
475-
return tx->GetHash().GetHex();
479+
UniValue address_amounts(UniValue::VOBJ);
480+
const std::string address = request.params[0].get_str();
481+
address_amounts.pushKV(address, request.params[1]);
482+
UniValue subtractFeeFromAmount(UniValue::VARR);
483+
if (fSubtractFeeFromAmount) {
484+
subtractFeeFromAmount.push_back(address);
485+
}
486+
487+
std::vector<CRecipient> recipients;
488+
ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
489+
490+
return SendMoney(pwallet, coin_control, recipients, mapValue);
476491
},
477492
};
478493
}
@@ -935,9 +950,9 @@ static RPCHelpMan sendmany()
935950
if (!request.params[4].isNull() && !request.params[4].get_str().empty())
936951
mapValue["comment"] = request.params[4].get_str();
937952

938-
UniValue subtractFeeFrom(UniValue::VARR);
953+
UniValue subtractFeeFromAmount(UniValue::VARR);
939954
if (!request.params[5].isNull())
940-
subtractFeeFrom = request.params[5].get_array();
955+
subtractFeeFromAmount = request.params[5].get_array();
941956

942957
// request.params[6] ("use_is") is deprecated and not used here
943958

@@ -949,53 +964,10 @@ static RPCHelpMan sendmany()
949964

950965
SetFeeEstimateMode(pwallet, coin_control, request.params[9], request.params[8]);
951966

952-
if (coin_control.IsUsingCoinJoin()) {
953-
mapValue["DS"] = "1";
954-
}
955-
956-
std::set<CTxDestination> destinations;
957-
std::vector<CRecipient> vecSend;
958-
959-
std::vector<std::string> keys = sendTo.getKeys();
960-
for (const std::string& name_ : keys) {
961-
CTxDestination dest = DecodeDestination(name_);
962-
if (!IsValidDestination(dest)) {
963-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + name_);
964-
}
965-
966-
if (destinations.count(dest)) {
967-
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
968-
}
969-
destinations.insert(dest);
967+
std::vector<CRecipient> recipients;
968+
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
970969

971-
CScript scriptPubKey = GetScriptForDestination(dest);
972-
CAmount nAmount = AmountFromValue(sendTo[name_]);
973-
if (nAmount <= 0)
974-
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
975-
976-
bool fSubtractFeeFromAmount = false;
977-
for (unsigned int idx = 0; idx < subtractFeeFrom.size(); idx++) {
978-
const UniValue& addr = subtractFeeFrom[idx];
979-
if (addr.get_str() == name_)
980-
fSubtractFeeFromAmount = true;
981-
}
982-
983-
CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
984-
vecSend.push_back(recipient);
985-
}
986-
987-
EnsureWalletIsUnlocked(pwallet);
988-
989-
// Send
990-
CAmount nFeeRequired = 0;
991-
int nChangePosRet = -1;
992-
bilingual_str error;
993-
CTransactionRef tx;
994-
bool fCreated = pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control);
995-
if (!fCreated)
996-
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
997-
pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
998-
return tx->GetHash().GetHex();
970+
return SendMoney(pwallet, coin_control, recipients, std::move(mapValue));
999971
},
1000972
};
1001973
}

test/functional/wallet_basic.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def run_test(self):
126126
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])
127127
self.nodes[2].lockunspent(False, [unspent_0])
128128
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
129-
assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 200)
129+
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 200)
130130
assert_equal([unspent_0], self.nodes[2].listlockunspent())
131131
self.nodes[2].lockunspent(True, [unspent_0])
132132
assert_equal(len(self.nodes[2].listlockunspent()), 0)
@@ -380,6 +380,9 @@ def run_test(self):
380380
assert_equal(tx_obj['amount'], Decimal('-0.0001'))
381381

382382
# General checks for errors from incorrect inputs
383+
# This will raise an exception because the amount is negative
384+
assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
385+
383386
# This will raise an exception because the amount type is wrong
384387
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
385388

@@ -608,7 +611,7 @@ def run_test(self):
608611

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

613616
# Verify nothing new in wallet
614617
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
@@ -24,7 +24,7 @@ def run_test(self):
2424

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

0 commit comments

Comments
 (0)