aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bench/coin_selection.cpp3
-rw-r--r--src/interfaces/chain.h4
-rw-r--r--src/node/interfaces.cpp17
-rw-r--r--src/qt/bitcoingui.cpp33
-rw-r--r--src/qt/bitcoingui.h1
-rw-r--r--src/qt/optionsdialog.cpp16
-rw-r--r--src/qt/test/wallettests.cpp2
-rw-r--r--src/qt/walletcontroller.cpp43
-rw-r--r--src/qt/walletcontroller.h20
-rw-r--r--src/wallet/rpc/transactions.cpp2
-rw-r--r--src/wallet/spend.cpp24
-rw-r--r--src/wallet/spend.h17
-rw-r--r--src/wallet/test/coinselector_tests.cpp21
-rw-r--r--src/wallet/test/util.cpp2
-rw-r--r--src/wallet/test/wallet_tests.cpp25
-rw-r--r--src/wallet/wallet.cpp45
-rw-r--r--src/wallet/wallet.h11
17 files changed, 220 insertions, 66 deletions
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index b2958bcc9f..eaefb9b63a 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -58,7 +58,8 @@ static void CoinSelection(benchmark::Bench& bench)
// Create coins
std::vector<COutput> coins;
for (const auto& wtx : wtxs) {
- coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
+ const auto txout = wtx->tx->vout.at(0);
+ coins.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
}
const CoinEligibilityFilter filter_standard(1, 6, 0);
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index ddfb4bda95..df9e55874f 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -111,6 +111,10 @@ public:
//! Get locator for the current chain tip.
virtual CBlockLocator getTipLocator() = 0;
+ //! Return a locator that refers to a block in the active chain.
+ //! If specified block is not in the active chain, return locator for the latest ancestor that is in the chain.
+ virtual CBlockLocator getActiveChainLocator(const uint256& block_hash) = 0;
+
//! Return height of the highest block on chain in common with the locator,
//! which will either be the original block used to create the locator,
//! or one of its ancestors.
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 3c085ae6fb..93aa9e526b 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -524,20 +524,27 @@ public:
}
bool haveBlockOnDisk(int height) override
{
- LOCK(cs_main);
+ LOCK(::cs_main);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
CBlockIndex* block = active[height];
return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0;
}
CBlockLocator getTipLocator() override
{
- LOCK(cs_main);
+ LOCK(::cs_main);
const CChain& active = Assert(m_node.chainman)->ActiveChain();
return active.GetLocator();
}
+ CBlockLocator getActiveChainLocator(const uint256& block_hash) override
+ {
+ LOCK(::cs_main);
+ const CBlockIndex* index = chainman().m_blockman.LookupBlockIndex(block_hash);
+ if (!index) return {};
+ return chainman().ActiveChain().GetLocator(index);
+ }
std::optional<int> findLocatorFork(const CBlockLocator& locator) override
{
- LOCK(cs_main);
+ LOCK(::cs_main);
const CChainState& active = Assert(m_node.chainman)->ActiveChainstate();
if (const CBlockIndex* fork = active.FindForkInGlobalIndex(locator)) {
return fork->nHeight;
@@ -593,7 +600,7 @@ public:
void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); }
double guessVerificationProgress(const uint256& block_hash) override
{
- LOCK(cs_main);
+ LOCK(::cs_main);
return GuessVerificationProgress(chainman().GetParams().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash));
}
bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override
@@ -693,7 +700,7 @@ public:
CFeeRate relayDustFee() override { return ::dustRelayFee; }
bool havePruned() override
{
- LOCK(cs_main);
+ LOCK(::cs_main);
return m_node.chainman->m_blockman.m_have_pruned;
}
bool isReadyToBroadcast() override { return !node::fImporting && !node::fReindex && !isInitialBlockDownload(); }
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index d65fc58865..35e32f515b 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -47,6 +47,7 @@
#include <QCursor>
#include <QDateTime>
#include <QDragEnterEvent>
+#include <QInputDialog>
#include <QKeySequence>
#include <QListWidget>
#include <QMenu>
@@ -348,6 +349,12 @@ void BitcoinGUI::createActions()
m_create_wallet_action->setEnabled(false);
m_create_wallet_action->setStatusTip(tr("Create a new wallet"));
+ //: Name of the menu item that restores wallet from a backup file.
+ m_restore_wallet_action = new QAction(tr("Restore Wallet…"), this);
+ m_restore_wallet_action->setEnabled(false);
+ //: Status tip for Restore Wallet menu item
+ m_restore_wallet_action->setStatusTip(tr("Restore a wallet from a backup file"));
+
m_close_all_wallets_action = new QAction(tr("Close All Wallets…"), this);
m_close_all_wallets_action->setStatusTip(tr("Close all wallets"));
@@ -412,6 +419,27 @@ void BitcoinGUI::createActions()
action->setEnabled(false);
}
});
+ connect(m_restore_wallet_action, &QAction::triggered, [this] {
+ //: Name of the wallet data file format.
+ QString name_data_file = tr("Wallet Data");
+
+ //: The title for Restore Wallet File Windows
+ QString title_windows = tr("Load Wallet Backup");
+
+ QString backup_file = GUIUtil::getOpenFileName(this, title_windows, QString(), name_data_file + QLatin1String(" (*.dat)"), nullptr);
+ if (backup_file.isEmpty()) return;
+
+ bool wallet_name_ok;
+ //: Title of the Restore Wallet input dialog (where the wallet name is entered)
+ QString wallet_name = QInputDialog::getText(this, tr("Restore Name"), tr("Wallet Name:"), QLineEdit::Normal, "", &wallet_name_ok);
+ if (!wallet_name_ok || wallet_name.isEmpty()) return;
+
+ auto activity = new RestoreWalletActivity(m_wallet_controller, this);
+ connect(activity, &RestoreWalletActivity::restored, this, &BitcoinGUI::setCurrentWallet, Qt::QueuedConnection);
+
+ auto backup_file_path = fs::PathFromString(backup_file.toStdString());
+ activity->restore(backup_file_path, wallet_name.toStdString());
+ });
connect(m_close_wallet_action, &QAction::triggered, [this] {
m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this);
});
@@ -450,8 +478,10 @@ void BitcoinGUI::createMenuBar()
file->addAction(m_close_wallet_action);
file->addAction(m_close_all_wallets_action);
file->addSeparator();
- file->addAction(openAction);
file->addAction(backupWalletAction);
+ file->addAction(m_restore_wallet_action);
+ file->addSeparator();
+ file->addAction(openAction);
file->addAction(signMessageAction);
file->addAction(verifyMessageAction);
file->addAction(m_load_psbt_action);
@@ -642,6 +672,7 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller)
m_create_wallet_action->setEnabled(true);
m_open_wallet_action->setEnabled(true);
m_open_wallet_action->setMenu(m_open_wallet_menu);
+ m_restore_wallet_action->setEnabled(true);
GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index 6272d5d947..b2e13245e1 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -157,6 +157,7 @@ private:
QAction* m_create_wallet_action{nullptr};
QAction* m_open_wallet_action{nullptr};
QMenu* m_open_wallet_menu{nullptr};
+ QAction* m_restore_wallet_action{nullptr};
QAction* m_close_wallet_action{nullptr};
QAction* m_close_all_wallets_action{nullptr};
QAction* m_wallet_selector_label_action = nullptr;
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 462b923d61..42c8b07763 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -286,11 +286,19 @@ void OptionsDialog::on_resetButton_clicked()
{
if (model) {
// confirmation dialog
+ /*: Text explaining that the settings changed will not come into effect
+ until the client is restarted. */
+ QString reset_dialog_text = tr("Client restart required to activate changes.") + "<br><br>";
+ /*: Text explaining to the user that the client's current settings
+ will be backed up at a specific location. %1 is a stand-in
+ argument for the backup location's path. */
+ reset_dialog_text.append(tr("Current settings will be backed up at \"%1\".").arg(m_client_model->dataDir()) + "<br><br>");
+ /*: Text asking the user to confirm if they would like to proceed
+ with a client shutdown. */
+ reset_dialog_text.append(tr("Client will be shut down. Do you want to proceed?"));
+ //: Window title text of pop-up window shown when the user has chosen to reset options.
QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm options reset"),
- tr("Client restart required to activate changes.") + "<br><br>" +
- tr("Current settings will be backed up at \"%1\".").arg(m_client_model->dataDir()) + "<br><br>" +
- tr("Client will be shut down. Do you want to proceed?"),
- QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
+ reset_dialog_text, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
if (btnRetVal == QMessageBox::Cancel)
return;
diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp
index 7671bfc739..b8ae62ecab 100644
--- a/src/qt/test/wallettests.cpp
+++ b/src/qt/test/wallettests.cpp
@@ -173,7 +173,7 @@ void TestGUI(interfaces::Node& node)
{
WalletRescanReserver reserver(*wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, 0 /* block height */, {} /* max height */, reserver, true /* fUpdate */);
+ CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/false);
QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
QCOMPARE(result.last_scanned_block, node.context()->chainman->ActiveChain().Tip()->GetBlockHash());
QVERIFY(result.last_failed_block.IsNull());
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index d27ddf1aba..11140c5da9 100644
--- a/src/qt/walletcontroller.cpp
+++ b/src/qt/walletcontroller.cpp
@@ -373,3 +373,46 @@ void LoadWalletsActivity::load()
QTimer::singleShot(0, this, [this] { Q_EMIT finished(); });
});
}
+
+RestoreWalletActivity::RestoreWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
+ : WalletControllerActivity(wallet_controller, parent_widget)
+{
+}
+
+void RestoreWalletActivity::restore(const fs::path& backup_file, const std::string& wallet_name)
+{
+ QString name = QString::fromStdString(wallet_name);
+
+ showProgressDialog(
+ //: Title of progress window which is displayed when wallets are being restored.
+ tr("Restore Wallet"),
+ /*: Descriptive text of the restore wallets progress window which indicates to
+ the user that wallets are currently being restored.*/
+ tr("Restoring Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
+
+ QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
+ std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().restoreWallet(backup_file, wallet_name, m_error_message, m_warning_message);
+
+ if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
+
+ QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
+ });
+}
+
+void RestoreWalletActivity::finish()
+{
+ if (!m_error_message.empty()) {
+ //: Title of message box which is displayed when the wallet could not be restored.
+ QMessageBox::critical(m_parent_widget, tr("Restore wallet failed"), QString::fromStdString(m_error_message.translated));
+ } else if (!m_warning_message.empty()) {
+ //: Title of message box which is displayed when the wallet is restored with some warning.
+ QMessageBox::warning(m_parent_widget, tr("Restore wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
+ } else {
+ //: Title of message box which is displayed when the wallet is successfully restored.
+ QMessageBox::information(m_parent_widget, tr("Restore wallet message"), QString::fromStdString(Untranslated("Wallet restored successfully \n").translated));
+ }
+
+ if (m_wallet_model) Q_EMIT restored(m_wallet_model);
+
+ Q_EMIT finished();
+}
diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h
index 24f85c258c..fcd65756c6 100644
--- a/src/qt/walletcontroller.h
+++ b/src/qt/walletcontroller.h
@@ -33,6 +33,10 @@ class Node;
class Wallet;
} // namespace interfaces
+namespace fs {
+class path;
+}
+
class AskPassphraseDialog;
class CreateWalletActivity;
class CreateWalletDialog;
@@ -155,4 +159,20 @@ public:
void load();
};
+class RestoreWalletActivity : public WalletControllerActivity
+{
+ Q_OBJECT
+
+public:
+ RestoreWalletActivity(WalletController* wallet_controller, QWidget* parent_widget);
+
+ void restore(const fs::path& backup_file, const std::string& wallet_name);
+
+Q_SIGNALS:
+ void restored(WalletModel* wallet_model);
+
+private:
+ void finish();
+};
+
#endif // BITCOIN_QT_WALLETCONTROLLER_H
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index 6e42c0736d..d44b62c934 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -889,7 +889,7 @@ RPCHelpMan rescanblockchain()
}
CWallet::ScanResult result =
- pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, true /* fUpdate */);
+ pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, /*fUpdate=*/true, /*save_progress=*/false);
switch (result.status) {
case CWallet::ScanResult::SUCCESS:
break;
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 4a54a5f671..9be29c4709 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -27,25 +27,20 @@ using interfaces::FoundBlock;
namespace wallet {
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
-int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig)
-{
- return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig);
-}
-
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, const CCoinControl* coin_control)
{
CMutableTransaction txn;
- txn.vin.push_back(CTxIn(COutPoint()));
- if (!provider || !DummySignInput(*provider, txn.vin[0], txout, use_max_sig)) {
+ txn.vin.push_back(CTxIn(outpoint));
+ if (!provider || !DummySignInput(*provider, txn.vin[0], txout, coin_control)) {
return -1;
}
return GetVirtualTransactionInputSize(txn.vin[0]);
}
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, const CCoinControl* coin_control)
{
const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey);
- return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig);
+ return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), coin_control);
}
// txouts needs to be in the order of tx.vin
@@ -198,7 +193,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
// Filter by spendable outputs only
if (!spendable && only_spendable) continue;
- int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
+ int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
result.coins.emplace_back(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
result.total_amount += output.nValue;
@@ -289,8 +284,9 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
) {
CTxDestination address;
if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) {
+ const auto out = wtx.tx->vout.at(output.n);
result[address].emplace_back(
- COutPoint(wtx.GetHash(), output.n), wtx.tx->vout.at(output.n), depth, GetTxSpendSize(wallet, wtx, output.n), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL));
+ COutPoint(wtx.GetHash(), output.n), out, depth, CalculateMaximumSignedInputSize(out, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL));
}
}
}
@@ -450,14 +446,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
if (ptr_wtx->tx->vout.size() <= outpoint.n) {
return std::nullopt;
}
- input_bytes = GetTxSpendSize(wallet, *ptr_wtx, outpoint.n, false);
txout = ptr_wtx->tx->vout.at(outpoint.n);
+ input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
} else {
// The input is external. We did not find the tx in mapWallet.
if (!coin_control.GetExternalOutput(outpoint, txout)) {
return std::nullopt;
}
- input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /*use_max_sig=*/true);
+ input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
}
// If available, override calculated size with coin control specified size
if (coin_control.HasInputWeight(outpoint)) {
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index e9078bd792..ab0ff1ee58 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -16,23 +16,16 @@
namespace wallet {
/** Get the marginal bytes if spending the specified output from this transaction.
- * use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the
- * size of the input spend. This should only be set when watch-only outputs are allowed */
-int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false);
-
-//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);
-
+ * Use CoinControl to determine whether to expect signature grinding when calculating the size of the input spend. */
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, const CCoinControl* coin_control = nullptr);
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* pwallet, const CCoinControl* coin_control = nullptr);
struct TxSize {
int64_t vsize{-1};
int64_t weight{-1};
};
-/** Calculate the size of the transaction assuming all signatures are max size
-* Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
-* NOTE: this requires that all inputs must be in mapWallet (eg the tx should
-* be AllInputsMine). */
+/** Calculate the size of the transaction using CoinControl to determine
+ * whether to expect signature grinding when calculating the size of the input spend. */
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);
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index d77c383a30..a418105ee1 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -84,7 +84,8 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
assert(ret.second);
CWalletTx& wtx = (*ret.first).second;
- coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
+ const auto& txout = wtx.tx->vout.at(nInput);
+ coins.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
}
/** Check if SelectionResult a is equivalent to SelectionResult b.
@@ -864,7 +865,23 @@ BOOST_AUTO_TEST_CASE(waste_test)
const CAmount new_target{in_amt - fee * 2 - fee_diff * 2};
add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
- BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /* change cost */ 0, new_target));
+ BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /*change_cost=*/ 0, new_target));
+ selection.clear();
+
+ // Negative waste when the long term fee is greater than the current fee and the selected value == target
+ const CAmount exact_target1{3 * COIN - 2 * fee};
+ const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff))
+ add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
+ add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
+ BOOST_CHECK_EQUAL(target_waste1, GetSelectionWaste(selection, /*change_cost=*/ 0, exact_target1));
+ selection.clear();
+
+ // Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee))
+ const CAmount large_fee_diff{90};
+ const CAmount target_waste2{-2 * large_fee_diff + change_cost}; // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
+ add_coin(1 * COIN, 1, selection, fee, fee + large_fee_diff);
+ add_coin(2 * COIN, 2, selection, fee, fee + large_fee_diff);
+ BOOST_CHECK_EQUAL(target_waste2, GetSelectionWaste(selection, change_cost, target));
}
BOOST_AUTO_TEST_CASE(effective_value_test)
diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp
index aa3121511d..ab72721f9d 100644
--- a/src/wallet/test/util.cpp
+++ b/src/wallet/test/util.cpp
@@ -38,7 +38,7 @@ std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cc
}
WalletRescanReserver reserver(*wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet->ScanForWalletTransactions(cchain.Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet->ScanForWalletTransactions(cchain.Genesis()->GetBlockHash(), /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK_EQUAL(result.last_scanned_block, cchain.Tip()->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, cchain.Height());
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index e893521704..81c01e5023 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -110,7 +110,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/{}, /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK(result.last_scanned_block.IsNull());
@@ -121,7 +121,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
- CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
@@ -129,13 +129,28 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
+ std::chrono::steady_clock::time_point fake_time;
+ reserver.setNow([&] { fake_time += 60s; return fake_time; });
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
+
+ {
+ CBlockLocator locator;
+ BOOST_CHECK(!WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
+ BOOST_CHECK(locator.IsNull());
+ }
+
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/true);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN);
+
+ {
+ CBlockLocator locator;
+ BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
+ BOOST_CHECK(!locator.IsNull());
+ }
}
// Prune the older block file.
@@ -159,7 +174,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
@@ -186,7 +201,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());
BOOST_CHECK(result.last_scanned_block.IsNull());
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index e3f10fc74b..de4a8a6eb7 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1504,13 +1504,16 @@ 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 DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig)
+// or a max-sized low-S signature (e.g. 72 bytes) depending on coin_control
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control)
{
// Fill in dummy signatures for fee calculation.
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
+ // 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(tx_in.prevout));
if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
@@ -1577,12 +1580,9 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
nIn++;
continue;
}
- // 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)) {
+ if (!provider || !DummySignInput(*provider, txin, txout, coin_control)) {
+ if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, coin_control)) {
return false;
}
}
@@ -1665,7 +1665,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
if (start) {
// TODO: this should take into account failure by ScanResult::USER_ABORT
- ScanResult result = ScanForWalletTransactions(start_block, start_height, {} /* max_height */, reserver, update);
+ ScanResult result = ScanForWalletTransactions(start_block, start_height, /*max_height=*/{}, reserver, /*fUpdate=*/update, /*save_progress=*/false);
if (result.status == ScanResult::FAILURE) {
int64_t time_max;
CHECK_NONFATAL(chain().findBlock(result.last_failed_block, FoundBlock().maxTime(time_max)));
@@ -1696,12 +1696,11 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
* the main chain after to the addition of any new keys you want to detect
* transactions for.
*/
-CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate)
+CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate, const bool save_progress)
{
- using Clock = std::chrono::steady_clock;
- constexpr auto LOG_INTERVAL{60s};
- auto current_time{Clock::now()};
- auto start_time{Clock::now()};
+ constexpr auto INTERVAL_TIME{60s};
+ auto current_time{reserver.now()};
+ auto start_time{reserver.now()};
assert(reserver.isReserved());
@@ -1728,8 +1727,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
}
- if (Clock::now() >= current_time + LOG_INTERVAL) {
- current_time = Clock::now();
+
+ bool next_interval = reserver.now() >= current_time + INTERVAL_TIME;
+ if (next_interval) {
+ current_time = reserver.now();
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
}
@@ -1759,6 +1760,16 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
// scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash;
result.last_scanned_height = block_height;
+
+ if (save_progress && next_interval) {
+ CBlockLocator loc = m_chain->getActiveChainLocator(block_hash);
+
+ if (!loc.IsNull()) {
+ WalletLogPrintf("Saving scan progress %d.\n", block_height);
+ WalletBatch batch(GetDatabase());
+ batch.WriteBestBlock(loc);
+ }
+ }
} else {
// could not scan block, keep scanning but record this block as the most recent failure
result.last_failed_block = block_hash;
@@ -1796,7 +1807,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current);
result.status = ScanResult::USER_ABORT;
} else {
- auto duration_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start_time);
+ auto duration_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(reserver.now() - start_time);
WalletLogPrintf("Rescan completed in %15dms\n", duration_milliseconds.count());
}
return result;
@@ -3068,7 +3079,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
{
WalletRescanReserver reserver(*walletInstance);
- if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) {
+ if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/true).status)) {
error = _("Failed to rescan the wallet during initialization");
return false;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 888e2f1181..e2c3d76438 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -528,7 +528,7 @@ public:
//! USER_ABORT.
uint256 last_failed_block;
};
- ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate);
+ ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate, const bool save_progress);
void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override;
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions();
@@ -919,8 +919,11 @@ void MaybeResendWalletTxs(WalletContext& context);
class WalletRescanReserver
{
private:
+ using Clock = std::chrono::steady_clock;
+ using NowFn = std::function<Clock::time_point()>;
CWallet& m_wallet;
bool m_could_reserve;
+ NowFn m_now;
public:
explicit WalletRescanReserver(CWallet& w) : m_wallet(w), m_could_reserve(false) {}
@@ -941,6 +944,10 @@ public:
return (m_could_reserve && m_wallet.fScanningWallet);
}
+ Clock::time_point now() const { return m_now ? m_now() : Clock::now(); };
+
+ void setNow(NowFn now) { m_now = std::move(now); }
+
~WalletRescanReserver()
{
if (m_could_reserve) {
@@ -955,7 +962,7 @@ 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);
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control = nullptr);
bool FillInputToWeight(CTxIn& txin, int64_t target_weight);
} // namespace wallet