Skip to content

Commit 7bfb770

Browse files
committed
Merge #9640: Bumpfee: bugfixes for error handling and feerate calculation
9522b53 rpc: bumpfee: handle errors more gracefully (Suhas Daftuar) f626594 rpc: bumpfee: use correct maximum signed tx size for fee calculation (Suhas Daftuar) d625b90 wallet: Refactor dummy signature signing for reusability (Suhas Daftuar)
2 parents e99f0d7 + 9522b53 commit 7bfb770

File tree

3 files changed

+77
-21
lines changed

3 files changed

+77
-21
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,6 +2664,33 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
26642664
return result;
26652665
}
26662666

2667+
// Calculate the size of the transaction assuming all signatures are max size
2668+
// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
2669+
// TODO: re-use this in CWallet::CreateTransaction (right now
2670+
// CreateTransaction uses the constructed dummy-signed tx to do a priority
2671+
// calculation, but we should be able to refactor after priority is removed).
2672+
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
2673+
// be IsAllFromMe).
2674+
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx)
2675+
{
2676+
CMutableTransaction txNew(tx);
2677+
std::vector<pair<CWalletTx *, unsigned int>> vCoins;
2678+
// Look up the inputs. We should have already checked that this transaction
2679+
// IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
2680+
// wallet, with a valid index into the vout array.
2681+
for (auto& input : tx.vin) {
2682+
const auto mi = pwalletMain->mapWallet.find(input.prevout.hash);
2683+
assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
2684+
vCoins.emplace_back(make_pair(&(mi->second), input.prevout.n));
2685+
}
2686+
if (!pwalletMain->DummySignTx(txNew, vCoins)) {
2687+
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
2688+
// implies that we can sign for every input.
2689+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that cannot be signed");
2690+
}
2691+
return GetVirtualTransactionSize(txNew);
2692+
}
2693+
26672694
UniValue bumpfee(const JSONRPCRequest& request)
26682695
{
26692696
if (!EnsureWalletIsAvailable(request.fHelp)) {
@@ -2706,6 +2733,7 @@ UniValue bumpfee(const JSONRPCRequest& request)
27062733
" \"txid\": \"value\", (string) The id of the new transaction\n"
27072734
" \"origfee\": n, (numeric) Fee of the replaced transaction\n"
27082735
" \"fee\": n, (numeric) Fee of the new transaction\n"
2736+
" \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n"
27092737
"}\n"
27102738
"\nExamples:\n"
27112739
"\nBump the fee, get the new transaction\'s txid\n" +
@@ -2769,9 +2797,9 @@ UniValue bumpfee(const JSONRPCRequest& request)
27692797
throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not have a change output");
27702798
}
27712799

2772-
// signature sizes can vary by a byte, so add 1 for each input when calculating the new fee
2800+
// Calculate the expected size of the new transaction.
27732801
int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
2774-
const int64_t maxNewTxSize = txSize + wtx.tx->vin.size();
2802+
const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx);
27752803

27762804
// optional parameters
27772805
bool specifiedConfirmTarget = false;
@@ -2845,8 +2873,12 @@ UniValue bumpfee(const JSONRPCRequest& request)
28452873
nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
28462874

28472875
// New fee rate must be at least old rate + minimum incremental relay rate
2848-
if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + walletIncrementalRelayFee.GetFeePerK()) {
2849-
nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + walletIncrementalRelayFee.GetFeePerK());
2876+
// walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
2877+
// in that unit (fee per kb).
2878+
// However, nOldFeeRate is a calculated value from the tx fee/size, so
2879+
// add 1 satoshi to the result, because it may have been rounded down.
2880+
if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
2881+
nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
28502882
nNewFee = nNewFeeRate.GetFee(maxNewTxSize);
28512883
}
28522884
}
@@ -2914,23 +2946,32 @@ UniValue bumpfee(const JSONRPCRequest& request)
29142946
CWalletTx wtxBumped(pwalletMain, MakeTransactionRef(std::move(tx)));
29152947
wtxBumped.mapValue["replaces_txid"] = hash.ToString();
29162948
CValidationState state;
2917-
if (!pwalletMain->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state) || !state.IsValid()) {
2949+
if (!pwalletMain->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
2950+
// NOTE: CommitTransaction never returns false, so this should never happen.
29182951
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
29192952
}
29202953

2954+
UniValue vErrors(UniValue::VARR);
2955+
if (state.IsInvalid()) {
2956+
// This can happen if the mempool rejected the transaction. Report
2957+
// what happened in the "errors" response.
2958+
vErrors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
2959+
}
2960+
29212961
// mark the original tx as bumped
29222962
if (!pwalletMain->MarkReplaced(wtx.GetHash(), wtxBumped.GetHash())) {
29232963
// TODO: see if JSON-RPC has a standard way of returning a response
29242964
// along with an exception. It would be good to return information about
29252965
// wtxBumped to the caller even if marking the original transaction
29262966
// replaced does not succeed for some reason.
2927-
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
2967+
vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
29282968
}
29292969

29302970
UniValue result(UniValue::VOBJ);
29312971
result.push_back(Pair("txid", wtxBumped.GetHash().GetHex()));
29322972
result.push_back(Pair("origfee", ValueFromAmount(nOldFee)));
29332973
result.push_back(Pair("fee", ValueFromAmount(nNewFee)));
2974+
result.push_back(Pair("errors", vErrors));
29342975

29352976
return result;
29362977
}

src/wallet/wallet.cpp

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2583,21 +2583,9 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
25832583
std::numeric_limits<unsigned int>::max() - (fWalletRbf ? 2 : 1)));
25842584

25852585
// Fill in dummy signatures for fee calculation.
2586-
int nIn = 0;
2587-
for (const auto& coin : setCoins)
2588-
{
2589-
const CScript& scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey;
2590-
SignatureData sigdata;
2591-
2592-
if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata))
2593-
{
2594-
strFailReason = _("Signing transaction failed");
2595-
return false;
2596-
} else {
2597-
UpdateTransaction(txNew, nIn, sigdata);
2598-
}
2599-
2600-
nIn++;
2586+
if (!DummySignTx(txNew, setCoins)) {
2587+
strFailReason = _("Signing transaction failed");
2588+
return false;
26012589
}
26022590

26032591
unsigned int nBytes = GetVirtualTransactionSize(txNew);

src/wallet/wallet.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "utilstrencodings.h"
1414
#include "validationinterface.h"
1515
#include "script/ismine.h"
16+
#include "script/sign.h"
1617
#include "wallet/crypter.h"
1718
#include "wallet/walletdb.h"
1819
#include "wallet/rpcwallet.h"
@@ -796,6 +797,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
796797
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries);
797798
bool AddAccountingEntry(const CAccountingEntry&);
798799
bool AddAccountingEntry(const CAccountingEntry&, CWalletDB *pwalletdb);
800+
template <typename ContainerType>
801+
bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins);
799802

800803
static CFeeRate minTxFee;
801804
static CFeeRate fallbackFee;
@@ -1028,4 +1031,28 @@ class CAccount
10281031
}
10291032
};
10301033

1034+
// Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes)
1035+
// ContainerType is meant to hold pair<CWalletTx *, int>, and be iterable
1036+
// so that each entry corresponds to each vIn, in order.
1037+
template <typename ContainerType>
1038+
bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins)
1039+
{
1040+
// Fill in dummy signatures for fee calculation.
1041+
int nIn = 0;
1042+
for (const auto& coin : coins)
1043+
{
1044+
const CScript& scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey;
1045+
SignatureData sigdata;
1046+
1047+
if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata))
1048+
{
1049+
return false;
1050+
} else {
1051+
UpdateTransaction(txNew, nIn, sigdata);
1052+
}
1053+
1054+
nIn++;
1055+
}
1056+
return true;
1057+
}
10311058
#endif // BITCOIN_WALLET_WALLET_H

0 commit comments

Comments
 (0)