LCOV - code coverage report
Current view: top level - src/wallet - feebumper.cpp (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 135 171 78.9 %
Date: 2021-06-29 14:35:33 Functions: 6 7 85.7 %
Legend: Modified by patch:
Lines: hit not hit | Branches: + taken - not taken # not executed

Not modified by patch:
Lines: hit not hit | Branches: + taken - not taken # not executed
Branches: 53 66 80.3 %

           Branch data     Line data    Source code
#       1                 :            : // Copyright (c) 2017-2020 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.
#       4                 :            : 
#       5                 :            : #include <interfaces/chain.h>
#       6                 :            : #include <policy/fees.h>
#       7                 :            : #include <policy/policy.h>
#       8                 :            : #include <util/moneystr.h>
#       9                 :            : #include <util/rbf.h>
#      10                 :            : #include <util/system.h>
#      11                 :            : #include <util/translation.h>
#      12                 :            : #include <wallet/coincontrol.h>
#      13                 :            : #include <wallet/feebumper.h>
#      14                 :            : #include <wallet/fees.h>
#      15                 :            : #include <wallet/wallet.h>
#      16                 :            : 
#      17                 :            : //! Check whether transaction has descendant in wallet or mempool, or has been
#      18                 :            : //! mined, or conflicts with a mined transaction. Return a feebumper::Result.
#      19                 :            : static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWalletTx& wtx, std::vector<bilingual_str>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
#      20                 :        316 : {
#      21         [ +  + ]:        316 :     if (wallet.HasWalletSpend(wtx.GetHash())) {
#      22                 :          2 :         errors.push_back(Untranslated("Transaction has descendants in the wallet"));
#      23                 :          2 :         return feebumper::Result::INVALID_PARAMETER;
#      24                 :          2 :     }
#      25                 :            : 
#      26                 :        314 :     {
#      27         [ -  + ]:        314 :         if (wallet.chain().hasDescendantsInMempool(wtx.GetHash())) {
#      28                 :          0 :             errors.push_back(Untranslated("Transaction has descendants in the mempool"));
#      29                 :          0 :             return feebumper::Result::INVALID_PARAMETER;
#      30                 :          0 :         }
#      31                 :        314 :     }
#      32                 :            : 
#      33         [ -  + ]:        314 :     if (wtx.GetDepthInMainChain() != 0) {
#      34                 :          0 :         errors.push_back(Untranslated("Transaction has been mined, or is conflicted with a mined transaction"));
#      35                 :          0 :         return feebumper::Result::WALLET_ERROR;
#      36                 :          0 :     }
#      37                 :            : 
#      38         [ +  + ]:        314 :     if (!SignalsOptInRBF(*wtx.tx)) {
#      39                 :          4 :         errors.push_back(Untranslated("Transaction is not BIP 125 replaceable"));
#      40                 :          4 :         return feebumper::Result::WALLET_ERROR;
#      41                 :          4 :     }
#      42                 :            : 
#      43         [ +  + ]:        310 :     if (wtx.mapValue.count("replaced_by_txid")) {
#      44                 :          2 :         errors.push_back(strprintf(Untranslated("Cannot bump transaction %s which was already bumped by transaction %s"), wtx.GetHash().ToString(), wtx.mapValue.at("replaced_by_txid")));
#      45                 :          2 :         return feebumper::Result::WALLET_ERROR;
#      46                 :          2 :     }
#      47                 :            : 
#      48                 :            :     // check that original tx consists entirely of our inputs
#      49                 :            :     // 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)
#      50 [ +  + ][ +  + ]:        308 :     isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
#      51         [ +  + ]:        308 :     if (!wallet.IsAllFromMe(*wtx.tx, filter)) {
#      52                 :          2 :         errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet"));
#      53                 :          2 :         return feebumper::Result::WALLET_ERROR;
#      54                 :          2 :     }
#      55                 :            : 
#      56                 :            : 
#      57                 :        306 :     return feebumper::Result::OK;
#      58                 :        306 : }
#      59                 :            : 
#      60                 :            : //! Check if the user provided a valid feeRate
#      61                 :            : static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, std::vector<bilingual_str>& errors)
#      62                 :         32 : {
#      63                 :            :     // check that fee rate is higher than mempool's minimum fee
#      64                 :            :     // (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
#      65                 :            :     // This may occur if the user set fee_rate or paytxfee too low, if fallbackfee is too low, or, perhaps,
#      66                 :            :     // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
#      67                 :            :     // moment earlier. In this case, we report an error to the user, who may adjust the fee.
#      68                 :         32 :     CFeeRate minMempoolFeeRate = wallet.chain().mempoolMinFee();
#      69                 :            : 
#      70         [ -  + ]:         32 :     if (newFeerate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
#      71                 :          0 :         errors.push_back(strprintf(
#      72                 :          0 :             Untranslated("New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- "),
#      73                 :          0 :             FormatMoney(newFeerate.GetFeePerK()),
#      74                 :          0 :             FormatMoney(minMempoolFeeRate.GetFeePerK())));
#      75                 :          0 :         return feebumper::Result::WALLET_ERROR;
#      76                 :          0 :     }
#      77                 :            : 
#      78                 :         32 :     CAmount new_total_fee = newFeerate.GetFee(maxTxSize);
#      79                 :            : 
#      80                 :         32 :     CFeeRate incrementalRelayFee = std::max(wallet.chain().relayIncrementalFee(), CFeeRate(WALLET_INCREMENTAL_RELAY_FEE));
#      81                 :            : 
#      82                 :            :     // Given old total fee and transaction size, calculate the old feeRate
#      83 [ +  + ][ +  + ]:         32 :     isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
#      84                 :         32 :     CAmount old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut();
#      85                 :         32 :     const int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
#      86                 :         32 :     CFeeRate nOldFeeRate(old_fee, txSize);
#      87                 :            :     // Min total fee is old fee + relay fee
#      88                 :         32 :     CAmount minTotalFee = nOldFeeRate.GetFee(maxTxSize) + incrementalRelayFee.GetFee(maxTxSize);
#      89                 :            : 
#      90         [ +  + ]:         32 :     if (new_total_fee < minTotalFee) {
#      91                 :         14 :         errors.push_back(strprintf(Untranslated("Insufficient total fee %s, must be at least %s (oldFee %s + incrementalFee %s)"),
#      92                 :         14 :             FormatMoney(new_total_fee), FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxTxSize)), FormatMoney(incrementalRelayFee.GetFee(maxTxSize))));
#      93                 :         14 :         return feebumper::Result::INVALID_PARAMETER;
#      94                 :         14 :     }
#      95                 :            : 
#      96                 :         18 :     CAmount requiredFee = GetRequiredFee(wallet, maxTxSize);
#      97         [ -  + ]:         18 :     if (new_total_fee < requiredFee) {
#      98                 :          0 :         errors.push_back(strprintf(Untranslated("Insufficient total fee (cannot be less than required fee %s)"),
#      99                 :          0 :             FormatMoney(requiredFee)));
#     100                 :          0 :         return feebumper::Result::INVALID_PARAMETER;
#     101                 :          0 :     }
#     102                 :            : 
#     103                 :            :     // Check that in all cases the new fee doesn't violate maxTxFee
#     104                 :         18 :     const CAmount max_tx_fee = wallet.m_default_max_tx_fee;
#     105         [ +  + ]:         18 :     if (new_total_fee > max_tx_fee) {
#     106                 :          2 :         errors.push_back(strprintf(Untranslated("Specified or calculated fee %s is too high (cannot be higher than -maxtxfee %s)"),
#     107                 :          2 :             FormatMoney(new_total_fee), FormatMoney(max_tx_fee)));
#     108                 :          2 :         return feebumper::Result::WALLET_ERROR;
#     109                 :          2 :     }
#     110                 :            : 
#     111                 :         16 :     return feebumper::Result::OK;
#     112                 :         16 : }
#     113                 :            : 
#     114                 :            : static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CAmount old_fee, const CCoinControl& coin_control)
#     115                 :        134 : {
#     116                 :            :     // Get the fee rate of the original transaction. This is calculated from
#     117                 :            :     // the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the
#     118                 :            :     // result.
#     119                 :        134 :     int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
#     120                 :        134 :     CFeeRate feerate(old_fee, txSize);
#     121                 :        134 :     feerate += CFeeRate(1);
#     122                 :            : 
#     123                 :            :     // The node has a configurable incremental relay fee. Increment the fee by
#     124                 :            :     // the minimum of that and the wallet's conservative
#     125                 :            :     // WALLET_INCREMENTAL_RELAY_FEE value to future proof against changes to
#     126                 :            :     // network wide policy for incremental relay fee that our node may not be
#     127                 :            :     // aware of. This ensures we're over the required relay fee rate
#     128                 :            :     // (BIP 125 rule 4).  The replacement tx will be at least as large as the
#     129                 :            :     // original tx, so the total fee will be greater (BIP 125 rule 3)
#     130                 :        134 :     CFeeRate node_incremental_relay_fee = wallet.chain().relayIncrementalFee();
#     131                 :        134 :     CFeeRate wallet_incremental_relay_fee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE);
#     132                 :        134 :     feerate += std::max(node_incremental_relay_fee, wallet_incremental_relay_fee);
#     133                 :            : 
#     134                 :            :     // Fee rate must also be at least the wallet's GetMinimumFeeRate
#     135                 :        134 :     CFeeRate min_feerate(GetMinimumFeeRate(wallet, coin_control, /* feeCalc */ nullptr));
#     136                 :            : 
#     137                 :            :     // Set the required fee rate for the replacement transaction in coin control.
#     138                 :        134 :     return std::max(feerate, min_feerate);
#     139                 :        134 : }
#     140                 :            : 
#     141                 :            : namespace feebumper {
#     142                 :            : 
#     143                 :            : bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid)
#     144                 :          0 : {
#     145                 :          0 :     LOCK(wallet.cs_wallet);
#     146                 :          0 :     const CWalletTx* wtx = wallet.GetWalletTx(txid);
#     147         [ #  # ]:          0 :     if (wtx == nullptr) return false;
#     148                 :            : 
#     149                 :          0 :     std::vector<bilingual_str> errors_dummy;
#     150                 :          0 :     feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy);
#     151                 :          0 :     return res == feebumper::Result::OK;
#     152                 :          0 : }
#     153                 :            : 
#     154                 :            : Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors,
#     155                 :            :                                  CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx)
#     156                 :        176 : {
#     157                 :            :     // We are going to modify coin control later, copy to re-use
#     158                 :        176 :     CCoinControl new_coin_control(coin_control);
#     159                 :            : 
#     160                 :        176 :     LOCK(wallet.cs_wallet);
#     161                 :        176 :     errors.clear();
#     162                 :        176 :     auto it = wallet.mapWallet.find(txid);
#     163         [ -  + ]:        176 :     if (it == wallet.mapWallet.end()) {
#     164                 :          0 :         errors.push_back(Untranslated("Invalid or non-wallet transaction id"));
#     165                 :          0 :         return Result::INVALID_ADDRESS_OR_KEY;
#     166                 :          0 :     }
#     167                 :        176 :     const CWalletTx& wtx = it->second;
#     168                 :            : 
#     169                 :        176 :     Result result = PreconditionChecks(wallet, wtx, errors);
#     170         [ +  + ]:        176 :     if (result != Result::OK) {
#     171                 :         10 :         return result;
#     172                 :         10 :     }
#     173                 :            : 
#     174                 :            :     // Fill in recipients(and preserve a single change key if there is one)
#     175                 :        166 :     std::vector<CRecipient> recipients;
#     176         [ +  + ]:        328 :     for (const auto& output : wtx.tx->vout) {
#     177         [ +  + ]:        328 :         if (!wallet.IsChange(output)) {
#     178                 :        166 :             CRecipient recipient = {output.scriptPubKey, output.nValue, false};
#     179                 :        166 :             recipients.push_back(recipient);
#     180                 :        166 :         } else {
#     181                 :        162 :             CTxDestination change_dest;
#     182                 :        162 :             ExtractDestination(output.scriptPubKey, change_dest);
#     183                 :        162 :             new_coin_control.destChange = change_dest;
#     184                 :        162 :         }
#     185                 :        328 :     }
#     186                 :            : 
#     187 [ +  + ][ +  + ]:        166 :     isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
#     188                 :        166 :     old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut();
#     189                 :            : 
#     190         [ +  + ]:        166 :     if (coin_control.m_feerate) {
#     191                 :            :         // The user provided a feeRate argument.
#     192                 :            :         // We calculate this here to avoid compiler warning on the cs_wallet lock
#     193                 :         32 :         const int64_t maxTxSize{CalculateMaximumSignedTxSize(*wtx.tx, &wallet).vsize};
#     194                 :         32 :         Result res = CheckFeeRate(wallet, wtx, *new_coin_control.m_feerate, maxTxSize, errors);
#     195         [ +  + ]:         32 :         if (res != Result::OK) {
#     196                 :         16 :             return res;
#     197                 :         16 :         }
#     198                 :        134 :     } else {
#     199                 :            :         // The user did not provide a feeRate argument
#     200                 :        134 :         new_coin_control.m_feerate = EstimateFeeRate(wallet, wtx, old_fee, new_coin_control);
#     201                 :        134 :     }
#     202                 :            : 
#     203                 :            :     // Fill in required inputs we are double-spending(all of them)
#     204                 :            :     // N.B.: bip125 doesn't require all the inputs in the replaced transaction to be
#     205                 :            :     // used in the replacement transaction, but it's very important for wallets to make
#     206                 :            :     // sure that happens. If not, it would be possible to bump a transaction A twice to
#     207                 :            :     // A2 and A3 where A2 and A3 don't conflict (or alternatively bump A to A2 and A2
#     208                 :            :     // to A3 where A and A3 don't conflict). If both later get confirmed then the sender
#     209                 :            :     // has accidentally double paid.
#     210         [ +  + ]:        438 :     for (const auto& inputs : wtx.tx->vin) {
#     211                 :        438 :         new_coin_control.Select(COutPoint(inputs.prevout));
#     212                 :        438 :     }
#     213                 :        150 :     new_coin_control.fAllowOtherInputs = true;
#     214                 :            : 
#     215                 :            :     // We cannot source new unconfirmed inputs(bip125 rule 2)
#     216                 :        150 :     new_coin_control.m_min_depth = 1;
#     217                 :            : 
#     218                 :        150 :     CTransactionRef tx_new;
#     219                 :        150 :     CAmount fee_ret;
#     220                 :        150 :     int change_pos_in_out = -1; // No requested location for change
#     221                 :        150 :     bilingual_str fail_reason;
#     222                 :        150 :     FeeCalculation fee_calc_out;
#     223         [ +  + ]:        150 :     if (!wallet.CreateTransaction(recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) {
#     224                 :          4 :         errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
#     225                 :          4 :         return Result::WALLET_ERROR;
#     226                 :          4 :     }
#     227                 :            : 
#     228                 :            :     // Write back new fee if successful
#     229                 :        146 :     new_fee = fee_ret;
#     230                 :            : 
#     231                 :            :     // Write back transaction
#     232                 :        146 :     mtx = CMutableTransaction(*tx_new);
#     233                 :            :     // Mark new tx not replaceable, if requested.
#     234         [ +  + ]:        146 :     if (!coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf)) {
#     235         [ +  + ]:          2 :         for (auto& input : mtx.vin) {
#     236         [ -  + ]:          2 :             if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
#     237                 :          2 :         }
#     238                 :          2 :     }
#     239                 :            : 
#     240                 :        146 :     return Result::OK;
#     241                 :        146 : }
#     242                 :            : 
#     243                 :        140 : bool SignTransaction(CWallet& wallet, CMutableTransaction& mtx) {
#     244                 :        140 :     LOCK(wallet.cs_wallet);
#     245                 :        140 :     return wallet.SignTransaction(mtx);
#     246                 :        140 : }
#     247                 :            : 
#     248                 :            : Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<bilingual_str>& errors, uint256& bumped_txid)
#     249                 :        140 : {
#     250                 :        140 :     LOCK(wallet.cs_wallet);
#     251         [ -  + ]:        140 :     if (!errors.empty()) {
#     252                 :          0 :         return Result::MISC_ERROR;
#     253                 :          0 :     }
#     254         [ -  + ]:        140 :     auto it = txid.IsNull() ? wallet.mapWallet.end() : wallet.mapWallet.find(txid);
#     255         [ -  + ]:        140 :     if (it == wallet.mapWallet.end()) {
#     256                 :          0 :         errors.push_back(Untranslated("Invalid or non-wallet transaction id"));
#     257                 :          0 :         return Result::MISC_ERROR;
#     258                 :          0 :     }
#     259                 :        140 :     const CWalletTx& oldWtx = it->second;
#     260                 :            : 
#     261                 :            :     // make sure the transaction still has no descendants and hasn't been mined in the meantime
#     262                 :        140 :     Result result = PreconditionChecks(wallet, oldWtx, errors);
#     263         [ -  + ]:        140 :     if (result != Result::OK) {
#     264                 :          0 :         return result;
#     265                 :          0 :     }
#     266                 :            : 
#     267                 :            :     // commit/broadcast the tx
#     268                 :        140 :     CTransactionRef tx = MakeTransactionRef(std::move(mtx));
#     269                 :        140 :     mapValue_t mapValue = oldWtx.mapValue;
#     270                 :        140 :     mapValue["replaces_txid"] = oldWtx.GetHash().ToString();
#     271                 :            : 
#     272                 :        140 :     wallet.CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm);
#     273                 :            : 
#     274                 :            :     // mark the original tx as bumped
#     275                 :        140 :     bumped_txid = tx->GetHash();
#     276         [ -  + ]:        140 :     if (!wallet.MarkReplaced(oldWtx.GetHash(), bumped_txid)) {
#     277                 :            :         // TODO: see if JSON-RPC has a standard way of returning a response
#     278                 :            :         // along with an exception. It would be good to return information about
#     279                 :            :         // wtxBumped to the caller even if marking the original transaction
#     280                 :            :         // replaced does not succeed for some reason.
#     281                 :          0 :         errors.push_back(Untranslated("Created new bumpfee transaction but could not mark the original transaction as replaced"));
#     282                 :          0 :     }
#     283                 :        140 :     return Result::OK;
#     284                 :        140 : }
#     285                 :            : 
#     286                 :            : } // namespace feebumper

Generated by: LCOV version 1.14