aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dummywallet.cpp2
-rw-r--r--src/init.cpp2
-rw-r--r--src/net_processing.cpp12
-rw-r--r--src/qt/bitcoingui.cpp8
-rw-r--r--src/qt/walletcontroller.cpp64
-rw-r--r--src/qt/walletcontroller.h17
-rw-r--r--src/wallet/coincontrol.h29
-rw-r--r--src/wallet/coinselection.h12
-rw-r--r--src/wallet/init.cpp1
-rw-r--r--src/wallet/rpcdump.cpp4
-rw-r--r--src/wallet/rpcwallet.cpp134
-rw-r--r--src/wallet/salvage.cpp2
-rw-r--r--src/wallet/spend.cpp81
-rw-r--r--src/wallet/spend.h5
-rw-r--r--src/wallet/test/wallet_tests.cpp4
-rw-r--r--src/wallet/wallet.cpp42
-rw-r--r--src/wallet/wallet.h11
-rw-r--r--src/wallet/walletdb.cpp22
-rw-r--r--src/wallet/walletdb.h3
-rw-r--r--src/wallet/wallettool.cpp4
20 files changed, 339 insertions, 120 deletions
diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp
index 8caeb32627..7f6471740f 100644
--- a/src/dummywallet.cpp
+++ b/src/dummywallet.cpp
@@ -39,8 +39,6 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
"-maxtxfee=<amt>",
"-mintxfee=<amt>",
"-paytxfee=<amt>",
- "-rescan",
- "-salvagewallet",
"-signer=<cmd>",
"-spendzeroconfchange",
"-txconfirmtarget=<n>",
diff --git a/src/init.cpp b/src/init.cpp
index df0c753b04..65e3c275e9 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -397,7 +397,7 @@ void SetupServerArgs(ArgsManager& argsman)
-GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
- argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex, -coinstatsindex and -rescan. "
+ argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -coinstatsindex. "
"Warning: Reverting this setting requires re-downloading the entire blockchain. "
"(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index e130272ff1..008b4d679c 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -884,6 +884,12 @@ bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, st
void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid)
{
AssertLockHeld(cs_main);
+
+ // Never request high-bandwidth mode from peers if we're blocks-only. Our
+ // mempool will not contain the transactions necessary to reconstruct the
+ // compact block.
+ if (m_ignore_incoming_txs) return;
+
CNodeState* nodestate = State(nodeid);
if (!nodestate || !nodestate->fSupportsDesiredCmpctVersion) {
// Never ask from peers who can't provide witnesses.
@@ -2165,7 +2171,11 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
pindexLast->GetBlockHash().ToString(), pindexLast->nHeight);
}
if (vGetData.size() > 0) {
- if (nodestate->fSupportsDesiredCmpctVersion && vGetData.size() == 1 && mapBlocksInFlight.size() == 1 && pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) {
+ if (!m_ignore_incoming_txs &&
+ nodestate->fSupportsDesiredCmpctVersion &&
+ vGetData.size() == 1 &&
+ mapBlocksInFlight.size() == 1 &&
+ pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) {
// In any case, we want to download using a compact block, not a regular one
vGetData[0] = CInv(MSG_CMPCT_BLOCK, vGetData[0].hash);
}
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index af05f35759..610637360b 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -107,7 +107,6 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
walletFrame = new WalletFrame(_platformStyle, this);
connect(walletFrame, &WalletFrame::createWalletButtonClicked, [this] {
auto activity = new CreateWalletActivity(getWalletController(), this);
- connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater);
activity->create();
});
connect(walletFrame, &WalletFrame::message, [this](const QString& title, const QString& message, unsigned int style) {
@@ -418,7 +417,6 @@ void BitcoinGUI::createActions()
connect(action, &QAction::triggered, [this, path] {
auto activity = new OpenWalletActivity(m_wallet_controller, this);
connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet);
- connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater);
activity->open(path);
});
}
@@ -433,7 +431,6 @@ void BitcoinGUI::createActions()
connect(m_create_wallet_action, &QAction::triggered, [this] {
auto activity = new CreateWalletActivity(m_wallet_controller, this);
connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet);
- connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater);
activity->create();
});
connect(m_close_all_wallets_action, &QAction::triggered, [this] {
@@ -664,9 +661,8 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller)
GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
- for (WalletModel* wallet_model : m_wallet_controller->getOpenWallets()) {
- addWallet(wallet_model);
- }
+ auto activity = new LoadWalletsActivity(m_wallet_controller, this);
+ activity->load();
}
WalletController* BitcoinGUI::getWalletController()
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index 3cceb5ca5a..4c74bcd480 100644
--- a/src/qt/walletcontroller.cpp
+++ b/src/qt/walletcontroller.cpp
@@ -41,10 +41,6 @@ WalletController::WalletController(ClientModel& client_model, const PlatformStyl
getOrCreateWallet(std::move(wallet));
});
- for (std::unique_ptr<interfaces::Wallet>& wallet : m_node.walletClient().getWallets()) {
- getOrCreateWallet(std::move(wallet));
- }
-
m_activity_worker->moveToThread(m_activity_thread);
m_activity_thread->start();
QTimer::singleShot(0, m_activity_worker, []() {
@@ -61,12 +57,6 @@ WalletController::~WalletController()
delete m_activity_worker;
}
-std::vector<WalletModel*> WalletController::getOpenWallets() const
-{
- QMutexLocker locker(&m_mutex);
- return m_wallets;
-}
-
std::map<std::string, bool> WalletController::listWalletDir() const
{
QMutexLocker locker(&m_mutex);
@@ -191,33 +181,23 @@ WalletControllerActivity::WalletControllerActivity(WalletController* wallet_cont
, m_wallet_controller(wallet_controller)
, m_parent_widget(parent_widget)
{
-}
-
-WalletControllerActivity::~WalletControllerActivity()
-{
- delete m_progress_dialog;
+ connect(this, &WalletControllerActivity::finished, this, &QObject::deleteLater);
}
void WalletControllerActivity::showProgressDialog(const QString& label_text)
{
- assert(!m_progress_dialog);
- m_progress_dialog = new QProgressDialog(m_parent_widget);
-
- m_progress_dialog->setLabelText(label_text);
- m_progress_dialog->setRange(0, 0);
- m_progress_dialog->setCancelButton(nullptr);
- m_progress_dialog->setWindowModality(Qt::ApplicationModal);
- GUIUtil::PolishProgressDialog(m_progress_dialog);
+ auto progress_dialog = new QProgressDialog(m_parent_widget);
+ progress_dialog->setAttribute(Qt::WA_DeleteOnClose);
+ connect(this, &WalletControllerActivity::finished, progress_dialog, &QWidget::close);
+
+ progress_dialog->setLabelText(label_text);
+ progress_dialog->setRange(0, 0);
+ progress_dialog->setCancelButton(nullptr);
+ progress_dialog->setWindowModality(Qt::ApplicationModal);
+ GUIUtil::PolishProgressDialog(progress_dialog);
// The setValue call forces QProgressDialog to start the internal duration estimation.
// See details in https://bugreports.qt.io/browse/QTBUG-47042.
- m_progress_dialog->setValue(0);
-}
-
-void WalletControllerActivity::destroyProgressDialog()
-{
- assert(m_progress_dialog);
- delete m_progress_dialog;
- m_progress_dialog = nullptr;
+ progress_dialog->setValue(0);
}
CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
@@ -279,8 +259,6 @@ void CreateWalletActivity::createWallet()
void CreateWalletActivity::finish()
{
- destroyProgressDialog();
-
if (!m_error_message.empty()) {
QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message.translated));
} else if (!m_warning_message.empty()) {
@@ -329,8 +307,6 @@ OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWid
void OpenWalletActivity::finish()
{
- destroyProgressDialog();
-
if (!m_error_message.empty()) {
QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message.translated));
} else if (!m_warning_message.empty()) {
@@ -356,3 +332,21 @@ void OpenWalletActivity::open(const std::string& path)
QTimer::singleShot(0, this, &OpenWalletActivity::finish);
});
}
+
+LoadWalletsActivity::LoadWalletsActivity(WalletController* wallet_controller, QWidget* parent_widget)
+ : WalletControllerActivity(wallet_controller, parent_widget)
+{
+}
+
+void LoadWalletsActivity::load()
+{
+ showProgressDialog(tr("Loading wallets…"));
+
+ QTimer::singleShot(0, worker(), [this] {
+ for (auto& wallet : node().walletClient().getWallets()) {
+ m_wallet_controller->getOrCreateWallet(std::move(wallet));
+ }
+
+ QTimer::singleShot(0, this, [this] { Q_EMIT finished(); });
+ });
+}
diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h
index f7e366878d..f97a7a1e84 100644
--- a/src/qt/walletcontroller.h
+++ b/src/qt/walletcontroller.h
@@ -52,9 +52,6 @@ public:
WalletController(ClientModel& client_model, const PlatformStyle* platform_style, QObject* parent);
~WalletController();
- //! Returns wallet models currently open.
- std::vector<WalletModel*> getOpenWallets() const;
-
WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet);
//! Returns all wallet names in the wallet dir mapped to whether the wallet
@@ -90,7 +87,7 @@ class WalletControllerActivity : public QObject
public:
WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget);
- virtual ~WalletControllerActivity();
+ virtual ~WalletControllerActivity() = default;
Q_SIGNALS:
void finished();
@@ -100,11 +97,9 @@ protected:
QObject* worker() const { return m_wallet_controller->m_activity_worker; }
void showProgressDialog(const QString& label_text);
- void destroyProgressDialog();
WalletController* const m_wallet_controller;
QWidget* const m_parent_widget;
- QProgressDialog* m_progress_dialog{nullptr};
WalletModel* m_wallet_model{nullptr};
bilingual_str m_error_message;
std::vector<bilingual_str> m_warning_message;
@@ -150,4 +145,14 @@ private:
void finish();
};
+class LoadWalletsActivity : public WalletControllerActivity
+{
+ Q_OBJECT
+
+public:
+ LoadWalletsActivity(WalletController* wallet_controller, QWidget* parent_widget);
+
+ void load();
+};
+
#endif // BITCOIN_QT_WALLETCONTROLLER_H
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index 85cbec76b7..c989512d3e 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -9,9 +9,14 @@
#include <policy/feerate.h>
#include <policy/fees.h>
#include <primitives/transaction.h>
+#include <script/keyorigin.h>
+#include <script/signingprovider.h>
#include <script/standard.h>
#include <optional>
+#include <algorithm>
+#include <map>
+#include <set>
const int DEFAULT_MIN_DEPTH = 0;
const int DEFAULT_MAX_DEPTH = 9999999;
@@ -53,6 +58,8 @@ public:
int m_min_depth = DEFAULT_MIN_DEPTH;
//! Maximum chain depth value for coin availability
int m_max_depth = DEFAULT_MAX_DEPTH;
+ //! SigningProvider that has pubkeys and scripts to do spend size estimation for external inputs
+ FlatSigningProvider m_external_provider;
CCoinControl();
@@ -66,11 +73,32 @@ public:
return (setSelected.count(output) > 0);
}
+ bool IsExternalSelected(const COutPoint& output) const
+ {
+ return (m_external_txouts.count(output) > 0);
+ }
+
+ bool GetExternalOutput(const COutPoint& outpoint, CTxOut& txout) const
+ {
+ const auto ext_it = m_external_txouts.find(outpoint);
+ if (ext_it == m_external_txouts.end()) {
+ return false;
+ }
+ txout = ext_it->second;
+ return true;
+ }
+
void Select(const COutPoint& output)
{
setSelected.insert(output);
}
+ void Select(const COutPoint& outpoint, const CTxOut& txout)
+ {
+ setSelected.insert(outpoint);
+ m_external_txouts.emplace(outpoint, txout);
+ }
+
void UnSelect(const COutPoint& output)
{
setSelected.erase(output);
@@ -88,6 +116,7 @@ public:
private:
std::set<COutPoint> setSelected;
+ std::map<COutPoint, CTxOut> m_external_txouts;
};
#endif // BITCOIN_WALLET_COINCONTROL_H
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index a28bee622e..78d877a10b 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -37,6 +37,18 @@ public:
m_input_bytes = input_bytes;
}
+ CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in)
+ {
+ outpoint = outpoint_in;
+ txout = txout_in;
+ effective_value = txout.nValue;
+ }
+
+ CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in)
+ {
+ m_input_bytes = input_bytes;
+ }
+
COutPoint outpoint;
CTxOut txout;
CAmount effective_value;
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 7abdbb0e55..59a59f9794 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -62,7 +62,6 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-paytxfee=<amt>", strprintf("Fee rate (in %s/kvB) to add to transactions you send (default: %s)",
CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
- argsman.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
#ifdef ENABLE_EXTERNAL_SIGNER
argsman.AddArg("-signer=<cmd>", "External signing tool, see doc/external-signer.md", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
#endif
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 382e8b6116..4d7fb2d38c 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1439,7 +1439,7 @@ RPCHelpMan importmulti()
"and coins using this key may not appear in the wallet. This error could be "
"caused by pruning or data corruption (see bitcoind log for details) and could "
"be dealt with by downloading and rescanning the relevant blocks (see -reindex "
- "and -rescan options).",
+ "option and rescanblockchain RPC).",
GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
response.push_back(std::move(result));
}
@@ -1744,7 +1744,7 @@ RPCHelpMan importdescriptors()
"and coins using this desc may not appear in the wallet. This error could be "
"caused by pruning or data corruption (see bitcoind log for details) and could "
"be dealt with by downloading and rescanning the relevant blocks (see -reindex "
- "and -rescan options).",
+ "option and rescanblockchain RPC).",
GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
response.push_back(std::move(result));
}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 7bc2dc7b21..aa97b748b1 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -43,6 +43,7 @@
#include <univalue.h>
+#include <map>
using interfaces::FoundBlock;
@@ -2632,7 +2633,7 @@ static RPCHelpMan loadwallet()
return RPCHelpMan{"loadwallet",
"\nLoads a wallet from a wallet file or directory."
"\nNote that all wallet command-line options used when starting bitcoind will be"
- "\napplied to the new wallet (eg -rescan, etc).\n",
+ "\napplied to the new wallet.\n",
{
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
@@ -3213,6 +3214,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
{"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode()
{"feeRate", UniValueType()}, // will be checked by AmountFromValue() below
{"psbt", UniValueType(UniValue::VBOOL)},
+ {"solving_data", UniValueType(UniValue::VOBJ)},
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
{"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
{"replaceable", UniValueType(UniValue::VBOOL)},
@@ -3289,6 +3291,54 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, wallet);
}
+ if (options.exists("solving_data")) {
+ UniValue solving_data = options["solving_data"].get_obj();
+ if (solving_data.exists("pubkeys")) {
+ for (const UniValue& pk_univ : solving_data["pubkeys"].get_array().getValues()) {
+ const std::string& pk_str = pk_univ.get_str();
+ if (!IsHex(pk_str)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", pk_str));
+ }
+ const std::vector<unsigned char> data(ParseHex(pk_str));
+ CPubKey pubkey(data.begin(), data.end());
+ if (!pubkey.IsFullyValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not a valid public key", pk_str));
+ }
+ coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey);
+ // Add witness script for pubkeys
+ const CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey));
+ coinControl.m_external_provider.scripts.emplace(CScriptID(wit_script), wit_script);
+ }
+ }
+
+ if (solving_data.exists("scripts")) {
+ for (const UniValue& script_univ : solving_data["scripts"].get_array().getValues()) {
+ const std::string& script_str = script_univ.get_str();
+ if (!IsHex(script_str)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", script_str));
+ }
+ std::vector<unsigned char> script_data(ParseHex(script_str));
+ const CScript script(script_data.begin(), script_data.end());
+ coinControl.m_external_provider.scripts.emplace(CScriptID(script), script);
+ }
+ }
+
+ if (solving_data.exists("descriptors")) {
+ for (const UniValue& desc_univ : solving_data["descriptors"].get_array().getValues()) {
+ const std::string& desc_str = desc_univ.get_str();
+ FlatSigningProvider desc_out;
+ std::string error;
+ std::vector<CScript> scripts_temp;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, desc_out, error, true);
+ if (!desc) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unable to parse descriptor '%s': %s", desc_str, error));
+ }
+ desc->Expand(0, desc_out, scripts_temp, desc_out);
+ coinControl.m_external_provider = Merge(coinControl.m_external_provider, desc_out);
+ }
+ }
+ }
+
if (tx.vout.size() == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
@@ -3306,6 +3356,19 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
setSubtractFeeFromOutputs.insert(pos);
}
+ // Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected
+ // and to match with the given solving_data. Only used for non-wallet outputs.
+ std::map<COutPoint, Coin> coins;
+ for (const CTxIn& txin : tx.vin) {
+ coins[txin.prevout]; // Create empty map entry keyed by prevout.
+ }
+ wallet.chain().findCoins(coins);
+ for (const auto& coin : coins) {
+ if (!coin.second.out.IsNull()) {
+ coinControl.Select(coin.first, coin.second.out);
+ }
+ }
+
bilingual_str error;
if (!FundTransaction(wallet, tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
@@ -3321,8 +3384,9 @@ static RPCHelpMan fundrawtransaction()
"No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n"
"Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n"
"The inputs added will not be signed, use signrawtransactionwithkey\n"
- " or signrawtransactionwithwallet for that.\n"
- "Note that all existing inputs must have their previous output transaction be in the wallet.\n"
+ "or signrawtransactionwithwallet for that.\n"
+ "All existing inputs must either have their previous output transaction be in the wallet\n"
+ "or be in the UTXO set. Solving data must be provided for non-wallet inputs.\n"
"Note that all inputs selected must be of standard form and P2SH scripts must be\n"
"in the wallet using importaddress or addmultisigaddress (to calculate fees).\n"
"You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n"
@@ -3357,6 +3421,26 @@ static RPCHelpMan fundrawtransaction()
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
+ {"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n"
+ "Used for fee estimation during coin selection.",
+ {
+ {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.",
+ {
+ {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
+ },
+ },
+ {"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.",
+ {
+ {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
+ },
+ },
+ {"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.",
+ {
+ {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"},
+ },
+ }
+ }
+ },
},
"options"},
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
@@ -4202,6 +4286,26 @@ static RPCHelpMan send()
},
{"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Marks this transaction as BIP125 replaceable.\n"
"Allows this transaction to be replaced by a transaction with higher fees"},
+ {"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n"
+ "Used for fee estimation during coin selection.",
+ {
+ {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.",
+ {
+ {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
+ },
+ },
+ {"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.",
+ {
+ {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
+ },
+ },
+ {"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.",
+ {
+ {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"},
+ },
+ }
+ }
+ },
},
"options"},
},
@@ -4489,7 +4593,9 @@ static RPCHelpMan walletcreatefundedpsbt()
{
return RPCHelpMan{"walletcreatefundedpsbt",
"\nCreates and funds a transaction in the Partially Signed Transaction format.\n"
- "Implements the Creator and Updater roles.\n",
+ "Implements the Creator and Updater roles.\n"
+ "All existing inputs must either have their previous output transaction be in the wallet\n"
+ "or be in the UTXO set. Solving data must be provided for non-wallet inputs.\n",
{
{"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "Leave empty to add inputs automatically. See add_inputs option.",
{
@@ -4546,6 +4652,26 @@ static RPCHelpMan walletcreatefundedpsbt()
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
" \"" + FeeModes("\"\n\"") + "\""},
+ {"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n"
+ "Used for fee estimation during coin selection.",
+ {
+ {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.",
+ {
+ {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
+ },
+ },
+ {"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.",
+ {
+ {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
+ },
+ },
+ {"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.",
+ {
+ {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"},
+ },
+ }
+ }
+ },
},
"options"},
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp
index ea045eb6d8..4151099c1f 100644
--- a/src/wallet/salvage.cpp
+++ b/src/wallet/salvage.cpp
@@ -45,7 +45,7 @@ bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::v
// Call Salvage with fAggressive=true to
// get as much data as possible.
// Rewrite salvaged data to fresh wallet file
- // Set -rescan so any missing transactions will be
+ // Rescan so any missing transactions will be
// found.
int64_t now = GetTime();
std::string newFilename = strprintf("%s.%d.bak", filename, now);
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 1724375f4c..43fcc0416d 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -5,6 +5,7 @@
#include <consensus/validation.h>
#include <interfaces/chain.h>
#include <policy/policy.h>
+#include <script/signingprovider.h>
#include <util/check.h>
#include <util/fees.h>
#include <util/moneystr.h>
@@ -31,21 +32,27 @@ std::string COutput::ToString() const
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
}
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
{
CMutableTransaction txn;
txn.vin.push_back(CTxIn(COutPoint()));
- if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
+ if (!provider || !DummySignInput(*provider, txn.vin[0], txout, use_max_sig)) {
return -1;
}
return GetVirtualTransactionInputSize(txn.vin[0]);
}
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
+{
+ const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey);
+ return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig);
+}
+
// txouts needs to be in the order of tx.vin
-TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig)
+TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control)
{
CMutableTransaction txNew(tx);
- if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
+ if (!wallet->DummySignTx(txNew, txouts, coin_control)) {
return TxSize{-1, -1};
}
CTransaction ctx(txNew);
@@ -54,19 +61,27 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
return TxSize{vsize, weight};
}
-TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
+TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control)
{
std::vector<CTxOut> txouts;
+ // Look up the inputs. The inputs are either in the wallet, or in coin_control.
for (const CTxIn& input : tx.vin) {
const auto mi = wallet->mapWallet.find(input.prevout.hash);
// Can not estimate size without knowing the input details
- if (mi == wallet->mapWallet.end()) {
+ if (mi != wallet->mapWallet.end()) {
+ assert(input.prevout.n < mi->second.tx->vout.size());
+ txouts.emplace_back(mi->second.tx->vout.at(input.prevout.n));
+ } else if (coin_control) {
+ CTxOut txout;
+ if (!coin_control->GetExternalOutput(input.prevout, txout)) {
+ return TxSize{-1, -1};
+ }
+ txouts.emplace_back(txout);
+ } else {
return TxSize{-1, -1};
}
- assert(input.prevout.n < mi->second.tx->vout.size());
- txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
}
- return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig);
+ return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
}
void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
@@ -435,32 +450,40 @@ bool SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCo
std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
- for (const COutPoint& outpoint : vPresetInputs)
- {
+ for (const COutPoint& outpoint : vPresetInputs) {
+ int input_bytes = -1;
+ CTxOut txout;
std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash);
- if (it != wallet.mapWallet.end())
- {
+ if (it != wallet.mapWallet.end()) {
const CWalletTx& wtx = it->second;
// Clearly invalid input, fail
if (wtx.tx->vout.size() <= outpoint.n) {
return false;
}
- // Just to calculate the marginal byte size
- CInputCoin coin(wtx.tx, outpoint.n, GetTxSpendSize(wallet, wtx, outpoint.n, false));
- nValueFromPresetInputs += coin.txout.nValue;
- if (coin.m_input_bytes <= 0) {
- return false; // Not solvable, can't estimate size for fee
- }
- coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
- if (coin_selection_params.m_subtract_fee_outputs) {
- value_to_select -= coin.txout.nValue;
- } else {
- value_to_select -= coin.effective_value;
+ input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false);
+ txout = wtx.tx->vout.at(outpoint.n);
+ }
+ if (input_bytes == -1) {
+ // The input is external. We either did not find the tx in mapWallet, or we did but couldn't compute the input size with wallet data
+ if (!coin_control.GetExternalOutput(outpoint, txout)) {
+ // Not ours, and we don't have solving data.
+ return false;
}
- setPresetCoins.insert(coin);
+ input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true);
+ }
+
+ CInputCoin coin(outpoint, txout, input_bytes);
+ nValueFromPresetInputs += coin.txout.nValue;
+ if (coin.m_input_bytes <= 0) {
+ return false; // Not solvable, can't estimate size for fee
+ }
+ coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
+ if (coin_selection_params.m_subtract_fee_outputs) {
+ value_to_select -= coin.txout.nValue;
} else {
- return false; // TODO: Allow non-wallet inputs
+ value_to_select -= coin.effective_value;
}
+ setPresetCoins.insert(coin);
}
// remove preset inputs from vCoins so that Coin Selection doesn't pick them.
@@ -788,10 +811,10 @@ static bool CreateTransactionInternal(
}
// Calculate the transaction fee
- TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly);
+ TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
int nBytes = tx_sizes.vsize;
if (nBytes < 0) {
- error = _("Signing transaction failed");
+ error = _("Missing solving data for estimating transaction size");
return false;
}
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
@@ -813,7 +836,7 @@ static bool CreateTransactionInternal(
txNew.vout.erase(change_position);
// Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
- tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly);
+ tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
nBytes = tx_sizes.vsize;
fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
}
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index e39f134dc3..3a723d9832 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -66,6 +66,7 @@ public:
//Get the marginal bytes of spending the specified output
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false);
struct TxSize {
int64_t vsize{-1};
@@ -76,8 +77,8 @@ struct TxSize {
* Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
* NOTE: this requires that all inputs must be in mapWallet (eg the tx should
* be AllInputsMine). */
-TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
-TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
+TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr);
+TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
/**
* populate vCoins with vector of available COutputs.
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 5431a38bee..9938380369 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -232,8 +232,8 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
"seconds of key creation, and could contain transactions pertaining to the key. As a result, "
"transactions and coins using this key may not appear in the wallet. This error could be caused "
"by pruning or data corruption (see bitcoind log for details) and could be dealt with by "
- "downloading and rescanning the relevant blocks (see -reindex and -rescan "
- "options).\"}},{\"success\":true}]",
+ "downloading and rescanning the relevant blocks (see -reindex option and rescanblockchain "
+ "RPC).\"}},{\"success\":true}]",
0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW));
RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt);
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 6885499be2..bf6d8e7510 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1448,19 +1448,13 @@ bool CWallet::AddWalletFlags(uint64_t flags)
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
-bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) const
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig)
{
// Fill in dummy signatures for fee calculation.
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
- std::unique_ptr<SigningProvider> provider = GetSolvingProvider(scriptPubKey);
- if (!provider) {
- // We don't know about this scriptpbuKey;
- return false;
- }
-
- if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
+ if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
UpdateInput(tx_in, sigdata);
@@ -1468,14 +1462,21 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig
}
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
-bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig) const
+bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
{
// Fill in dummy signatures for fee calculation.
int nIn = 0;
for (const auto& txout : txouts)
{
- if (!DummySignInput(txNew.vin[nIn], txout, use_max_sig)) {
- return false;
+ CTxIn& txin = txNew.vin[nIn];
+ // Use max sig if watch only inputs were used or if this particular input is an external input
+ // to ensure a sufficient fee is attained for the requested feerate.
+ const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout));
+ const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
+ if (!provider || !DummySignInput(*provider, txin, txout, use_max_sig)) {
+ if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, use_max_sig)) {
+ return false;
+ }
}
nIn++;
@@ -1600,7 +1601,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());
fAbortRescan = false;
- ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
+ ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if rescan required on startup (e.g. due to corruption)
uint256 tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
uint256 end_hash = tip_hash;
if (max_height) chain().findAncestorByHeight(tip_hash, *max_height, FoundBlock().hash(end_hash));
@@ -2007,10 +2008,7 @@ DBErrors CWallet::LoadWallet()
assert(m_internal_spk_managers.empty());
}
- if (nLoadWalletRet != DBErrors::LOAD_OK)
- return nLoadWalletRet;
-
- return DBErrors::LOAD_OK;
+ return nLoadWalletRet;
}
DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
@@ -2542,6 +2540,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
// TODO: Can't use std::make_shared because we need a custom deleter but
// should be possible to use std::allocate_shared.
std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, std::move(database)), ReleaseWallet);
+ bool rescan_required = false;
DBErrors nLoadWalletRet = walletInstance->LoadWallet();
if (nLoadWalletRet != DBErrors::LOAD_OK) {
if (nLoadWalletRet == DBErrors::CORRUPT) {
@@ -2562,6 +2561,10 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
{
error = strprintf(_("Wallet needed to be rewritten: restart %s to complete"), PACKAGE_NAME);
return nullptr;
+ } else if (nLoadWalletRet == DBErrors::NEED_RESCAN) {
+ warnings.push_back(strprintf(_("Error reading %s! Transaction data may be missing or incorrect."
+ " Rescanning wallet."), walletFile));
+ rescan_required = true;
}
else {
error = strprintf(_("Error loading %s"), walletFile);
@@ -2753,7 +2756,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
LOCK(walletInstance->cs_wallet);
- if (chain && !AttachChain(walletInstance, *chain, error, warnings)) {
+ if (chain && !AttachChain(walletInstance, *chain, rescan_required, error, warnings)) {
return nullptr;
}
@@ -2775,7 +2778,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
return walletInstance;
}
-bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interfaces::Chain& chain, bilingual_str& error, std::vector<bilingual_str>& warnings)
+bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interfaces::Chain& chain, const bool rescan_required, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
LOCK(walletInstance->cs_wallet);
// allow setting the chain if it hasn't been set already but prevent changing it
@@ -2792,8 +2795,9 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
// interface.
walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance);
+ // If rescan_required = true, rescan_height remains equal to 0
int rescan_height = 0;
- if (!gArgs.GetBoolArg("-rescan", false))
+ if (!rescan_required)
{
WalletBatch batch(walletInstance->GetDatabase());
CBlockLocator locator;
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 15a5933424..b949fe3040 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -337,7 +337,7 @@ private:
* block locator and m_last_block_processed, and registering for
* notifications about new blocks and transactions.
*/
- static bool AttachChain(const std::shared_ptr<CWallet>& wallet, interfaces::Chain& chain, bilingual_str& error, std::vector<bilingual_str>& warnings);
+ static bool AttachChain(const std::shared_ptr<CWallet>& wallet, interfaces::Chain& chain, const bool rescan_required, bilingual_str& error, std::vector<bilingual_str>& warnings);
public:
/**
@@ -576,14 +576,13 @@ public:
/** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */
bool SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const;
- bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const
+ bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const
{
std::vector<CTxOut> v_txouts(txouts.size());
std::copy(txouts.begin(), txouts.end(), v_txouts.begin());
- return DummySignTx(txNew, v_txouts, use_max_sig);
+ return DummySignTx(txNew, v_txouts, coin_control);
}
- bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const;
- bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const;
+ bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const;
bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -928,4 +927,6 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
//! Remove wallet name from persistent configuration so it will not be loaded on startup.
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig);
+
#endif // BITCOIN_WALLET_WALLET_H
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index c697534c06..8ff09a0878 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -311,6 +311,7 @@ public:
std::map<std::pair<uint256, CKeyID>, CKey> m_descriptor_keys;
std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys;
std::map<uint160, CHDChain> m_hd_chains;
+ bool tx_corrupt{false};
CWalletScanState() {
}
@@ -345,7 +346,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
// LoadToWallet call below creates a new CWalletTx that fill_wtx
// callback fills with transaction metadata.
auto fill_wtx = [&](CWalletTx& wtx, bool new_tx) {
- assert(new_tx);
+ if(!new_tx) {
+ // There's some corruption here since the tx we just tried to load was already in the wallet.
+ // We don't consider this type of corruption critical, and can fix it by removing tx data and
+ // rescanning.
+ wss.tx_corrupt = true;
+ return false;
+ }
ssValue >> wtx;
if (wtx.GetHash() != hash)
return false;
@@ -755,6 +762,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
{
CWalletScanState wss;
bool fNoncriticalErrors = false;
+ bool rescan_required = false;
DBErrors result = DBErrors::LOAD_OK;
LOCK(pwallet->cs_wallet);
@@ -818,12 +826,17 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
} else if (strType == DBKeys::FLAGS) {
// reading the wallet flags can only fail if unknown flags are present
result = DBErrors::TOO_NEW;
+ } else if (wss.tx_corrupt) {
+ pwallet->WalletLogPrintf("Error: Corrupt transaction found. This can be fixed by removing transactions from wallet and rescanning.\n");
+ // Set tx_corrupt back to false so that the error is only printed once (per corrupt tx)
+ wss.tx_corrupt = false;
+ result = DBErrors::CORRUPT;
} else {
// Leave other errors alone, if we try to fix them we might make things worse.
fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
if (strType == DBKeys::TX)
// Rescan if there is a bad transaction record:
- gArgs.SoftSetBoolArg("-rescan", true);
+ rescan_required = true;
}
}
if (!strErr.empty())
@@ -859,8 +872,11 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
((DescriptorScriptPubKeyMan*)spk_man)->AddCryptedKey(desc_key_pair.first.second, desc_key_pair.second.first, desc_key_pair.second.second);
}
- if (fNoncriticalErrors && result == DBErrors::LOAD_OK)
+ if (rescan_required && result == DBErrors::LOAD_OK) {
+ result = DBErrors::NEED_RESCAN;
+ } else if (fNoncriticalErrors && result == DBErrors::LOAD_OK) {
result = DBErrors::NONCRITICAL_ERROR;
+ }
// Any wallet corruption at all: skip any rewriting or
// upgrading, we don't want to make it worse.
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index a549c8039c..715d9012ed 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -48,7 +48,8 @@ enum class DBErrors
NONCRITICAL_ERROR,
TOO_NEW,
LOAD_FAIL,
- NEED_REWRITE
+ NEED_REWRITE,
+ NEED_RESCAN
};
namespace DBKeys {
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 50b6c9d29f..e3cb5cee5d 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -76,6 +76,10 @@ static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::pa
} else if (load_wallet_ret == DBErrors::NEED_REWRITE) {
tfm::format(std::cerr, "Wallet needed to be rewritten: restart %s to complete", PACKAGE_NAME);
return nullptr;
+ } else if (load_wallet_ret == DBErrors::NEED_RESCAN) {
+ tfm::format(std::cerr, "Error reading %s! Some transaction data might be missing or"
+ " incorrect. Wallet requires a rescan.",
+ name);
} else {
tfm::format(std::cerr, "Error loading %s", name);
return nullptr;