Refactor Bumpfee core functionality
[bitcoinplatinum.git] / src / wallet / feebumper.cpp
blob98b6da83c26538f9c7f38a80012d4f09bf6a5b36
1 // Copyright (c) 2017 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 #include "consensus/validation.h"
6 #include "wallet/feebumper.h"
7 #include "wallet/wallet.h"
8 #include "policy/policy.h"
9 #include "policy/rbf.h"
10 #include "validation.h" //for mempool access
11 #include "txmempool.h"
12 #include "utilmoneystr.h"
13 #include "util.h"
14 #include "net.h"
16 // Calculate the size of the transaction assuming all signatures are max size
17 // Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
18 // TODO: re-use this in CWallet::CreateTransaction (right now
19 // CreateTransaction uses the constructed dummy-signed tx to do a priority
20 // calculation, but we should be able to refactor after priority is removed).
21 // NOTE: this requires that all inputs must be in mapWallet (eg the tx should
22 // be IsAllFromMe).
23 int64_t CalculateMaximumSignedTxSize(const CWallet *pWallet, const CTransaction &tx)
25 CMutableTransaction txNew(tx);
26 std::vector<std::pair<const CWalletTx *, unsigned int>> vCoins;
27 // Look up the inputs. We should have already checked that this transaction
28 // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
29 // wallet, with a valid index into the vout array.
30 for (auto& input : tx.vin) {
31 const auto mi = pWallet->mapWallet.find(input.prevout.hash);
32 assert(mi != pWallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
33 vCoins.emplace_back(std::make_pair(&(mi->second), input.prevout.n));
35 if (!pWallet->DummySignTx(txNew, vCoins)) {
36 // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
37 // implies that we can sign for every input.
38 return -1;
40 return GetVirtualTransactionSize(txNew);
43 CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable)
45 txid(txidIn),
46 nOldFee(0),
47 nNewFee(0)
49 vErrors.clear();
50 bumpedTxid.SetNull();
51 AssertLockHeld(pWallet->cs_wallet);
52 if (!pWallet->mapWallet.count(txid)) {
53 vErrors.push_back("Invalid or non-wallet transaction id");
54 currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY;
55 return;
57 auto it = pWallet->mapWallet.find(txid);
58 const CWalletTx& wtx = it->second;
60 if (pWallet->HasWalletSpend(txid)) {
61 vErrors.push_back("Transaction has descendants in the wallet");
62 currentResult = BumpFeeResult::INVALID_PARAMETER;
63 return;
67 LOCK(mempool.cs);
68 auto it_mp = mempool.mapTx.find(txid);
69 if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) {
70 vErrors.push_back("Transaction has descendants in the mempool");
71 currentResult = BumpFeeResult::INVALID_PARAMETER;
72 return;
76 if (wtx.GetDepthInMainChain() != 0) {
77 vErrors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
78 currentResult = BumpFeeResult::WALLET_ERROR;
79 return;
82 if (!SignalsOptInRBF(wtx)) {
83 vErrors.push_back("Transaction is not BIP 125 replaceable");
84 currentResult = BumpFeeResult::WALLET_ERROR;
85 return;
88 if (wtx.mapValue.count("replaced_by_txid")) {
89 vErrors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", txid.ToString(), wtx.mapValue.at("replaced_by_txid")));
90 currentResult = BumpFeeResult::WALLET_ERROR;
91 return;
94 // check that original tx consists entirely of our inputs
95 // 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)
96 if (!pWallet->IsAllFromMe(wtx, ISMINE_SPENDABLE)) {
97 vErrors.push_back("Transaction contains inputs that don't belong to this wallet");
98 currentResult = BumpFeeResult::WALLET_ERROR;
99 return;
102 // figure out which output was change
103 // if there was no change output or multiple change outputs, fail
104 int nOutput = -1;
105 for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
106 if (pWallet->IsChange(wtx.tx->vout[i])) {
107 if (nOutput != -1) {
108 vErrors.push_back("Transaction has multiple change outputs");
109 currentResult = BumpFeeResult::WALLET_ERROR;
110 return;
112 nOutput = i;
115 if (nOutput == -1) {
116 vErrors.push_back("Transaction does not have a change output");
117 currentResult = BumpFeeResult::WALLET_ERROR;
118 return;
121 // Calculate the expected size of the new transaction.
122 int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
123 const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(pWallet, *wtx.tx);
124 if (maxNewTxSize < 0) {
125 vErrors.push_back("Transaction contains inputs that cannot be signed");
126 currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY;
127 return;
130 // calculate the old fee and fee-rate
131 nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
132 CFeeRate nOldFeeRate(nOldFee, txSize);
133 CFeeRate nNewFeeRate;
134 // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to
135 // future proof against changes to network wide policy for incremental relay
136 // fee that our node may not be aware of.
137 CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE);
138 if (::incrementalRelayFee > walletIncrementalRelayFee) {
139 walletIncrementalRelayFee = ::incrementalRelayFee;
142 if (totalFee > 0) {
143 CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize);
144 if (totalFee < minTotalFee) {
145 vErrors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
146 FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize))));
147 currentResult = BumpFeeResult::INVALID_PARAMETER;
148 return;
150 CAmount requiredFee = pWallet->GetRequiredFee(maxNewTxSize);
151 if (totalFee < requiredFee) {
152 vErrors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
153 FormatMoney(requiredFee)));
154 currentResult = BumpFeeResult::INVALID_PARAMETER;
155 return;
157 nNewFee = totalFee;
158 nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
159 } else {
160 // if user specified a confirm target then don't consider any global payTxFee
161 if (specifiedConfirmTarget) {
162 nNewFee = pWallet->GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, CAmount(0));
164 // otherwise use the regular wallet logic to select payTxFee or default confirm target
165 else {
166 nNewFee = pWallet->GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool);
169 nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
171 // New fee rate must be at least old rate + minimum incremental relay rate
172 // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
173 // in that unit (fee per kb).
174 // However, nOldFeeRate is a calculated value from the tx fee/size, so
175 // add 1 satoshi to the result, because it may have been rounded down.
176 if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
177 nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
178 nNewFee = nNewFeeRate.GetFee(maxNewTxSize);
182 // Check that in all cases the new fee doesn't violate maxTxFee
183 if (nNewFee > maxTxFee) {
184 vErrors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)",
185 FormatMoney(nNewFee), FormatMoney(maxTxFee)));
186 currentResult = BumpFeeResult::WALLET_ERROR;
187 return;
190 // check that fee rate is higher than mempool's minimum fee
191 // (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
192 // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps,
193 // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
194 // moment earlier. In this case, we report an error to the user, who may use totalFee to make an adjustment.
195 CFeeRate minMempoolFeeRate = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
196 if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
197 vErrors.push_back(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())));
198 currentResult = BumpFeeResult::WALLET_ERROR;
199 return;
202 // Now modify the output to increase the fee.
203 // If the output is not large enough to pay the fee, fail.
204 CAmount nDelta = nNewFee - nOldFee;
205 assert(nDelta > 0);
206 mtx = *wtx.tx;
207 CTxOut* poutput = &(mtx.vout[nOutput]);
208 if (poutput->nValue < nDelta) {
209 vErrors.push_back("Change output is too small to bump the fee");
210 currentResult = BumpFeeResult::WALLET_ERROR;
211 return;
214 // If the output would become dust, discard it (converting the dust to fee)
215 poutput->nValue -= nDelta;
216 if (poutput->nValue <= poutput->GetDustThreshold(::dustRelayFee)) {
217 LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n");
218 nNewFee += poutput->nValue;
219 mtx.vout.erase(mtx.vout.begin() + nOutput);
222 // Mark new tx not replaceable, if requested.
223 if (!newTxReplaceable) {
224 for (auto& input : mtx.vin) {
225 if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
229 currentResult = BumpFeeResult::OK;
232 bool CFeeBumper::commit(CWallet *pWallet)
234 AssertLockHeld(pWallet->cs_wallet);
235 vErrors.clear();
236 if (txid.IsNull() || !pWallet->mapWallet.count(txid)) {
237 vErrors.push_back("Invalid or non-wallet transaction id");
238 currentResult = BumpFeeResult::MISC_ERROR;
240 CWalletTx& oldWtx = pWallet->mapWallet[txid];
242 CWalletTx wtxBumped(pWallet, MakeTransactionRef(std::move(mtx)));
243 // commit/broadcast the tx
244 CReserveKey reservekey(pWallet);
245 wtxBumped.mapValue = oldWtx.mapValue;
246 wtxBumped.mapValue["replaces_txid"] = oldWtx.GetHash().ToString();
247 wtxBumped.vOrderForm = oldWtx.vOrderForm;
248 wtxBumped.strFromAccount = oldWtx.strFromAccount;
249 wtxBumped.fTimeReceivedIsTxTime = true;
250 wtxBumped.fFromMe = true;
251 CValidationState state;
252 if (!pWallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
253 // NOTE: CommitTransaction never returns false, so this should never happen.
254 vErrors.push_back(strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
255 return false;
258 bumpedTxid = wtxBumped.GetHash();
259 if (state.IsInvalid()) {
260 // This can happen if the mempool rejected the transaction. Report
261 // what happened in the "errors" response.
262 vErrors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
265 // mark the original tx as bumped
266 if (!pWallet->MarkReplaced(oldWtx.GetHash(), wtxBumped.GetHash())) {
267 // TODO: see if JSON-RPC has a standard way of returning a response
268 // along with an exception. It would be good to return information about
269 // wtxBumped to the caller even if marking the original transaction
270 // replaced does not succeed for some reason.
271 vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
273 return true;