aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/coincontrol.h2
-rw-r--r--src/wallet/coinselection.h3
-rw-r--r--src/wallet/db.cpp7
-rw-r--r--src/wallet/dump.cpp3
-rw-r--r--src/wallet/init.cpp8
-rw-r--r--src/wallet/load.cpp5
-rw-r--r--src/wallet/rpcdump.cpp2
-rw-r--r--src/wallet/rpcwallet.cpp21
-rw-r--r--src/wallet/sqlite.cpp100
-rw-r--r--src/wallet/test/coinselector_tests.cpp3
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp3
-rw-r--r--src/wallet/test/wallet_tests.cpp24
-rw-r--r--src/wallet/wallet.cpp130
-rw-r--r--src/wallet/wallet.h13
-rw-r--r--src/wallet/wallettool.cpp3
15 files changed, 188 insertions, 139 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index 716e1922fe..85cbec76b7 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -29,6 +29,8 @@ public:
std::optional<OutputType> m_change_type;
//! If false, only selected inputs are used
bool m_add_inputs = true;
+ //! If false, only safe inputs will be used
+ bool m_include_unsafe_inputs = false;
//! If false, allows unselected inputs, but requires all selected inputs be used
bool fAllowOtherInputs = false;
//! Includes watch only addresses which are solvable
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 5c1b36be6e..5645e6db46 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -65,8 +65,7 @@ struct CoinEligibilityFilter
/** Minimum number of confirmations for outputs that we sent to ourselves.
* We may use unconfirmed UTXOs sent from ourselves, e.g. change outputs. */
const int conf_mine;
- /** Minimum number of confirmations for outputs received from a different
- * wallet. We never spend unconfirmed foreign outputs as we cannot rely on these funds yet. */
+ /** Minimum number of confirmations for outputs received from a different wallet. */
const int conf_theirs;
/** Maximum number of unconfirmed ancestors aggregated across all UTXOs in an OutputGroup. */
const uint64_t max_ancestors;
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index cd49baeb78..5bf037b222 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -18,7 +18,12 @@ std::vector<fs::path> ListDatabases(const fs::path& wallet_dir)
for (auto it = fs::recursive_directory_iterator(wallet_dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) {
if (ec) {
- LogPrintf("%s: %s %s\n", __func__, ec.message(), it->path().string());
+ if (fs::is_directory(*it)) {
+ it.no_push();
+ LogPrintf("%s: %s %s -- skipping.\n", __func__, ec.message(), it->path().string());
+ } else {
+ LogPrintf("%s: %s %s\n", __func__, ec.message(), it->path().string());
+ }
continue;
}
diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp
index e314107988..c39c0c7e73 100644
--- a/src/wallet/dump.cpp
+++ b/src/wallet/dump.cpp
@@ -194,8 +194,7 @@ bool CreateFromDump(const std::string& name, const fs::path& wallet_path, biling
std::shared_ptr<CWallet> wallet(new CWallet(nullptr /* chain */, name, std::move(database)), WalletToolReleaseWallet);
{
LOCK(wallet->cs_wallet);
- bool first_run = true;
- DBErrors load_wallet_ret = wallet->LoadWallet(first_run);
+ DBErrors load_wallet_ret = wallet->LoadWallet();
if (load_wallet_ret != DBErrors::LOAD_OK) {
error = strprintf(_("Error creating %s"), name);
return false;
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 0dc220b6fd..632aae87c9 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -46,19 +46,19 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u (always enabled for wallets with \"avoid_reuse\" enabled))", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
- argsman.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). "
+ argsman.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kvB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). "
"Note: An output is discarded if it is dust at this rate, but we will always discard up to the dust relay fee and a discard fee above that is limited by the fee estimate for the longest target",
CURRENCY_UNIT, FormatMoney(DEFAULT_DISCARD_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
- argsman.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data. 0 to entirely disable the fallbackfee feature. (default: %s)",
+ argsman.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kvB) that will be used when fee estimation has insufficient data. 0 to entirely disable the fallbackfee feature. (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u). Warning: Smaller sizes may increase the risk of losing funds when restoring from an old backup, if none of the addresses in the original keypool have been used.", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-maxapsfee=<n>", strprintf("Spend up to this amount in additional (absolute) fees (in %s) if it allows the use of partial spend avoidance (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MAX_AVOIDPARTIALSPEND_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
- argsman.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)",
+ argsman.AddArg("-mintxfee=<amt>", strprintf("Fee rates (in %s/kvB) smaller than this are considered zero fee for transaction creation (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
- argsman.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)",
+ 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
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 6a59bc2b38..e0df96666f 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -39,7 +39,7 @@ bool VerifyWallets(interfaces::Chain& chain)
LogPrintf("Using wallet directory %s\n", GetWalletDir().string());
- chain.initMessage(_("Verifying wallet(s)...").translated);
+ chain.initMessage(_("Verifying wallet(s)…").translated);
// For backwards compatibility if an unnamed top level wallet exists in the
// wallets directory, include it in the default list of wallets to load.
@@ -105,7 +105,8 @@ bool LoadWallets(interfaces::Chain& chain)
if (!database && status == DatabaseStatus::FAILED_NOT_FOUND) {
continue;
}
- std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(chain, name, std::move(database), options.create_flags, error, warnings) : nullptr;
+ chain.initMessage(_("Loading wallet...").translated);
+ std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(&chain, name, std::move(database), options.create_flags, error, warnings) : nullptr;
if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
if (!pwallet) {
chain.initError(error);
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index a851765c57..726b13beac 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -557,7 +557,7 @@ RPCHelpMan importwallet()
// Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which
// we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button.
- pwallet->chain().showProgress(strprintf("%s " + _("Importing...").translated, pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI
+ pwallet->chain().showProgress(strprintf("%s " + _("Importing…").translated, pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI
std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys;
std::vector<std::pair<CScript, int64_t>> scripts;
while (file.good()) {
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index fb19064b1e..4e3c8ce49d 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -2293,10 +2293,10 @@ static RPCHelpMan listlockunspent()
static RPCHelpMan settxfee()
{
return RPCHelpMan{"settxfee",
- "\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n"
+ "\nSet the transaction fee rate in " + CURRENCY_UNIT + "/kvB for this wallet. Overrides the global -paytxfee command line parameter.\n"
"Can be deactivated by passing 0 as the fee. In that case automatic fee selection will be used by default.\n",
{
- {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee in " + CURRENCY_UNIT + "/kvB"},
+ {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee rate in " + CURRENCY_UNIT + "/kvB"},
},
RPCResult{
RPCResult::Type::BOOL, "", "Returns true if successful"
@@ -2970,8 +2970,9 @@ static RPCHelpMan listunspent()
cctl.m_avoid_address_reuse = false;
cctl.m_min_depth = nMinDepth;
cctl.m_max_depth = nMaxDepth;
+ cctl.m_include_unsafe_inputs = include_unsafe;
LOCK(pwallet->cs_wallet);
- pwallet->AvailableCoins(vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
+ pwallet->AvailableCoins(vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
}
LOCK(pwallet->cs_wallet);
@@ -3076,6 +3077,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
RPCTypeCheckObj(options,
{
{"add_inputs", UniValueType(UniValue::VBOOL)},
+ {"include_unsafe", UniValueType(UniValue::VBOOL)},
{"add_to_wallet", UniValueType(UniValue::VBOOL)},
{"changeAddress", UniValueType(UniValue::VSTR)},
{"change_address", UniValueType(UniValue::VSTR)},
@@ -3136,6 +3138,10 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
lockUnspents = (options.exists("lock_unspents") ? options["lock_unspents"] : options["lockUnspents"]).get_bool();
}
+ if (options.exists("include_unsafe")) {
+ coinControl.m_include_unsafe_inputs = options["include_unsafe"].get_bool();
+ }
+
if (options.exists("feeRate")) {
if (options.exists("fee_rate")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both fee_rate (" + CURRENCY_ATOM + "/vB) and feeRate (" + CURRENCY_UNIT + "/kvB)");
@@ -3206,6 +3212,9 @@ static RPCHelpMan fundrawtransaction()
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}",
{
{"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{true}, "For a transaction with existing inputs, automatically include more if they are not enough."},
+ {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n"
+ "Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n"
+ "If that happens, you will need to fund the transaction with different inputs and republish it."},
{"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"},
{"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
@@ -4031,6 +4040,9 @@ static RPCHelpMan send()
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
{
{"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false}, "If inputs are specified, automatically include more if they are not enough."},
+ {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n"
+ "Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n"
+ "If that happens, you will need to fund the transaction with different inputs and republish it."},
{"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns a serialized transaction which will not be added to the wallet or broadcast"},
{"change_address", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"},
{"change_position", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
@@ -4374,6 +4386,9 @@ static RPCHelpMan walletcreatefundedpsbt()
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
{
{"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false}, "If inputs are specified, automatically include more if they are not enough."},
+ {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n"
+ "Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n"
+ "If that happens, you will need to fund the transaction with different inputs and republish it."},
{"changeAddress", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"},
{"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp
index e245a277e4..2e60aca017 100644
--- a/src/wallet/sqlite.cpp
+++ b/src/wallet/sqlite.cpp
@@ -16,6 +16,7 @@
#include <sqlite3.h>
#include <stdint.h>
+#include <optional>
#include <utility>
#include <vector>
@@ -35,6 +36,36 @@ static void ErrorLogCallback(void* arg, int code, const char* msg)
LogPrintf("SQLite Error. Code: %d. Message: %s\n", code, msg);
}
+static std::optional<int> ReadPragmaInteger(sqlite3* db, const std::string& key, const std::string& description, bilingual_str& error)
+{
+ std::string stmt_text = strprintf("PRAGMA %s", key);
+ sqlite3_stmt* pragma_read_stmt{nullptr};
+ int ret = sqlite3_prepare_v2(db, stmt_text.c_str(), -1, &pragma_read_stmt, nullptr);
+ if (ret != SQLITE_OK) {
+ sqlite3_finalize(pragma_read_stmt);
+ error = Untranslated(strprintf("SQLiteDatabase: Failed to prepare the statement to fetch %s: %s", description, sqlite3_errstr(ret)));
+ return std::nullopt;
+ }
+ ret = sqlite3_step(pragma_read_stmt);
+ if (ret != SQLITE_ROW) {
+ sqlite3_finalize(pragma_read_stmt);
+ error = Untranslated(strprintf("SQLiteDatabase: Failed to fetch %s: %s", description, sqlite3_errstr(ret)));
+ return std::nullopt;
+ }
+ int result = sqlite3_column_int(pragma_read_stmt, 0);
+ sqlite3_finalize(pragma_read_stmt);
+ return result;
+}
+
+static void SetPragma(sqlite3* db, const std::string& key, const std::string& value, const std::string& err_msg)
+{
+ std::string stmt_text = strprintf("PRAGMA %s = %s", key, value);
+ int ret = sqlite3_exec(db, stmt_text.c_str(), nullptr, nullptr, nullptr);
+ if (ret != SQLITE_OK) {
+ throw std::runtime_error(strprintf("SQLiteDatabase: %s: %s\n", err_msg, sqlite3_errstr(ret)));
+ }
+}
+
SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock)
: WalletDatabase(), m_mock(mock), m_dir_path(dir_path.string()), m_file_path(file_path.string())
{
@@ -114,21 +145,9 @@ bool SQLiteDatabase::Verify(bilingual_str& error)
assert(m_db);
// Check the application ID matches our network magic
- sqlite3_stmt* app_id_stmt{nullptr};
- int ret = sqlite3_prepare_v2(m_db, "PRAGMA application_id", -1, &app_id_stmt, nullptr);
- if (ret != SQLITE_OK) {
- sqlite3_finalize(app_id_stmt);
- error = strprintf(_("SQLiteDatabase: Failed to prepare the statement to fetch the application id: %s"), sqlite3_errstr(ret));
- return false;
- }
- ret = sqlite3_step(app_id_stmt);
- if (ret != SQLITE_ROW) {
- sqlite3_finalize(app_id_stmt);
- error = strprintf(_("SQLiteDatabase: Failed to fetch the application id: %s"), sqlite3_errstr(ret));
- return false;
- }
- uint32_t app_id = static_cast<uint32_t>(sqlite3_column_int(app_id_stmt, 0));
- sqlite3_finalize(app_id_stmt);
+ auto read_result = ReadPragmaInteger(m_db, "application_id", "the application id", error);
+ if (!read_result.has_value()) return false;
+ uint32_t app_id = static_cast<uint32_t>(read_result.value());
uint32_t net_magic = ReadBE32(Params().MessageStart());
if (app_id != net_magic) {
error = strprintf(_("SQLiteDatabase: Unexpected application id. Expected %u, got %u"), net_magic, app_id);
@@ -136,28 +155,16 @@ bool SQLiteDatabase::Verify(bilingual_str& error)
}
// Check our schema version
- sqlite3_stmt* user_ver_stmt{nullptr};
- ret = sqlite3_prepare_v2(m_db, "PRAGMA user_version", -1, &user_ver_stmt, nullptr);
- if (ret != SQLITE_OK) {
- sqlite3_finalize(user_ver_stmt);
- error = strprintf(_("SQLiteDatabase: Failed to prepare the statement to fetch sqlite wallet schema version: %s"), sqlite3_errstr(ret));
- return false;
- }
- ret = sqlite3_step(user_ver_stmt);
- if (ret != SQLITE_ROW) {
- sqlite3_finalize(user_ver_stmt);
- error = strprintf(_("SQLiteDatabase: Failed to fetch sqlite wallet schema version: %s"), sqlite3_errstr(ret));
- return false;
- }
- int32_t user_ver = sqlite3_column_int(user_ver_stmt, 0);
- sqlite3_finalize(user_ver_stmt);
+ read_result = ReadPragmaInteger(m_db, "user_version", "sqlite wallet schema version", error);
+ if (!read_result.has_value()) return false;
+ int32_t user_ver = read_result.value();
if (user_ver != WALLET_SCHEMA_VERSION) {
error = strprintf(_("SQLiteDatabase: Unknown sqlite wallet schema version %d. Only version %d is supported"), user_ver, WALLET_SCHEMA_VERSION);
return false;
}
sqlite3_stmt* stmt{nullptr};
- ret = sqlite3_prepare_v2(m_db, "PRAGMA integrity_check", -1, &stmt, nullptr);
+ int ret = sqlite3_prepare_v2(m_db, "PRAGMA integrity_check", -1, &stmt, nullptr);
if (ret != SQLITE_OK) {
sqlite3_finalize(stmt);
error = strprintf(_("SQLiteDatabase: Failed to prepare statement to verify database: %s"), sqlite3_errstr(ret));
@@ -213,12 +220,9 @@ void SQLiteDatabase::Open()
// Acquire an exclusive lock on the database
// First change the locking mode to exclusive
- int ret = sqlite3_exec(m_db, "PRAGMA locking_mode = exclusive", nullptr, nullptr, nullptr);
- if (ret != SQLITE_OK) {
- throw std::runtime_error(strprintf("SQLiteDatabase: Unable to change database locking mode to exclusive: %s\n", sqlite3_errstr(ret)));
- }
+ SetPragma(m_db, "locking_mode", "exclusive", "Unable to change database locking mode to exclusive");
// Now begin a transaction to acquire the exclusive lock. This lock won't be released until we close because of the exclusive locking mode.
- ret = sqlite3_exec(m_db, "BEGIN EXCLUSIVE TRANSACTION", nullptr, nullptr, nullptr);
+ int ret = sqlite3_exec(m_db, "BEGIN EXCLUSIVE TRANSACTION", nullptr, nullptr, nullptr);
if (ret != SQLITE_OK) {
throw std::runtime_error("SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?\n");
}
@@ -228,18 +232,12 @@ void SQLiteDatabase::Open()
}
// Enable fullfsync for the platforms that use it
- ret = sqlite3_exec(m_db, "PRAGMA fullfsync = true", nullptr, nullptr, nullptr);
- if (ret != SQLITE_OK) {
- throw std::runtime_error(strprintf("SQLiteDatabase: Failed to enable fullfsync: %s\n", sqlite3_errstr(ret)));
- }
+ SetPragma(m_db, "fullfsync", "true", "Failed to enable fullfsync");
if (gArgs.GetBoolArg("-unsafesqlitesync", false)) {
// Use normal synchronous mode for the journal
LogPrintf("WARNING SQLite is configured to not wait for data to be flushed to disk. Data loss and corruption may occur.\n");
- ret = sqlite3_exec(m_db, "PRAGMA synchronous = OFF", nullptr, nullptr, nullptr);
- if (ret != SQLITE_OK) {
- throw std::runtime_error(strprintf("SQLiteDatabase: Failed to set synchronous mode to OFF: %s\n", sqlite3_errstr(ret)));
- }
+ SetPragma(m_db, "synchronous", "OFF", "Failed to set synchronous mode to OFF");
}
// Make the table for our key-value pairs
@@ -271,18 +269,12 @@ void SQLiteDatabase::Open()
// Set the application id
uint32_t app_id = ReadBE32(Params().MessageStart());
- std::string set_app_id = strprintf("PRAGMA application_id = %d", static_cast<int32_t>(app_id));
- ret = sqlite3_exec(m_db, set_app_id.c_str(), nullptr, nullptr, nullptr);
- if (ret != SQLITE_OK) {
- throw std::runtime_error(strprintf("SQLiteDatabase: Failed to set the application id: %s\n", sqlite3_errstr(ret)));
- }
+ SetPragma(m_db, "application_id", strprintf("%d", static_cast<int32_t>(app_id)),
+ "Failed to set the application id");
// Set the user version
- std::string set_user_ver = strprintf("PRAGMA user_version = %d", WALLET_SCHEMA_VERSION);
- ret = sqlite3_exec(m_db, set_user_ver.c_str(), nullptr, nullptr, nullptr);
- if (ret != SQLITE_OK) {
- throw std::runtime_error(strprintf("SQLiteDatabase: Failed to set the wallet schema version: %s\n", sqlite3_errstr(ret)));
- }
+ SetPragma(m_db, "user_version", strprintf("%d", WALLET_SCHEMA_VERSION),
+ "Failed to set the wallet schema version");
}
}
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 7eff6e592d..7bca385deb 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -297,8 +297,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
empty_wallet();
{
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
- bool firstRun;
- wallet->LoadWallet(firstRun);
+ wallet->LoadWallet();
wallet->SetupLegacyScriptPubKeyMan();
LOCK(wallet->cs_wallet);
add_coin(*wallet, 5 * CENT, 6 * 24, false, 0, true);
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index badf2eb459..fc744ebe5b 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -8,8 +8,7 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
: TestingSetup(chainName),
m_wallet(m_node.chain.get(), "", CreateMockWalletDatabase())
{
- bool fFirstRun;
- m_wallet.LoadWallet(fFirstRun);
+ m_wallet.LoadWallet();
m_chain_notifications_handler = m_node.chain->handleNotifications({ &m_wallet, [](CWallet*) {} });
m_wallet_client->registerRpcs();
}
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index c8e3c8f819..512374c34f 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -38,7 +38,7 @@ static_assert(WALLET_INCREMENTAL_RELAY_FEE >= DEFAULT_INCREMENTAL_RELAY_FEE, "wa
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
-static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain& chain)
+static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain* chain)
{
DatabaseOptions options;
DatabaseStatus status;
@@ -46,7 +46,9 @@ static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain& chain)
std::vector<bilingual_str> warnings;
auto database = MakeWalletDatabase("", options, status, error);
auto wallet = CWallet::Create(chain, "", std::move(database), options.create_flags, error, warnings);
- wallet->postInitProcess();
+ if (chain) {
+ wallet->postInitProcess();
+ }
return wallet;
}
@@ -483,8 +485,7 @@ public:
LOCK2(wallet->cs_wallet, ::cs_main);
wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
- bool firstRun;
- wallet->LoadWallet(firstRun);
+ wallet->LoadWallet();
AddKey(*wallet, coinbaseKey);
WalletRescanReserver reserver(*wallet);
reserver.reserve();
@@ -690,7 +691,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
{
gArgs.ForceSetArg("-unsafesqlitesync", "1");
// Create new wallet with known key and unload it.
- auto wallet = TestLoadWallet(*m_node.chain);
+ auto wallet = TestLoadWallet(m_node.chain.get());
CKey key;
key.MakeNewKey(true);
AddKey(*wallet, key);
@@ -730,7 +731,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
// Reload wallet and make sure new transactions are detected despite events
// being blocked
- wallet = TestLoadWallet(*m_node.chain);
+ wallet = TestLoadWallet(m_node.chain.get());
BOOST_CHECK(rescan_completed);
BOOST_CHECK_EQUAL(addtx_count, 2);
{
@@ -770,7 +771,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
ENTER_CRITICAL_SECTION(wallet->wallet()->cs_wallet);
ENTER_CRITICAL_SECTION(cs_wallets);
});
- wallet = TestLoadWallet(*m_node.chain);
+ wallet = TestLoadWallet(m_node.chain.get());
BOOST_CHECK_EQUAL(addtx_count, 4);
{
LOCK(wallet->cs_wallet);
@@ -782,10 +783,17 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
TestUnloadWallet(std::move(wallet));
}
+BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup)
+{
+ auto wallet = TestLoadWallet(nullptr);
+ BOOST_CHECK(wallet);
+ UnloadWallet(std::move(wallet));
+}
+
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
{
gArgs.ForceSetArg("-unsafesqlitesync", "1");
- auto wallet = TestLoadWallet(*m_node.chain);
+ auto wallet = TestLoadWallet(m_node.chain.get());
CKey key;
key.MakeNewKey(true);
AddKey(*wallet, key);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 3d0ef9f8c0..7cdf2fcda0 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -213,7 +213,8 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std:
return nullptr;
}
- std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), options.create_flags, error, warnings);
+ chain.initMessage(_("Loading wallet...").translated);
+ std::shared_ptr<CWallet> wallet = CWallet::Create(&chain, name, std::move(database), options.create_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_LOAD;
@@ -292,7 +293,8 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin
}
// Make the wallet
- std::shared_ptr<CWallet> wallet = CWallet::Create(chain, name, std::move(database), wallet_creation_flags, error, warnings);
+ chain.initMessage(_("Loading wallet...").translated);
+ std::shared_ptr<CWallet> wallet = CWallet::Create(&chain, name, std::move(database), wallet_creation_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_CREATE;
@@ -1784,7 +1786,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 on startup
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));
@@ -1799,7 +1801,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
m_scanning_progress = 0;
}
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))));
+ ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
}
if (GetTime() >= nNow + 60) {
nNow = GetTime();
@@ -1861,7 +1863,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
}
}
}
- ShowProgress(strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 100); // hide progress dialog in GUI
+ ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 100); // hide progress dialog in GUI
if (block_height && fAbortRescan) {
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current);
result.status = ScanResult::USER_ABORT;
@@ -2197,7 +2199,7 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
CAmount balance = 0;
std::vector<COutput> vCoins;
- AvailableCoins(vCoins, true, coinControl);
+ AvailableCoins(vCoins, coinControl);
for (const COutput& out : vCoins) {
if (out.fSpendable) {
balance += out.tx->tx->vout[out.i].nValue;
@@ -2206,7 +2208,7 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
return balance;
}
-void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const
+void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const
{
AssertLockHeld(cs_wallet);
@@ -2217,6 +2219,7 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe, const
bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse);
const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH};
const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
+ const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true};
std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet)
@@ -2273,7 +2276,7 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe, const
safeTx = false;
}
- if (fOnlySafe && !safeTx) {
+ if (only_safe && !safeTx) {
continue;
}
@@ -2516,8 +2519,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) return true;
// Fall back to using zero confirmation change (but with as few ancestors in the mempool as
- // possible) if we cannot fund the transaction otherwise. We never spend unconfirmed
- // outputs received from other wallets.
+ // possible) if we cannot fund the transaction otherwise.
if (m_spend_zero_conf_change) {
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) return true;
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)),
@@ -2535,6 +2537,14 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
return true;
}
+ // Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
+ // received from other wallets.
+ if (coin_control.m_include_unsafe_inputs
+ && SelectCoinsMinConf(value_to_select,
+ CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
+ vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
+ return true;
+ }
// Try with unlimited ancestors/descendants. The transaction will still need to meet
// mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but
// OutputGroups use heuristics that may overestimate ancestor/descendant counts.
@@ -2836,7 +2846,7 @@ bool CWallet::CreateTransactionInternal(
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight());
{
std::vector<COutput> vAvailableCoins;
- AvailableCoins(vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
+ AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
@@ -3239,11 +3249,10 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
}
}
-DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
+DBErrors CWallet::LoadWallet()
{
LOCK(cs_wallet);
- fFirstRunRet = false;
DBErrors nLoadWalletRet = WalletBatch(GetDatabase()).LoadWallet(this);
if (nLoadWalletRet == DBErrors::NEED_REWRITE)
{
@@ -3255,9 +3264,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
}
}
- // This wallet is in its first run if there are no ScriptPubKeyMans and it isn't blank or no privkeys
- fFirstRunRet = m_spk_managers.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
- if (fFirstRunRet) {
+ if (m_spk_managers.empty()) {
assert(m_external_spk_managers.empty());
assert(m_internal_spk_managers.empty());
}
@@ -3878,18 +3885,15 @@ std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, cons
return MakeDatabase(wallet_path, options, status, error_string);
}
-std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
+std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
const std::string& walletFile = database->Filename();
- chain.initMessage(_("Loading wallet...").translated);
-
int64_t nStart = GetTimeMillis();
- bool fFirstRun = true;
// 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);
- DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
+ std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, std::move(database)), ReleaseWallet);
+ DBErrors nLoadWalletRet = walletInstance->LoadWallet();
if (nLoadWalletRet != DBErrors::LOAD_OK) {
if (nLoadWalletRet == DBErrors::CORRUPT) {
error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile);
@@ -3916,6 +3920,10 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
}
}
+ // This wallet is in its first run if there are no ScriptPubKeyMans and it isn't blank or no privkeys
+ const bool fFirstRun = walletInstance->m_spk_managers.empty() &&
+ !walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) &&
+ !walletInstance->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
if (fFirstRun)
{
// ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
@@ -3944,7 +3952,9 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
}
}
- walletInstance->chainStateFlushed(chain.getTipLocator());
+ if (chain) {
+ walletInstance->chainStateFlushed(chain->getTipLocator());
+ }
} else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
// Make it impossible to disable private keys after creation
error = strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile);
@@ -4041,9 +4051,9 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
_("This is the transaction fee you will pay if you send a transaction."));
}
walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
- if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) {
+ if (chain && walletInstance->m_pay_tx_fee < chain->relayMinFee()) {
error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
- gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString());
+ gArgs.GetArg("-paytxfee", ""), chain->relayMinFee().ToString());
return nullptr;
}
}
@@ -4057,15 +4067,15 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
if (nMaxFee > HIGH_MAX_TX_FEE) {
warnings.push_back(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
}
- if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) {
+ if (chain && CFeeRate(nMaxFee, 1000) < chain->relayMinFee()) {
error = strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
- gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString());
+ gArgs.GetArg("-maxtxfee", ""), chain->relayMinFee().ToString());
return nullptr;
}
walletInstance->m_default_max_tx_fee = nMaxFee;
}
- if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
+ if (chain && chain->relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-minrelaytxfee") + Untranslated(" ") +
_("The wallet will avoid paying less than the minimum relay fee."));
}
@@ -4081,6 +4091,35 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
LOCK(walletInstance->cs_wallet);
+ if (chain && !AttachChain(walletInstance, *chain, error, warnings)) {
+ return nullptr;
+ }
+
+ {
+ LOCK(cs_wallets);
+ for (auto& load_wallet : g_load_wallet_fns) {
+ load_wallet(interfaces::MakeWallet(walletInstance));
+ }
+ }
+
+ walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
+
+ {
+ walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
+ walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
+ walletInstance->WalletLogPrintf("m_address_book.size() = %u\n", walletInstance->m_address_book.size());
+ }
+
+ return walletInstance;
+}
+
+bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interfaces::Chain& chain, 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
+ assert(!walletInstance->m_chain || walletInstance->m_chain == &chain);
+ walletInstance->m_chain = &chain;
+
// Register wallet with validationinterface. It's done before rescan to avoid
// missing block connections between end of rescan and validation subscribing.
// Because of wallet lock being hold, block connection notifications are going to
@@ -4114,25 +4153,25 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
if (tip_height && *tip_height != rescan_height)
{
- // We can't rescan beyond non-pruned blocks, stop and throw an error.
- // This might happen if a user uses an old wallet within a pruned node
- // or if they ran -disablewallet for a longer time, then decided to re-enable
if (chain.havePruned()) {
- // Exit early and print an error.
- // If a block is pruned after this check, we will load the wallet,
- // but fail the rescan with a generic error.
int block_height = *tip_height;
while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) {
--block_height;
}
if (rescan_height != block_height) {
+ // We can't rescan beyond non-pruned blocks, stop and throw an error.
+ // This might happen if a user uses an old wallet within a pruned node
+ // or if they ran -disablewallet for a longer time, then decided to re-enable
+ // Exit early and print an error.
+ // If a block is pruned after this check, we will load the wallet,
+ // but fail the rescan with a generic error.
error = _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)");
- return nullptr;
+ return false;
}
}
- chain.initMessage(_("Rescanning...").translated);
+ chain.initMessage(_("Rescanning…").translated);
walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height);
// No need to read and scan block if block was created before
@@ -4150,29 +4189,14 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
WalletRescanReserver reserver(*walletInstance);
if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) {
error = _("Failed to rescan the wallet during initialization");
- return nullptr;
+ return false;
}
}
walletInstance->chainStateFlushed(chain.getTipLocator());
walletInstance->GetDatabase().IncrementUpdateCounter();
}
- {
- LOCK(cs_wallets);
- for (auto& load_wallet : g_load_wallet_fns) {
- load_wallet(interfaces::MakeWallet(walletInstance));
- }
- }
-
- walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
-
- {
- walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
- walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
- walletInstance->WalletLogPrintf("m_address_book.size() = %u\n", walletInstance->m_address_book.size());
- }
-
- return walletInstance;
+ return true;
}
const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest, bool allow_change) const
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 03adca7a89..fc4edd8d20 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -763,6 +763,13 @@ private:
bool CreateTransactionInternal(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign);
+ /**
+ * Catch wallet up to current chain, scanning new blocks, updating the best
+ * 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);
+
public:
/**
* Main wallet lock.
@@ -850,7 +857,7 @@ public:
/**
* populate vCoins with vector of available COutputs.
*/
- void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe = true, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Return list of available coins and locked coins grouped by non-change output address.
@@ -1126,7 +1133,7 @@ public:
CAmount GetChange(const CTransaction& tx) const;
void chainStateFlushed(const CBlockLocator& loc) override;
- DBErrors LoadWallet(bool& fFirstRunRet);
+ DBErrors LoadWallet();
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose);
@@ -1202,7 +1209,7 @@ public:
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
- static std::shared_ptr<CWallet> Create(interfaces::Chain& chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings);
+ static std::shared_ptr<CWallet> Create(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings);
/**
* Wallet post-init setup
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index b2cb0bf479..50b6c9d29f 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -54,8 +54,7 @@ static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::pa
std::shared_ptr<CWallet> wallet_instance{new CWallet(nullptr /* chain */, name, std::move(database)), WalletToolReleaseWallet};
DBErrors load_wallet_ret;
try {
- bool first_run;
- load_wallet_ret = wallet_instance->LoadWallet(first_run);
+ load_wallet_ret = wallet_instance->LoadWallet();
} catch (const std::runtime_error&) {
tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name);
return nullptr;