aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/rpcwallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/rpcwallet.cpp')
-rw-r--r--src/wallet/rpcwallet.cpp398
1 files changed, 378 insertions, 20 deletions
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 2428ef04e..36753d111 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -11,8 +11,10 @@
#include "init.h"
#include "validation.h"
#include "net.h"
+#include "policy/policy.h"
#include "policy/rbf.h"
#include "rpc/server.h"
+#include "script/sign.h"
#include "timedata.h"
#include "util.h"
#include "utilmoneystr.h"
@@ -666,8 +668,19 @@ UniValue getbalance(const JSONRPCRequest& request)
"Note that the account \"\" is not the same as leaving the parameter out.\n"
"The server total may be different to the balance in the default \"\" account.\n"
"\nArguments:\n"
- "1. \"account\" (string, optional) DEPRECATED. The selected account, or \"*\" for entire wallet. It may be the default account using \"\".\n"
- "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
+ "1. \"account\" (string, optional) DEPRECATED. The account string may be given as a\n"
+ " specific account name to find the balance associated with wallet keys in\n"
+ " a named account, or as the empty string (\"\") to find the balance\n"
+ " associated with wallet keys not in any named account, or as \"*\" to find\n"
+ " the balance associated with all wallet keys regardless of account.\n"
+ " When this option is specified, it calculates the balance in a different\n"
+ " way than when it is not specified, and which can count spends twice when\n"
+ " there are conflicting pending transactions (such as those created by\n"
+ " the bumpfee command), temporarily resulting in low or even negative\n"
+ " balances. In general, account balance calculation is not considered\n"
+ " reliable and has resulted in confusing outcomes, so it is recommended to\n"
+ " avoid passing this argument.\n"
+ "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
"3. include_watchonly (bool, optional, default=false) Also include balance in watch-only addresses (see 'importaddress')\n"
"\nResult:\n"
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n"
@@ -694,9 +707,12 @@ UniValue getbalance(const JSONRPCRequest& request)
filter = filter | ISMINE_WATCH_ONLY;
if (request.params[0].get_str() == "*") {
- // Calculate total balance a different way from GetBalance()
- // (GetBalance() sums up all unspent TxOuts)
- // getbalance and "getbalance * 1 true" should return the same number
+ // Calculate total balance in a very 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 nBalance = 0;
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
{
@@ -1679,7 +1695,7 @@ UniValue listsinceblock(const JSONRPCRequest& request)
LOCK2(cs_main, pwalletMain->cs_wallet);
- CBlockIndex *pindex = NULL;
+ const CBlockIndex *pindex = NULL;
int target_confirms = 1;
isminefilter filter = ISMINE_SPENDABLE;
@@ -1690,7 +1706,16 @@ UniValue listsinceblock(const JSONRPCRequest& request)
blockId.SetHex(request.params[0].get_str());
BlockMap::iterator it = mapBlockIndex.find(blockId);
if (it != mapBlockIndex.end())
+ {
pindex = it->second;
+ if (chainActive[pindex->nHeight] != pindex)
+ {
+ // the block being asked for is a part of a deactivated chain;
+ // we don't want to depend on its perceived height in the block
+ // chain, we want to instead use the last common ancestor
+ pindex = chainActive.FindFork(pindex);
+ }
+ }
}
if (request.params.size() > 1)
@@ -1701,9 +1726,10 @@ UniValue listsinceblock(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
}
- if(request.params.size() > 2)
- if(request.params[2].get_bool())
- filter = filter | ISMINE_WATCH_ONLY;
+ if (request.params.size() > 2 && request.params[2].get_bool())
+ {
+ filter = filter | ISMINE_WATCH_ONLY;
+ }
int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1;
@@ -2364,9 +2390,9 @@ UniValue listunspent(const JSONRPCRequest& request)
if (!EnsureWalletIsAvailable(request.fHelp))
return NullUniValue;
- if (request.fHelp || request.params.size() > 3)
+ if (request.fHelp || request.params.size() > 4)
throw runtime_error(
- "listunspent ( minconf maxconf [\"addresses\",...] )\n"
+ "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] )\n"
"\nReturns array of unspent transaction outputs\n"
"with between minconf and maxconf (inclusive) confirmations.\n"
"Optionally filter to only include txouts paid to specified addresses.\n"
@@ -2378,6 +2404,10 @@ UniValue listunspent(const JSONRPCRequest& request)
" \"address\" (string) bitcoin address\n"
" ,...\n"
" ]\n"
+ "4. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\n"
+ " because they come from unconfirmed untrusted transactions or unconfirmed\n"
+ " replacement transactions (cases where we are less sure that a conflicting\n"
+ " transaction won't be mined).\n"
"\nResult\n"
"[ (array of json object)\n"
" {\n"
@@ -2386,7 +2416,7 @@ UniValue listunspent(const JSONRPCRequest& request)
" \"address\" : \"address\", (string) the bitcoin address\n"
" \"account\" : \"account\", (string) DEPRECATED. The associated account, or \"\" for the default account\n"
" \"scriptPubKey\" : \"key\", (string) the script key\n"
- " \"amount\" : x.xxx, (numeric) the transaction amount in " + CURRENCY_UNIT + "\n"
+ " \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n"
" \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n"
" \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
@@ -2401,18 +2431,21 @@ UniValue listunspent(const JSONRPCRequest& request)
+ HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"")
);
- RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VARR));
-
int nMinDepth = 1;
- if (request.params.size() > 0)
+ if (request.params.size() > 0 && !request.params[0].isNull()) {
+ RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
nMinDepth = request.params[0].get_int();
+ }
int nMaxDepth = 9999999;
- if (request.params.size() > 1)
+ if (request.params.size() > 1 && !request.params[1].isNull()) {
+ RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
nMaxDepth = request.params[1].get_int();
+ }
set<CBitcoinAddress> setAddress;
- if (request.params.size() > 2) {
+ if (request.params.size() > 2 && !request.params[2].isNull()) {
+ RPCTypeCheckArgument(request.params[2], UniValue::VARR);
UniValue inputs = request.params[2].get_array();
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
const UniValue& input = inputs[idx];
@@ -2425,11 +2458,17 @@ UniValue listunspent(const JSONRPCRequest& request)
}
}
+ bool include_unsafe = true;
+ if (request.params.size() > 3 && !request.params[3].isNull()) {
+ RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
+ include_unsafe = request.params[3].get_bool();
+ }
+
UniValue results(UniValue::VARR);
vector<COutput> vecOutputs;
assert(pwalletMain != NULL);
LOCK2(cs_main, pwalletMain->cs_wallet);
- pwalletMain->AvailableCoins(vecOutputs, false, NULL, true);
+ pwalletMain->AvailableCoins(vecOutputs, !include_unsafe, NULL, true);
BOOST_FOREACH(const COutput& out, vecOutputs) {
if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth)
continue;
@@ -2496,6 +2535,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
" \"changePosition\" (numeric, optional, default random) The index of the change output\n"
" \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
" \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
+ " \"reserveChangeKey\" (boolean, optional, default true) Reserves the change output key from the keypool\n"
" \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific feerate (" + CURRENCY_UNIT + " per KB)\n"
" \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n"
" The fee will be equally deducted from the amount of each specified output.\n"
@@ -2528,6 +2568,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
int changePosition = -1;
bool includeWatching = false;
bool lockUnspents = false;
+ bool reserveChangeKey = true;
CFeeRate feeRate = CFeeRate(0);
bool overrideEstimatedFeerate = false;
UniValue subtractFeeFromOutputs;
@@ -2549,6 +2590,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
{"changePosition", UniValueType(UniValue::VNUM)},
{"includeWatching", UniValueType(UniValue::VBOOL)},
{"lockUnspents", UniValueType(UniValue::VBOOL)},
+ {"reserveChangeKey", UniValueType(UniValue::VBOOL)},
{"feeRate", UniValueType()}, // will be checked below
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
},
@@ -2572,6 +2614,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
if (options.exists("lockUnspents"))
lockUnspents = options["lockUnspents"].get_bool();
+ if (options.exists("reserveChangeKey"))
+ reserveChangeKey = options["reserveChangeKey"].get_bool();
+
if (options.exists("feeRate"))
{
feeRate = CFeeRate(AmountFromValue(options["feeRate"]));
@@ -2608,7 +2653,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
CAmount nFeeOut;
string strFailReason;
- if(!pwalletMain->FundTransaction(tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, setSubtractFeeFromOutputs, changeAddress))
+ if(!pwalletMain->FundTransaction(tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, setSubtractFeeFromOutputs, reserveChangeKey, changeAddress))
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
UniValue result(UniValue::VOBJ);
@@ -2619,6 +2664,318 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
return result;
}
+// Calculate the size of the transaction assuming all signatures are max size
+// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
+// TODO: re-use this in CWallet::CreateTransaction (right now
+// CreateTransaction uses the constructed dummy-signed tx to do a priority
+// calculation, but we should be able to refactor after priority is removed).
+// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
+// be IsAllFromMe).
+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx)
+{
+ CMutableTransaction txNew(tx);
+ std::vector<pair<CWalletTx *, unsigned int>> vCoins;
+ // Look up the inputs. We should have already checked that this transaction
+ // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
+ // wallet, with a valid index into the vout array.
+ for (auto& input : tx.vin) {
+ const auto mi = pwalletMain->mapWallet.find(input.prevout.hash);
+ assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
+ vCoins.emplace_back(make_pair(&(mi->second), input.prevout.n));
+ }
+ if (!pwalletMain->DummySignTx(txNew, vCoins)) {
+ // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
+ // implies that we can sign for every input.
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that cannot be signed");
+ }
+ return GetVirtualTransactionSize(txNew);
+}
+
+UniValue bumpfee(const JSONRPCRequest& request)
+{
+ if (!EnsureWalletIsAvailable(request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
+ throw runtime_error(
+ "bumpfee \"txid\" ( options ) \n"
+ "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
+ "An opt-in RBF transaction with the given txid must be in the wallet.\n"
+ "The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n"
+ "If the change output is not big enough to cover the increased fee, the command will currently fail\n"
+ "instead of adding new inputs to compensate. (A future implementation could improve this.)\n"
+ "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n"
+ "By default, the new fee will be calculated automatically using estimatefee.\n"
+ "The user can specify a confirmation target for estimatefee.\n"
+ "Alternatively, the user can specify totalFee, or use RPC setpaytxfee to set a higher fee rate.\n"
+ "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n"
+ "returned by getnetworkinfo) to enter the node's mempool.\n"
+ "\nArguments:\n"
+ "1. txid (string, required) The txid to be bumped\n"
+ "2. options (object, optional)\n"
+ " {\n"
+ " \"confTarget\" (numeric, optional) Confirmation target (in blocks)\n"
+ " \"totalFee\" (numeric, optional) Total fee (NOT feerate) to pay, in satoshis.\n"
+ " In rare cases, the actual fee paid might be slightly higher than the specified\n"
+ " totalFee if the tx change output has to be removed because it is too close to\n"
+ " the dust threshold.\n"
+ " \"replaceable\" (boolean, optional, default true) Whether the new transaction should still be\n"
+ " marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n"
+ " be left unchanged from the original. If false, any input sequence numbers in the\n"
+ " original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n"
+ " so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
+ " still be replacable in practice, for example if it has unconfirmed ancestors which\n"
+ " are replaceable).\n"
+ " }\n"
+ "\nResult:\n"
+ "{\n"
+ " \"txid\": \"value\", (string) The id of the new transaction\n"
+ " \"origfee\": n, (numeric) Fee of the replaced transaction\n"
+ " \"fee\": n, (numeric) Fee of the new transaction\n"
+ " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n"
+ "}\n"
+ "\nExamples:\n"
+ "\nBump the fee, get the new transaction\'s txid\n" +
+ HelpExampleCli("bumpfee", "<txid>"));
+ }
+
+ RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ));
+ uint256 hash;
+ hash.SetHex(request.params[0].get_str());
+
+ // retrieve the original tx from the wallet
+ LOCK2(cs_main, pwalletMain->cs_wallet);
+ EnsureWalletIsUnlocked();
+ if (!pwalletMain->mapWallet.count(hash)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
+ }
+ CWalletTx& wtx = pwalletMain->mapWallet[hash];
+
+ if (pwalletMain->HasWalletSpend(hash)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction has descendants in the wallet");
+ }
+
+ {
+ LOCK(mempool.cs);
+ auto it = mempool.mapTx.find(hash);
+ if (it != mempool.mapTx.end() && it->GetCountWithDescendants() > 1) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction has descendants in the mempool");
+ }
+ }
+
+ if (wtx.GetDepthInMainChain() != 0) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction has been mined, or is conflicted with a mined transaction");
+ }
+
+ if (!SignalsOptInRBF(wtx)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction is not BIP 125 replaceable");
+ }
+
+ if (wtx.mapValue.count("replaced_by_txid")) {
+ throw JSONRPCError(RPC_INVALID_REQUEST, strprintf("Cannot bump transaction %s which was already bumped by transaction %s", hash.ToString(), wtx.mapValue.at("replaced_by_txid")));
+ }
+
+ // check that original tx consists entirely of our inputs
+ // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
+ if (!pwalletMain->IsAllFromMe(wtx, ISMINE_SPENDABLE)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that don't belong to this wallet");
+ }
+
+ // figure out which output was change
+ // if there was no change output or multiple change outputs, fail
+ int nOutput = -1;
+ for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
+ if (pwalletMain->IsChange(wtx.tx->vout[i])) {
+ if (nOutput != -1) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction has multiple change outputs");
+ }
+ nOutput = i;
+ }
+ }
+ if (nOutput == -1) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not have a change output");
+ }
+
+ // Calculate the expected size of the new transaction.
+ int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
+ const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx);
+
+ // optional parameters
+ bool specifiedConfirmTarget = false;
+ int newConfirmTarget = nTxConfirmTarget;
+ CAmount totalFee = 0;
+ bool replaceable = true;
+ if (request.params.size() > 1) {
+ UniValue options = request.params[1];
+ RPCTypeCheckObj(options,
+ {
+ {"confTarget", UniValueType(UniValue::VNUM)},
+ {"totalFee", UniValueType(UniValue::VNUM)},
+ {"replaceable", UniValueType(UniValue::VBOOL)},
+ },
+ true, true);
+
+ if (options.exists("confTarget") && options.exists("totalFee")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction.");
+ } else if (options.exists("confTarget")) {
+ specifiedConfirmTarget = true;
+ newConfirmTarget = options["confTarget"].get_int();
+ if (newConfirmTarget <= 0) { // upper-bound will be checked by estimatefee/smartfee
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid confTarget (cannot be <= 0)");
+ }
+ } else if (options.exists("totalFee")) {
+ totalFee = options["totalFee"].get_int64();
+ CAmount requiredFee = CWallet::GetRequiredFee(maxNewTxSize);
+ if (totalFee < requiredFee ) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER,
+ strprintf("Insufficient totalFee (cannot be less than required fee %s)",
+ FormatMoney(requiredFee)));
+ }
+ }
+
+ if (options.exists("replaceable")) {
+ replaceable = options["replaceable"].get_bool();
+ }
+ }
+
+ // calculate the old fee and fee-rate
+ CAmount nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
+ CFeeRate nOldFeeRate(nOldFee, txSize);
+ CAmount nNewFee;
+ CFeeRate nNewFeeRate;
+ // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to
+ // future proof against changes to network wide policy for incremental relay
+ // fee that our node may not be aware of.
+ CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE);
+ if (::incrementalRelayFee > walletIncrementalRelayFee) {
+ walletIncrementalRelayFee = ::incrementalRelayFee;
+ }
+
+ if (totalFee > 0) {
+ CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize);
+ if (totalFee < minTotalFee) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
+ FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize))));
+ }
+ nNewFee = totalFee;
+ nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
+ } else {
+ // if user specified a confirm target then don't consider any global payTxFee
+ if (specifiedConfirmTarget) {
+ nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, CAmount(0));
+ }
+ // otherwise use the regular wallet logic to select payTxFee or default confirm target
+ else {
+ nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool);
+ }
+
+ nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
+
+ // New fee rate must be at least old rate + minimum incremental relay rate
+ // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
+ // in that unit (fee per kb).
+ // However, nOldFeeRate is a calculated value from the tx fee/size, so
+ // add 1 satoshi to the result, because it may have been rounded down.
+ if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
+ nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
+ nNewFee = nNewFeeRate.GetFee(maxNewTxSize);
+ }
+ }
+
+ // Check that in all cases the new fee doesn't violate maxTxFee
+ if (nNewFee > maxTxFee) {
+ throw JSONRPCError(RPC_MISC_ERROR,
+ strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)",
+ FormatMoney(nNewFee), FormatMoney(maxTxFee)));
+ }
+
+ // check that fee rate is higher than mempool's minimum fee
+ // (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
+ // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps,
+ // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
+ // moment earlier. In this case, we report an error to the user, who may use totalFee to make an adjustment.
+ CFeeRate minMempoolFeeRate = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
+ if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
+ throw JSONRPCError(RPC_MISC_ERROR, strprintf("New fee rate (%s) is less than the minimum fee rate (%s) to get into the mempool. totalFee value should to be at least %s or settxfee value should be at least %s to add transaction.", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), FormatMoney(minMempoolFeeRate.GetFeePerK())));
+ }
+
+ // Now modify the output to increase the fee.
+ // If the output is not large enough to pay the fee, fail.
+ CAmount nDelta = nNewFee - nOldFee;
+ assert(nDelta > 0);
+ CMutableTransaction tx(*(wtx.tx));
+ CTxOut* poutput = &(tx.vout[nOutput]);
+ if (poutput->nValue < nDelta) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Change output is too small to bump the fee");
+ }
+
+ // If the output would become dust, discard it (converting the dust to fee)
+ poutput->nValue -= nDelta;
+ if (poutput->nValue <= poutput->GetDustThreshold(::dustRelayFee)) {
+ LogPrint("rpc", "Bumping fee and discarding dust output\n");
+ nNewFee += poutput->nValue;
+ tx.vout.erase(tx.vout.begin() + nOutput);
+ }
+
+ // Mark new tx not replaceable, if requested.
+ if (!replaceable) {
+ for (auto& input : tx.vin) {
+ if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
+ }
+ }
+
+ // sign the new tx
+ CTransaction txNewConst(tx);
+ int nIn = 0;
+ for (auto& input : tx.vin) {
+ std::map<uint256, CWalletTx>::const_iterator mi = pwalletMain->mapWallet.find(input.prevout.hash);
+ assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
+ const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey;
+ const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue;
+ SignatureData sigdata;
+ if (!ProduceSignature(TransactionSignatureCreator(pwalletMain, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
+ }
+ UpdateTransaction(tx, nIn, sigdata);
+ nIn++;
+ }
+
+ // commit/broadcast the tx
+ CReserveKey reservekey(pwalletMain);
+ CWalletTx wtxBumped(pwalletMain, MakeTransactionRef(std::move(tx)));
+ wtxBumped.mapValue["replaces_txid"] = hash.ToString();
+ CValidationState state;
+ if (!pwalletMain->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
+ // NOTE: CommitTransaction never returns false, so this should never happen.
+ throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
+ }
+
+ UniValue vErrors(UniValue::VARR);
+ if (state.IsInvalid()) {
+ // This can happen if the mempool rejected the transaction. Report
+ // what happened in the "errors" response.
+ vErrors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
+ }
+
+ // mark the original tx as bumped
+ if (!pwalletMain->MarkReplaced(wtx.GetHash(), wtxBumped.GetHash())) {
+ // TODO: see if JSON-RPC has a standard way of returning a response
+ // along with an exception. It would be good to return information about
+ // wtxBumped to the caller even if marking the original transaction
+ // replaced does not succeed for some reason.
+ vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
+ }
+
+ UniValue result(UniValue::VOBJ);
+ result.push_back(Pair("txid", wtxBumped.GetHash().GetHex()));
+ result.push_back(Pair("origfee", ValueFromAmount(nOldFee)));
+ result.push_back(Pair("fee", ValueFromAmount(nNewFee)));
+ result.push_back(Pair("errors", vErrors));
+
+ return result;
+}
+
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
extern UniValue importprivkey(const JSONRPCRequest& request);
extern UniValue importaddress(const JSONRPCRequest& request);
@@ -2638,6 +2995,7 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, true, {"nrequired","keys","account"} },
{ "wallet", "addwitnessaddress", &addwitnessaddress, true, {"address"} },
{ "wallet", "backupwallet", &backupwallet, true, {"destination"} },
+ { "wallet", "bumpfee", &bumpfee, true, {"txid", "options"} },
{ "wallet", "dumpprivkey", &dumpprivkey, true, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, true, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, true, {"passphrase"} },
@@ -2666,7 +3024,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} },
{ "wallet", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} },
- { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses"} },
+ { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe"} },
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },