mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-02 09:46:52 -05:00
[rpc] refactor: consolidate sendmany and sendtoaddress code
The only new behavior is some error codes are changed from -4 to -6.
This commit is contained in:
parent
0101110f9b
commit
08fc6f6cfc
4 changed files with 72 additions and 85 deletions
8
doc/release-notes-18202.md
Normal file
8
doc/release-notes-18202.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Low-level RPC Changes
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
- To make RPC `sendtoaddress` more consistent with `sendmany` the following error
|
||||||
|
`sendtoaddress` codes were changed from `-4` to `-6`:
|
||||||
|
- Insufficient funds
|
||||||
|
- Fee estimation failed
|
||||||
|
- Transaction has too long of a mempool chain
|
|
@ -321,36 +321,54 @@ static UniValue setlabel(const JSONRPCRequest& request)
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient> &recipients) {
|
||||||
|
std::set<CTxDestination> destinations;
|
||||||
|
int i = 0;
|
||||||
|
for (const std::string& address: address_amounts.getKeys()) {
|
||||||
|
CTxDestination dest = DecodeDestination(address);
|
||||||
|
if (!IsValidDestination(dest)) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + address);
|
||||||
|
}
|
||||||
|
|
||||||
static CTransactionRef SendMoney(CWallet* const pwallet, const CTxDestination& address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue)
|
if (destinations.count(dest)) {
|
||||||
{
|
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + address);
|
||||||
CAmount curBalance = pwallet->GetBalance(0, coin_control.m_avoid_address_reuse).m_mine_trusted;
|
}
|
||||||
|
destinations.insert(dest);
|
||||||
|
|
||||||
// Check amount
|
CScript script_pub_key = GetScriptForDestination(dest);
|
||||||
if (nValue <= 0)
|
CAmount amount = AmountFromValue(address_amounts[i++]);
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");
|
|
||||||
|
|
||||||
if (nValue > curBalance)
|
bool subtract_fee = false;
|
||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
|
for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) {
|
||||||
|
const UniValue& addr = subtract_fee_outputs[idx];
|
||||||
|
if (addr.get_str() == address) {
|
||||||
|
subtract_fee = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse Bitcoin address
|
CRecipient recipient = {script_pub_key, amount, subtract_fee};
|
||||||
CScript scriptPubKey = GetScriptForDestination(address);
|
recipients.push_back(recipient);
|
||||||
|
|
||||||
// Create and send the transaction
|
|
||||||
CAmount nFeeRequired = 0;
|
|
||||||
bilingual_str error;
|
|
||||||
std::vector<CRecipient> vecSend;
|
|
||||||
int nChangePosRet = -1;
|
|
||||||
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
|
|
||||||
vecSend.push_back(recipient);
|
|
||||||
CTransactionRef tx;
|
|
||||||
if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control)) {
|
|
||||||
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
|
|
||||||
error = strprintf(Untranslated("Error: This transaction requires a transaction fee of at least %s"), FormatMoney(nFeeRequired));
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
|
|
||||||
}
|
}
|
||||||
pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
|
}
|
||||||
return tx;
|
|
||||||
|
UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value)
|
||||||
|
{
|
||||||
|
EnsureWalletIsUnlocked(pwallet);
|
||||||
|
|
||||||
|
// Shuffle recipient list
|
||||||
|
std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());
|
||||||
|
|
||||||
|
// Send
|
||||||
|
CAmount nFeeRequired = 0;
|
||||||
|
int nChangePosRet = -1;
|
||||||
|
bilingual_str error;
|
||||||
|
CTransactionRef tx;
|
||||||
|
bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
|
||||||
|
if (!fCreated) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
|
||||||
|
}
|
||||||
|
pwallet->CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
|
||||||
|
return tx->GetHash().GetHex();
|
||||||
}
|
}
|
||||||
|
|
||||||
static UniValue sendtoaddress(const JSONRPCRequest& request)
|
static UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||||
|
@ -398,16 +416,6 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||||
|
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
||||||
CTxDestination dest = DecodeDestination(request.params[0].get_str());
|
|
||||||
if (!IsValidDestination(dest)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Amount
|
|
||||||
CAmount nAmount = AmountFromValue(request.params[1]);
|
|
||||||
if (nAmount <= 0)
|
|
||||||
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
|
|
||||||
|
|
||||||
// Wallet comments
|
// Wallet comments
|
||||||
mapValue_t mapValue;
|
mapValue_t mapValue;
|
||||||
if (!request.params[2].isNull() && !request.params[2].get_str().empty())
|
if (!request.params[2].isNull() && !request.params[2].get_str().empty())
|
||||||
|
@ -441,8 +449,18 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
||||||
|
|
||||||
EnsureWalletIsUnlocked(pwallet);
|
EnsureWalletIsUnlocked(pwallet);
|
||||||
|
|
||||||
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
|
UniValue address_amounts(UniValue::VOBJ);
|
||||||
return tx->GetHash().GetHex();
|
const std::string address = request.params[0].get_str();
|
||||||
|
address_amounts.pushKV(address, request.params[1]);
|
||||||
|
UniValue subtractFeeFromAmount(UniValue::VARR);
|
||||||
|
if (fSubtractFeeFromAmount) {
|
||||||
|
subtractFeeFromAmount.push_back(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CRecipient> recipients;
|
||||||
|
ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
|
||||||
|
|
||||||
|
return SendMoney(pwallet, coin_control, recipients, mapValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
static UniValue listaddressgroupings(const JSONRPCRequest& request)
|
static UniValue listaddressgroupings(const JSONRPCRequest& request)
|
||||||
|
@ -840,52 +858,10 @@ static UniValue sendmany(const JSONRPCRequest& request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<CTxDestination> destinations;
|
std::vector<CRecipient> recipients;
|
||||||
std::vector<CRecipient> vecSend;
|
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
|
||||||
|
|
||||||
std::vector<std::string> keys = sendTo.getKeys();
|
return SendMoney(pwallet, coin_control, recipients, std::move(mapValue));
|
||||||
for (const std::string& name_ : keys) {
|
|
||||||
CTxDestination dest = DecodeDestination(name_);
|
|
||||||
if (!IsValidDestination(dest)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destinations.count(dest)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
|
|
||||||
}
|
|
||||||
destinations.insert(dest);
|
|
||||||
|
|
||||||
CScript scriptPubKey = GetScriptForDestination(dest);
|
|
||||||
CAmount nAmount = AmountFromValue(sendTo[name_]);
|
|
||||||
if (nAmount <= 0)
|
|
||||||
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
|
|
||||||
|
|
||||||
bool fSubtractFeeFromAmount = false;
|
|
||||||
for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) {
|
|
||||||
const UniValue& addr = subtractFeeFromAmount[idx];
|
|
||||||
if (addr.get_str() == name_)
|
|
||||||
fSubtractFeeFromAmount = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
|
|
||||||
vecSend.push_back(recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureWalletIsUnlocked(pwallet);
|
|
||||||
|
|
||||||
// Shuffle recipient list
|
|
||||||
std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext());
|
|
||||||
|
|
||||||
// Send
|
|
||||||
CAmount nFeeRequired = 0;
|
|
||||||
int nChangePosRet = -1;
|
|
||||||
bilingual_str error;
|
|
||||||
CTransactionRef tx;
|
|
||||||
bool fCreated = pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control);
|
|
||||||
if (!fCreated)
|
|
||||||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
|
|
||||||
pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */);
|
|
||||||
return tx->GetHash().GetHex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static UniValue addmultisigaddress(const JSONRPCRequest& request)
|
static UniValue addmultisigaddress(const JSONRPCRequest& request)
|
||||||
|
|
|
@ -119,7 +119,7 @@ class WalletTest(BitcoinTestFramework):
|
||||||
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])
|
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output", self.nodes[2].lockunspent, True, [unspent_0])
|
||||||
self.nodes[2].lockunspent(False, [unspent_0])
|
self.nodes[2].lockunspent(False, [unspent_0])
|
||||||
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
|
assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0])
|
||||||
assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
|
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
|
||||||
assert_equal([unspent_0], self.nodes[2].listlockunspent())
|
assert_equal([unspent_0], self.nodes[2].listlockunspent())
|
||||||
self.nodes[2].lockunspent(True, [unspent_0])
|
self.nodes[2].lockunspent(True, [unspent_0])
|
||||||
assert_equal(len(self.nodes[2].listlockunspent()), 0)
|
assert_equal(len(self.nodes[2].listlockunspent()), 0)
|
||||||
|
@ -309,6 +309,9 @@ class WalletTest(BitcoinTestFramework):
|
||||||
assert_equal(tx_obj['amount'], Decimal('-0.0001'))
|
assert_equal(tx_obj['amount'], Decimal('-0.0001'))
|
||||||
|
|
||||||
# General checks for errors from incorrect inputs
|
# General checks for errors from incorrect inputs
|
||||||
|
# This will raise an exception because the amount is negative
|
||||||
|
assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
|
||||||
|
|
||||||
# This will raise an exception because the amount type is wrong
|
# This will raise an exception because the amount type is wrong
|
||||||
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
|
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
|
||||||
|
|
||||||
|
@ -468,7 +471,7 @@ class WalletTest(BitcoinTestFramework):
|
||||||
|
|
||||||
node0_balance = self.nodes[0].getbalance()
|
node0_balance = self.nodes[0].getbalance()
|
||||||
# With walletrejectlongchains we will not create the tx and store it in our wallet.
|
# With walletrejectlongchains we will not create the tx and store it in our wallet.
|
||||||
assert_raises_rpc_error(-4, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
|
assert_raises_rpc_error(-6, "Transaction has too long of a mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
|
||||||
|
|
||||||
# Verify nothing new in wallet
|
# Verify nothing new in wallet
|
||||||
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
|
assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999)))
|
||||||
|
|
|
@ -22,7 +22,7 @@ class WalletRBFTest(BitcoinTestFramework):
|
||||||
|
|
||||||
# test sending a tx with disabled fallback fee (must fail)
|
# test sending a tx with disabled fallback fee (must fail)
|
||||||
self.restart_node(0, extra_args=["-fallbackfee=0"])
|
self.restart_node(0, extra_args=["-fallbackfee=0"])
|
||||||
assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
|
assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1))
|
||||||
assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].fundrawtransaction(self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})))
|
assert_raises_rpc_error(-4, "Fee estimation failed", lambda: self.nodes[0].fundrawtransaction(self.nodes[0].createrawtransaction([], {self.nodes[0].getnewaddress(): 1})))
|
||||||
assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendmany("", {self.nodes[0].getnewaddress(): 1}))
|
assert_raises_rpc_error(-6, "Fee estimation failed", lambda: self.nodes[0].sendmany("", {self.nodes[0].getnewaddress(): 1}))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue