aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/bench/checkblock.cpp4
-rw-r--r--src/clientversion.h26
-rw-r--r--src/net_processing.cpp6
-rw-r--r--src/qt/forms/optionsdialog.ui102
-rw-r--r--src/qt/guiutil.cpp31
-rw-r--r--src/qt/guiutil.h3
-rw-r--r--src/qt/optionsdialog.cpp12
-rw-r--r--src/qt/optionsdialog.h1
-rw-r--r--src/qt/test/test_main.cpp8
-rw-r--r--src/qt/test/wallettests.cpp12
-rw-r--r--src/rpc/server.cpp4
-rw-r--r--src/validation.cpp10
-rw-r--r--src/wallet/feebumper.cpp283
-rw-r--r--src/wallet/feebumper.h56
-rw-r--r--src/wallet/rpcwallet.cpp262
-rw-r--r--src/wallet/wallet.cpp28
-rw-r--r--src/wallet/wallet.h5
18 files changed, 556 insertions, 299 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 8680546b99..69b5bb48ad 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -157,6 +157,7 @@ BITCOIN_CORE_H = \
wallet/coincontrol.h \
wallet/crypter.h \
wallet/db.h \
+ wallet/feebumper.h \
wallet/rpcwallet.h \
wallet/wallet.h \
wallet/walletdb.h \
@@ -231,6 +232,7 @@ libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_wallet_a_SOURCES = \
wallet/crypter.cpp \
wallet/db.cpp \
+ wallet/feebumper.cpp \
wallet/rpcdump.cpp \
wallet/rpcwallet.cpp \
wallet/wallet.cpp \
diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp
index 230e4ca773..c6c932454a 100644
--- a/src/bench/checkblock.cpp
+++ b/src/bench/checkblock.cpp
@@ -22,7 +22,7 @@ static void DeserializeBlockTest(benchmark::State& state)
CDataStream stream((const char*)block_bench::block413567,
(const char*)&block_bench::block413567[sizeof(block_bench::block413567)],
SER_NETWORK, PROTOCOL_VERSION);
- char a;
+ char a = '\0';
stream.write(&a, 1); // Prevent compaction
while (state.KeepRunning()) {
@@ -37,7 +37,7 @@ static void DeserializeAndCheckBlockTest(benchmark::State& state)
CDataStream stream((const char*)block_bench::block413567,
(const char*)&block_bench::block413567[sizeof(block_bench::block413567)],
SER_NETWORK, PROTOCOL_VERSION);
- char a;
+ char a = '\0';
stream.write(&a, 1); // Prevent compaction
Consensus::Params params = Params(CBaseChainParams::MAIN).GetConsensus();
diff --git a/src/clientversion.h b/src/clientversion.h
index 69154d546d..3d5392619b 100644
--- a/src/clientversion.h
+++ b/src/clientversion.h
@@ -7,29 +7,13 @@
#if defined(HAVE_CONFIG_H)
#include "config/bitcoin-config.h"
-#else
-
-/**
- * client versioning and copyright year
- */
-
-//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
-#define CLIENT_VERSION_MAJOR 0
-#define CLIENT_VERSION_MINOR 14
-#define CLIENT_VERSION_REVISION 99
-#define CLIENT_VERSION_BUILD 0
-
-//! Set to true for release, false for prerelease or test build
-#define CLIENT_VERSION_IS_RELEASE false
-
-/**
- * Copyright year (2009-this)
- * Todo: update this when changing our copyright comments in the source
- */
-#define COPYRIGHT_YEAR 2017
-
#endif //HAVE_CONFIG_H
+// Check that required client information is defined
+#if !defined(CLIENT_VERSION_MAJOR) || !defined(CLIENT_VERSION_MINOR) || !defined(CLIENT_VERSION_REVISION) || !defined(CLIENT_VERSION_BUILD) || !defined(CLIENT_VERSION_IS_RELEASE) || !defined(COPYRIGHT_YEAR)
+#error Client version information missing: version is not defined by bitcoin-config.h or in any other way
+#endif
+
/**
* Converts the parameter X to a string after macro replacement on X has been performed.
* Don't merge these into one macro!
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index f838250379..d881b8ddda 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -858,8 +858,8 @@ void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationSta
int nDoS = 0;
if (state.IsInvalid(nDoS)) {
- if (it != mapBlockSource.end() && State(it->second.first)) {
- assert (state.GetRejectCode() < REJECT_INTERNAL); // Blocks are never rejected with internal reject codes
+ // Don't send reject message with code 0 or an internal reject code.
+ if (it != mapBlockSource.end() && State(it->second.first) && state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) {
CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), hash};
State(it->second.first)->rejects.push_back(reject);
if (nDoS > 0 && it->second.second)
@@ -1947,7 +1947,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(),
pfrom->id,
FormatStateMessage(state));
- if (state.GetRejectCode() < REJECT_INTERNAL) // Never send AcceptToMemoryPool's internal codes over P2P
+ if (state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) // Never send AcceptToMemoryPool's internal codes over P2P
connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, (unsigned char)state.GetRejectCode(),
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash));
if (nDoS > 0) {
diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui
index 0b29201872..0f1b3f4a73 100644
--- a/src/qt/forms/optionsdialog.ui
+++ b/src/qt/forms/optionsdialog.ui
@@ -692,17 +692,34 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_Buttons">
<item>
- <widget class="QPushButton" name="resetButton">
- <property name="toolTip">
- <string>Reset all client options to default.</string>
- </property>
- <property name="text">
- <string>&amp;Reset Options</string>
- </property>
- <property name="autoDefault">
- <bool>false</bool>
- </property>
- </widget>
+ <layout class="QVBoxLayout" name="verticalLayout_Buttons">
+ <item>
+ <widget class="QPushButton" name="openBitcoinConfButton">
+ <property name="toolTip">
+ <string>Open the %1 configuration file from the working directory.</string>
+ </property>
+ <property name="text">
+ <string>Open Configuration File</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="resetButton">
+ <property name="toolTip">
+ <string>Reset all client options to default.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Reset Options</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item>
<spacer name="horizontalSpacer_1">
@@ -756,27 +773,48 @@
</spacer>
</item>
<item>
- <widget class="QPushButton" name="okButton">
- <property name="text">
- <string>&amp;OK</string>
- </property>
- <property name="autoDefault">
- <bool>false</bool>
- </property>
- <property name="default">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="cancelButton">
- <property name="text">
- <string>&amp;Cancel</string>
- </property>
- <property name="autoDefault">
- <bool>false</bool>
- </property>
- </widget>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="okButton">
+ <property name="text">
+ <string>&amp;OK</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="cancelButton">
+ <property name="text">
+ <string>&amp;Cancel</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
</item>
</layout>
</item>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index a2699d374a..3f3f9b9ccb 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -415,6 +415,22 @@ void openDebugLogfile()
QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathDebug)));
}
+bool openBitcoinConf()
+{
+ boost::filesystem::path pathConfig = GetConfigFile(BITCOIN_CONF_FILENAME);
+
+ /* Create the file */
+ boost::filesystem::ofstream configFile(pathConfig, std::ios_base::app);
+
+ if (!configFile.good())
+ return false;
+
+ configFile.close();
+
+ /* Open bitcoin.conf with the associated application */
+ return QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathConfig)));
+}
+
void SubstituteFonts(const QString& language)
{
#if defined(Q_OS_MAC)
@@ -839,14 +855,17 @@ void restoreWindowGeometry(const QString& strSetting, const QSize& defaultSize,
QPoint pos = settings.value(strSetting + "Pos").toPoint();
QSize size = settings.value(strSetting + "Size", defaultSize).toSize();
- if (!pos.x() && !pos.y()) {
- QRect screen = QApplication::desktop()->screenGeometry();
- pos.setX((screen.width() - size.width()) / 2);
- pos.setY((screen.height() - size.height()) / 2);
- }
-
parent->resize(size);
parent->move(pos);
+
+ if ((!pos.x() && !pos.y()) || (QApplication::desktop()->screenNumber(parent) == -1))
+ {
+ QRect screen = QApplication::desktop()->screenGeometry();
+ QPoint defaultPos((screen.width() - defaultSize.width()) / 2,
+ (screen.height() - defaultSize.height()) / 2);
+ parent->resize(defaultSize);
+ parent->move(defaultPos);
+ }
}
void setClipboard(const QString& str)
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index f956770156..d6aa8c4ea6 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -113,6 +113,9 @@ namespace GUIUtil
// Open debug.log
void openDebugLogfile();
+ // Open the config file
+ bool openBitcoinConf();
+
// Replace invalid default fonts with known good ones
void SubstituteFonts(const QString& language);
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 7ff00b1e9e..efb25aaf18 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -232,6 +232,18 @@ void OptionsDialog::on_resetButton_clicked()
}
}
+void OptionsDialog::on_openBitcoinConfButton_clicked()
+{
+ /* explain the purpose of the config file */
+ QMessageBox::information(this, tr("Configuration options"),
+ tr("The configuration file is used to specify advanced user options which override GUI settings. "
+ "Additionally, any command-line options will override this configuration file."));
+
+ /* show an error if there was some problem opening the file */
+ if (!GUIUtil::openBitcoinConf())
+ QMessageBox::critical(this, tr("Error"), tr("The configuration file could not be opened."));
+}
+
void OptionsDialog::on_okButton_clicked()
{
mapper->submit();
diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h
index d98c1dc193..f9f5823c05 100644
--- a/src/qt/optionsdialog.h
+++ b/src/qt/optionsdialog.h
@@ -47,6 +47,7 @@ private Q_SLOTS:
/* set OK button state (enabled / disabled) */
void setOkButtonState(bool fState);
void on_resetButton_clicked();
+ void on_openBitcoinConfButton_clicked();
void on_okButton_clicked();
void on_cancelButton_clicked();
diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp
index d8bcfedb7c..cae18f41a5 100644
--- a/src/qt/test/test_main.cpp
+++ b/src/qt/test/test_main.cpp
@@ -31,6 +31,9 @@ Q_IMPORT_PLUGIN(qjpcodecs)
Q_IMPORT_PLUGIN(qtwcodecs)
Q_IMPORT_PLUGIN(qkrcodecs)
#else
+#if defined(QT_QPA_PLATFORM_MINIMAL)
+Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin);
+#endif
#if defined(QT_QPA_PLATFORM_XCB)
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin);
#elif defined(QT_QPA_PLATFORM_WINDOWS)
@@ -53,6 +56,11 @@ int main(int argc, char *argv[])
bool fInvalid = false;
+ // Prefer the "minimal" platform for the test instead of the normal default
+ // platform ("xcb", "windows", or "cocoa") so tests can't unintentially
+ // interfere with any background GUIs and don't require extra resources.
+ setenv("QT_QPA_PLATFORM", "minimal", 0);
+
// Don't remove this, it's needed to access
// QApplication:: and QCoreApplication:: in the tests
QApplication app(argc, argv);
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index b7c1e4c4d5..f794b6b382 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -68,6 +68,18 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
}
//! Simple qt wallet tests.
+//
+// Test widgets can be debugged interactively calling show() on them and
+// manually running the event loop, e.g.:
+//
+// sendCoinsDialog.show();
+// QEventLoop().exec();
+//
+// This also requires overriding the default minimal Qt platform:
+//
+// src/qt/test/test_bitcoin-qt -platform xcb # Linux
+// src/qt/test/test_bitcoin-qt -platform windows # Windows
+// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
void WalletTests::walletTests()
{
// Set up wallet and chain with 101 blocks (1 mature block for spending).
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 4032c5163c..5c493428d8 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -369,9 +369,7 @@ void JSONRPCRequest::parse(const UniValue& valRequest)
if (!valMethod.isStr())
throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string");
strMethod = valMethod.get_str();
- if (strMethod != "getblocktemplate") {
- LogPrint(BCLog::RPC, "ThreadRPCServer method=%s\n", SanitizeString(strMethod));
- }
+ LogPrint(BCLog::RPC, "ThreadRPCServer method=%s\n", SanitizeString(strMethod));
// Parse params
UniValue valParams = find_value(request, "params");
diff --git a/src/validation.cpp b/src/validation.cpp
index c32cdf9dfc..99ce53986f 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1881,7 +1881,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
REJECT_INVALID, "bad-cb-amount");
if (!control.Wait())
- return state.DoS(100, false);
+ return state.DoS(100, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed");
int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2;
LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs]\n", nInputs - 1, 0.001 * (nTime4 - nTime2), nInputs <= 1 ? 0 : 0.001 * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * 0.000001);
@@ -2890,10 +2890,12 @@ static bool CheckIndexAgainstCheckpoint(const CBlockIndex* pindexPrev, CValidati
return true;
int nHeight = pindexPrev->nHeight+1;
- // Don't accept any forks from the main chain prior to last checkpoint
+ // Don't accept any forks from the main chain prior to last checkpoint.
+ // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our
+ // MapBlockIndex.
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(chainparams.Checkpoints());
if (pcheckpoint && nHeight < pcheckpoint->nHeight)
- return state.DoS(100, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight));
+ return state.DoS(100, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint");
return true;
}
@@ -3094,7 +3096,7 @@ static bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state
CBlockIndex* pindexPrev = NULL;
BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock);
if (mi == mapBlockIndex.end())
- return state.DoS(10, error("%s: prev block not found", __func__), 0, "bad-prevblk");
+ return state.DoS(10, error("%s: prev block not found", __func__), 0, "prev-blk-not-found");
pindexPrev = (*mi).second;
if (pindexPrev->nStatus & BLOCK_FAILED_MASK)
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
new file mode 100644
index 0000000000..fe3871a91d
--- /dev/null
+++ b/src/wallet/feebumper.cpp
@@ -0,0 +1,283 @@
+// Copyright (c) 2017 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "consensus/validation.h"
+#include "wallet/feebumper.h"
+#include "wallet/wallet.h"
+#include "policy/policy.h"
+#include "policy/rbf.h"
+#include "validation.h" //for mempool access
+#include "txmempool.h"
+#include "utilmoneystr.h"
+#include "util.h"
+#include "net.h"
+
+// 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, const CWallet *pWallet)
+{
+ CMutableTransaction txNew(tx);
+ std::vector<std::pair<const 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 = pWallet->mapWallet.find(input.prevout.hash);
+ assert(mi != pWallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
+ vCoins.emplace_back(&(mi->second), input.prevout.n);
+ }
+ if (!pWallet->DummySignTx(txNew, vCoins)) {
+ // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
+ // implies that we can sign for every input.
+ return -1;
+ }
+ return GetVirtualTransactionSize(txNew);
+}
+
+CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable)
+ :
+ txid(std::move(txidIn)),
+ nOldFee(0),
+ nNewFee(0)
+{
+ vErrors.clear();
+ bumpedTxid.SetNull();
+ AssertLockHeld(pWallet->cs_wallet);
+ if (!pWallet->mapWallet.count(txid)) {
+ vErrors.push_back("Invalid or non-wallet transaction id");
+ currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY;
+ return;
+ }
+ auto it = pWallet->mapWallet.find(txid);
+ const CWalletTx& wtx = it->second;
+
+ if (pWallet->HasWalletSpend(txid)) {
+ vErrors.push_back("Transaction has descendants in the wallet");
+ currentResult = BumpFeeResult::INVALID_PARAMETER;
+ return;
+ }
+
+ {
+ LOCK(mempool.cs);
+ auto it_mp = mempool.mapTx.find(txid);
+ if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) {
+ vErrors.push_back("Transaction has descendants in the mempool");
+ currentResult = BumpFeeResult::INVALID_PARAMETER;
+ return;
+ }
+ }
+
+ if (wtx.GetDepthInMainChain() != 0) {
+ vErrors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ if (!SignalsOptInRBF(wtx)) {
+ vErrors.push_back("Transaction is not BIP 125 replaceable");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ if (wtx.mapValue.count("replaced_by_txid")) {
+ vErrors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", txid.ToString(), wtx.mapValue.at("replaced_by_txid")));
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // 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 (!pWallet->IsAllFromMe(wtx, ISMINE_SPENDABLE)) {
+ vErrors.push_back("Transaction contains inputs that don't belong to this wallet");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // 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 (pWallet->IsChange(wtx.tx->vout[i])) {
+ if (nOutput != -1) {
+ vErrors.push_back("Transaction has multiple change outputs");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+ nOutput = i;
+ }
+ }
+ if (nOutput == -1) {
+ vErrors.push_back("Transaction does not have a change output");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // Calculate the expected size of the new transaction.
+ int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
+ const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, pWallet);
+ if (maxNewTxSize < 0) {
+ vErrors.push_back("Transaction contains inputs that cannot be signed");
+ currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY;
+ return;
+ }
+
+ // calculate the old fee and fee-rate
+ nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
+ CFeeRate nOldFeeRate(nOldFee, txSize);
+ 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) {
+ vErrors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
+ FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize))));
+ currentResult = BumpFeeResult::INVALID_PARAMETER;
+ return;
+ }
+ CAmount requiredFee = CWallet::GetRequiredFee(maxNewTxSize);
+ if (totalFee < requiredFee) {
+ vErrors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
+ FormatMoney(requiredFee)));
+ currentResult = BumpFeeResult::INVALID_PARAMETER;
+ return;
+ }
+ 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) {
+ vErrors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)",
+ FormatMoney(nNewFee), FormatMoney(maxTxFee)));
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // 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()) {
+ 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())));
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // 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);
+ mtx = *wtx.tx;
+ CTxOut* poutput = &(mtx.vout[nOutput]);
+ if (poutput->nValue < nDelta) {
+ vErrors.push_back("Change output is too small to bump the fee");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // If the output would become dust, discard it (converting the dust to fee)
+ poutput->nValue -= nDelta;
+ if (poutput->nValue <= poutput->GetDustThreshold(::dustRelayFee)) {
+ LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n");
+ nNewFee += poutput->nValue;
+ mtx.vout.erase(mtx.vout.begin() + nOutput);
+ }
+
+ // Mark new tx not replaceable, if requested.
+ if (!newTxReplaceable) {
+ for (auto& input : mtx.vin) {
+ if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
+ }
+ }
+
+ currentResult = BumpFeeResult::OK;
+}
+
+bool CFeeBumper::signTransaction(CWallet *pWallet)
+{
+ return pWallet->SignTransaction(mtx);
+}
+
+bool CFeeBumper::commit(CWallet *pWallet)
+{
+ AssertLockHeld(pWallet->cs_wallet);
+ if (!vErrors.empty() || currentResult != BumpFeeResult::OK) {
+ return false;
+ }
+ if (txid.IsNull() || !pWallet->mapWallet.count(txid)) {
+ vErrors.push_back("Invalid or non-wallet transaction id");
+ currentResult = BumpFeeResult::MISC_ERROR;
+ return false;
+ }
+ CWalletTx& oldWtx = pWallet->mapWallet[txid];
+
+ CWalletTx wtxBumped(pWallet, MakeTransactionRef(std::move(mtx)));
+ // commit/broadcast the tx
+ CReserveKey reservekey(pWallet);
+ wtxBumped.mapValue = oldWtx.mapValue;
+ wtxBumped.mapValue["replaces_txid"] = oldWtx.GetHash().ToString();
+ wtxBumped.vOrderForm = oldWtx.vOrderForm;
+ wtxBumped.strFromAccount = oldWtx.strFromAccount;
+ wtxBumped.fTimeReceivedIsTxTime = true;
+ wtxBumped.fFromMe = true;
+ CValidationState state;
+ if (!pWallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
+ // NOTE: CommitTransaction never returns false, so this should never happen.
+ vErrors.push_back(strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
+ return false;
+ }
+
+ bumpedTxid = wtxBumped.GetHash();
+ 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 (!pWallet->MarkReplaced(oldWtx.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.");
+ }
+ return true;
+}
+
diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h
new file mode 100644
index 0000000000..681f55e4e5
--- /dev/null
+++ b/src/wallet/feebumper.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2017 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_FEEBUMPER_H
+#define BITCOIN_WALLET_FEEBUMPER_H
+
+#include <primitives/transaction.h>
+
+class CWallet;
+class uint256;
+
+enum class BumpFeeResult
+{
+ OK,
+ INVALID_ADDRESS_OR_KEY,
+ INVALID_REQUEST,
+ INVALID_PARAMETER,
+ WALLET_ERROR,
+ MISC_ERROR,
+};
+
+class CFeeBumper
+{
+public:
+ CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable);
+ BumpFeeResult getResult() const { return currentResult; }
+ const std::vector<std::string>& getErrors() const { return vErrors; }
+ CAmount getOldFee() const { return nOldFee; }
+ CAmount getNewFee() const { return nNewFee; }
+ uint256 getBumpedTxId() const { return bumpedTxid; }
+
+ /* signs the new transaction,
+ * returns false if the tx couldn't be found or if it was
+ * impossible to create the signature(s)
+ */
+ bool signTransaction(CWallet *pWallet);
+
+ /* commits the fee bump,
+ * returns true, in case of CWallet::CommitTransaction was successful
+ * but, eventually sets vErrors if the tx could not be added to the mempool (will try later)
+ * or if the old transaction could not be marked as replaced
+ */
+ bool commit(CWallet *pWalletNonConst);
+
+private:
+ const uint256 txid;
+ uint256 bumpedTxid;
+ CMutableTransaction mtx;
+ std::vector<std::string> vErrors;
+ BumpFeeResult currentResult;
+ CAmount nOldFee;
+ CAmount nNewFee;
+};
+
+#endif // BITCOIN_WALLET_FEEBUMPER_H
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index ccb744c6b2..2cc3072c16 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -18,8 +18,9 @@
#include "timedata.h"
#include "util.h"
#include "utilmoneystr.h"
-#include "wallet.h"
-#include "walletdb.h"
+#include "wallet/feebumper.h"
+#include "wallet/wallet.h"
+#include "wallet/walletdb.h"
#include <stdint.h>
@@ -2778,33 +2779,6 @@ 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, CWallet &wallet)
-{
- CMutableTransaction txNew(tx);
- std::vector<std::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 = wallet.mapWallet.find(input.prevout.hash);
- assert(mi != wallet.mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
- vCoins.emplace_back(std::make_pair(&(mi->second), input.prevout.n));
- }
- if (!wallet.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)
{
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
@@ -2859,63 +2833,6 @@ UniValue bumpfee(const JSONRPCRequest& request)
uint256 hash;
hash.SetHex(request.params[0].get_str());
- // retrieve the original tx from the wallet
- LOCK2(cs_main, pwallet->cs_wallet);
- EnsureWalletIsUnlocked(pwallet);
- if (!pwallet->mapWallet.count(hash)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
- }
- CWalletTx& wtx = pwallet->mapWallet[hash];
-
- if (pwallet->HasWalletSpend(hash)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "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_INVALID_PARAMETER, "Transaction has descendants in the mempool");
- }
- }
-
- if (wtx.GetDepthInMainChain() != 0) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction has been mined, or is conflicted with a mined transaction");
- }
-
- if (!SignalsOptInRBF(wtx)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction is not BIP 125 replaceable");
- }
-
- if (wtx.mapValue.count("replaced_by_txid")) {
- throw JSONRPCError(RPC_WALLET_ERROR, 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 (!pwallet->IsAllFromMe(wtx, ISMINE_SPENDABLE)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "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 (pwallet->IsChange(wtx.tx->vout[i])) {
- if (nOutput != -1) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction has multiple change outputs");
- }
- nOutput = i;
- }
- }
- if (nOutput == -1) {
- throw JSONRPCError(RPC_WALLET_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, *pwallet);
-
// optional parameters
bool specifiedConfirmTarget = false;
int newConfirmTarget = nTxConfirmTarget;
@@ -2941,11 +2858,8 @@ UniValue bumpfee(const JSONRPCRequest& request)
}
} 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 (totalFee <= 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee %s (must be greater than 0)", FormatMoney(totalFee)));
}
}
@@ -2954,144 +2868,48 @@ UniValue bumpfee(const JSONRPCRequest& request)
}
}
- // 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_WALLET_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_WALLET_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_WALLET_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(BCLog::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;
- }
- }
+ LOCK2(cs_main, pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(pwallet);
- // sign the new tx
- CTransaction txNewConst(tx);
- int nIn = 0;
- for (auto& input : tx.vin) {
- std::map<uint256, CWalletTx>::const_iterator mi = pwallet->mapWallet.find(input.prevout.hash);
- assert(mi != pwallet->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(pwallet, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
+ CFeeBumper feeBump(pwallet, hash, newConfirmTarget, specifiedConfirmTarget, totalFee, replaceable);
+ BumpFeeResult res = feeBump.getResult();
+ if (res != BumpFeeResult::OK)
+ {
+ switch(res) {
+ case BumpFeeResult::INVALID_ADDRESS_OR_KEY:
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, feeBump.getErrors()[0]);
+ break;
+ case BumpFeeResult::INVALID_REQUEST:
+ throw JSONRPCError(RPC_INVALID_REQUEST, feeBump.getErrors()[0]);
+ break;
+ case BumpFeeResult::INVALID_PARAMETER:
+ throw JSONRPCError(RPC_INVALID_PARAMETER, feeBump.getErrors()[0]);
+ break;
+ case BumpFeeResult::WALLET_ERROR:
+ throw JSONRPCError(RPC_WALLET_ERROR, feeBump.getErrors()[0]);
+ break;
+ default:
+ throw JSONRPCError(RPC_MISC_ERROR, feeBump.getErrors()[0]);
+ break;
}
- UpdateTransaction(tx, nIn, sigdata);
- nIn++;
}
- // commit/broadcast the tx
- CReserveKey reservekey(pwallet);
- CWalletTx wtxBumped(pwallet, MakeTransactionRef(std::move(tx)));
- wtxBumped.mapValue = wtx.mapValue;
- wtxBumped.mapValue["replaces_txid"] = hash.ToString();
- wtxBumped.vOrderForm = wtx.vOrderForm;
- wtxBumped.strFromAccount = wtx.strFromAccount;
- wtxBumped.fTimeReceivedIsTxTime = true;
- wtxBumped.fFromMe = true;
- CValidationState state;
- if (!pwallet->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)));
+ // sign bumped transaction
+ if (!feeBump.signTransaction(pwallet)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
}
-
- // mark the original tx as bumped
- if (!pwallet->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.");
+ // commit the bumped transaction
+ if(!feeBump.commit(pwallet)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, feeBump.getErrors()[0]);
}
-
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));
+ result.push_back(Pair("txid", feeBump.getBumpedTxId().GetHex()));
+ result.push_back(Pair("origfee", ValueFromAmount(feeBump.getOldFee())));
+ result.push_back(Pair("fee", ValueFromAmount(feeBump.getNewFee())));
+ UniValue errors(UniValue::VARR);
+ for (const std::string& err: feeBump.getErrors())
+ errors.push_back(err);
+ result.push_back(errors);
return result;
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 1b92c5e747..b0aa743422 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -17,6 +17,7 @@
#include "validation.h"
#include "net.h"
#include "policy/policy.h"
+#include "policy/rbf.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "script/script.h"
@@ -1792,10 +1793,7 @@ CAmount CWalletTx::GetChange() const
bool CWalletTx::InMempool() const
{
LOCK(mempool.cs);
- if (mempool.exists(GetHash())) {
- return true;
- }
- return false;
+ return mempool.exists(GetHash());
}
bool CWalletTx::IsTrusted() const
@@ -2287,6 +2285,28 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
return res;
}
+bool CWallet::SignTransaction(CMutableTransaction &tx)
+{
+ // sign the new tx
+ CTransaction txNewConst(tx);
+ int nIn = 0;
+ for (auto& input : tx.vin) {
+ std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash);
+ if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) {
+ return false;
+ }
+ 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(this, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
+ return false;
+ }
+ UpdateTransaction(tx, nIn, sigdata);
+ nIn++;
+ }
+ return true;
+}
+
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey, const CTxDestination& destChange)
{
std::vector<CRecipient> vecSend;
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 05c00326ec..cf90d261a5 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -870,6 +870,7 @@ public:
* calling CreateTransaction();
*/
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey = true, const CTxDestination& destChange = CNoDestination());
+ bool SignTransaction(CMutableTransaction& tx);
/**
* Create a new transaction paying the recipients with a set of coins
@@ -884,7 +885,7 @@ public:
bool AddAccountingEntry(const CAccountingEntry&);
bool AddAccountingEntry(const CAccountingEntry&, CWalletDB *pwalletdb);
template <typename ContainerType>
- bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins);
+ bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const;
static CFeeRate minTxFee;
static CFeeRate fallbackFee;
@@ -1123,7 +1124,7 @@ public:
// ContainerType is meant to hold pair<CWalletTx *, int>, and be iterable
// so that each entry corresponds to each vIn, in order.
template <typename ContainerType>
-bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins)
+bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const
{
// Fill in dummy signatures for fee calculation.
int nIn = 0;