2
2
// Distributed under the MIT software license, see the accompanying
3
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
4
5
+ #include < consensus/validation.h>
5
6
#include < interfaces/chain.h>
6
7
#include < policy/fees.h>
7
8
#include < policy/policy.h>
19
20
namespace wallet {
20
21
// ! Check whether transaction has descendant in wallet or mempool, or has been
21
22
// ! mined, or conflicts with a mined transaction. Return a feebumper::Result.
22
- static feebumper::Result PreconditionChecks (const CWallet& wallet, const CWalletTx& wtx, std::vector<bilingual_str>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
23
+ static feebumper::Result PreconditionChecks (const CWallet& wallet, const CWalletTx& wtx, bool require_mine, std::vector<bilingual_str>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
23
24
{
24
25
if (wallet.HasWalletSpend (wtx.tx )) {
25
26
errors.push_back (Untranslated (" Transaction has descendants in the wallet" ));
@@ -48,20 +49,21 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet
48
49
return feebumper::Result::WALLET_ERROR;
49
50
}
50
51
51
- // check that original tx consists entirely of our inputs
52
- // 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)
53
- isminefilter filter = wallet.GetLegacyScriptPubKeyMan () && wallet.IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
54
- if (!AllInputsMine (wallet, *wtx.tx , filter)) {
55
- errors.push_back (Untranslated (" Transaction contains inputs that don't belong to this wallet" ));
56
- return feebumper::Result::WALLET_ERROR;
52
+ if (require_mine) {
53
+ // check that original tx consists entirely of our inputs
54
+ // 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)
55
+ isminefilter filter = wallet.GetLegacyScriptPubKeyMan () && wallet.IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
56
+ if (!AllInputsMine (wallet, *wtx.tx , filter)) {
57
+ errors.push_back (Untranslated (" Transaction contains inputs that don't belong to this wallet" ));
58
+ return feebumper::Result::WALLET_ERROR;
59
+ }
57
60
}
58
61
59
-
60
62
return feebumper::Result::OK;
61
63
}
62
64
63
65
// ! Check if the user provided a valid feeRate
64
- static feebumper::Result CheckFeeRate (const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, std::vector<bilingual_str>& errors)
66
+ static feebumper::Result CheckFeeRate (const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, CAmount old_fee, std::vector<bilingual_str>& errors)
65
67
{
66
68
// check that fee rate is higher than mempool's minimum fee
67
69
// (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
@@ -83,8 +85,6 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt
83
85
CFeeRate incrementalRelayFee = std::max (wallet.chain ().relayIncrementalFee (), CFeeRate (WALLET_INCREMENTAL_RELAY_FEE));
84
86
85
87
// Given old total fee and transaction size, calculate the old feeRate
86
- isminefilter filter = wallet.GetLegacyScriptPubKeyMan () && wallet.IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
87
- CAmount old_fee = CachedTxGetDebit (wallet, wtx, filter) - wtx.tx ->GetValueOut ();
88
88
const int64_t txSize = GetVirtualTransactionSize (*(wtx.tx ));
89
89
CFeeRate nOldFeeRate (old_fee, txSize);
90
90
// Min total fee is old fee + relay fee
@@ -150,12 +150,12 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid)
150
150
if (wtx == nullptr ) return false ;
151
151
152
152
std::vector<bilingual_str> errors_dummy;
153
- feebumper::Result res = PreconditionChecks (wallet, *wtx, errors_dummy);
153
+ feebumper::Result res = PreconditionChecks (wallet, *wtx, /* require_mine= */ true , errors_dummy);
154
154
return res == feebumper::Result::OK;
155
155
}
156
156
157
157
Result CreateRateBumpTransaction (CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors,
158
- CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx)
158
+ CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine )
159
159
{
160
160
// We are going to modify coin control later, copy to re-use
161
161
CCoinControl new_coin_control (coin_control);
@@ -169,13 +169,63 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
169
169
}
170
170
const CWalletTx& wtx = it->second ;
171
171
172
- Result result = PreconditionChecks (wallet, wtx, errors);
172
+ // Retrieve all of the UTXOs and add them to coin control
173
+ // While we're here, calculate the input amount
174
+ std::map<COutPoint, Coin> coins;
175
+ CAmount input_value = 0 ;
176
+ std::vector<CTxOut> spent_outputs;
177
+ for (const CTxIn& txin : wtx.tx ->vin ) {
178
+ coins[txin.prevout ]; // Create empty map entry keyed by prevout.
179
+ }
180
+ wallet.chain ().findCoins (coins);
181
+ for (const CTxIn& txin : wtx.tx ->vin ) {
182
+ const Coin& coin = coins.at (txin.prevout );
183
+ if (coin.out .IsNull ()) {
184
+ errors.push_back (Untranslated (strprintf (" %s:%u is already spent" , txin.prevout .hash .GetHex (), txin.prevout .n )));
185
+ return Result::MISC_ERROR;
186
+ }
187
+ if (wallet.IsMine (txin.prevout )) {
188
+ new_coin_control.Select (txin.prevout );
189
+ } else {
190
+ new_coin_control.SelectExternal (txin.prevout , coin.out );
191
+ }
192
+ input_value += coin.out .nValue ;
193
+ spent_outputs.push_back (coin.out );
194
+ }
195
+
196
+ // Figure out if we need to compute the input weight, and do so if necessary
197
+ PrecomputedTransactionData txdata;
198
+ txdata.Init (*wtx.tx , std::move (spent_outputs), /* force=*/ true );
199
+ for (unsigned int i = 0 ; i < wtx.tx ->vin .size (); ++i) {
200
+ const CTxIn& txin = wtx.tx ->vin .at (i);
201
+ const Coin& coin = coins.at (txin.prevout );
202
+
203
+ if (new_coin_control.IsExternalSelected (txin.prevout )) {
204
+ // For external inputs, we estimate the size using the size of this input
205
+ int64_t input_weight = GetTransactionInputWeight (txin);
206
+ // Because signatures can have different sizes, we need to figure out all of the
207
+ // signature sizes and replace them with the max sized signature.
208
+ // In order to do this, we verify the script with a special SignatureChecker which
209
+ // will observe the signatures verified and record their sizes.
210
+ SignatureWeights weights;
211
+ TransactionSignatureChecker tx_checker (wtx.tx .get (), i, coin.out .nValue , txdata, MissingDataBehavior::FAIL);
212
+ SignatureWeightChecker size_checker (weights, tx_checker);
213
+ VerifyScript (txin.scriptSig , coin.out .scriptPubKey , &txin.scriptWitness , STANDARD_SCRIPT_VERIFY_FLAGS, size_checker);
214
+ // Add the difference between max and current to input_weight so that it represents the largest the input could be
215
+ input_weight += weights.GetWeightDiffToMax ();
216
+ new_coin_control.SetInputWeight (txin.prevout , input_weight);
217
+ }
218
+ }
219
+
220
+ Result result = PreconditionChecks (wallet, wtx, require_mine, errors);
173
221
if (result != Result::OK) {
174
222
return result;
175
223
}
176
224
177
225
// Fill in recipients(and preserve a single change key if there is one)
226
+ // While we're here, calculate the output amount
178
227
std::vector<CRecipient> recipients;
228
+ CAmount output_value = 0 ;
179
229
for (const auto & output : wtx.tx ->vout ) {
180
230
if (!OutputIsChange (wallet, output)) {
181
231
CRecipient recipient = {output.scriptPubKey , output.nValue , false };
@@ -185,16 +235,22 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
185
235
ExtractDestination (output.scriptPubKey , change_dest);
186
236
new_coin_control.destChange = change_dest;
187
237
}
238
+ output_value += output.nValue ;
188
239
}
189
240
190
- isminefilter filter = wallet.GetLegacyScriptPubKeyMan () && wallet.IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
191
- old_fee = CachedTxGetDebit (wallet, wtx, filter) - wtx.tx ->GetValueOut ();
241
+ old_fee = input_value - output_value;
192
242
193
243
if (coin_control.m_feerate ) {
194
244
// The user provided a feeRate argument.
195
245
// We calculate this here to avoid compiler warning on the cs_wallet lock
196
- const int64_t maxTxSize{CalculateMaximumSignedTxSize (*wtx.tx , &wallet).vsize };
197
- Result res = CheckFeeRate (wallet, wtx, *new_coin_control.m_feerate , maxTxSize, errors);
246
+ // We need to make a temporary transaction with no input witnesses as the dummy signer expects them to be empty for external inputs
247
+ CMutableTransaction mtx{*wtx.tx };
248
+ for (auto & txin : mtx.vin ) {
249
+ txin.scriptSig .clear ();
250
+ txin.scriptWitness .SetNull ();
251
+ }
252
+ const int64_t maxTxSize{CalculateMaximumSignedTxSize (CTransaction (mtx), &wallet, &new_coin_control).vsize };
253
+ Result res = CheckFeeRate (wallet, wtx, *new_coin_control.m_feerate , maxTxSize, old_fee, errors);
198
254
if (res != Result::OK) {
199
255
return res;
200
256
}
@@ -254,7 +310,7 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti
254
310
const CWalletTx& oldWtx = it->second ;
255
311
256
312
// make sure the transaction still has no descendants and hasn't been mined in the meantime
257
- Result result = PreconditionChecks (wallet, oldWtx, errors);
313
+ Result result = PreconditionChecks (wallet, oldWtx, /* require_mine= */ false , errors);
258
314
if (result != Result::OK) {
259
315
return result;
260
316
}
0 commit comments