diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4ab688c672..755d40a4c9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -251,6 +251,13 @@ linux64_cxx20-build: variables: BUILD_TARGET: linux64_cxx20 +linux64_sqlite-build: + extends: .build-template + needs: + - x86_64-pc-linux-gnu-debug + variables: + BUILD_TARGET: linux64_sqlite + linux64_fuzz-build: extends: .build-template needs: @@ -312,6 +319,13 @@ linux64-test: variables: BUILD_TARGET: linux64 +linux64_sqlite-test: + extends: .test-template + needs: + - linux64_sqlite-build + variables: + BUILD_TARGET: linux64_sqlite + linux64_tsan-test: extends: - .test-template diff --git a/ci/dash/matrix.sh b/ci/dash/matrix.sh index 21028410a4..6b21a0f5d8 100755 --- a/ci/dash/matrix.sh +++ b/ci/dash/matrix.sh @@ -30,6 +30,8 @@ elif [ "$BUILD_TARGET" = "linux64_fuzz" ]; then source ./ci/test/00_setup_env_native_fuzz.sh elif [ "$BUILD_TARGET" = "linux64_cxx20" ]; then source ./ci/test/00_setup_env_native_cxx20.sh +elif [ "$BUILD_TARGET" = "linux64_sqlite" ]; then + source ./ci/test/00_setup_env_native_sqlite.sh elif [ "$BUILD_TARGET" = "linux64_nowallet" ]; then source ./ci/test/00_setup_env_native_nowallet.sh elif [ "$BUILD_TARGET" = "mac" ]; then diff --git a/ci/test/00_setup_env_native_sqlite.sh b/ci/test/00_setup_env_native_sqlite.sh new file mode 100644 index 0000000000..e97fd0ebb9 --- /dev/null +++ b/ci/test/00_setup_env_native_sqlite.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C.UTF-8 + +export PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libdbus-1-dev libharfbuzz-dev" +export DEP_OPTS="NO_BDB=1 NO_UPNP=1 DEBUG=1" +export GOAL="install" +export BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports --with-sqlite --without-bdb LDFLAGS=-static-libstdc++" diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index f8632e856b..9e30cb7a5c 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -40,7 +40,6 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const "-rescan=", "-salvagewallet", "-spendzeroconfchange", - "-upgradewallet", "-wallet=", "-walletbackupsdir=", "-walletbroadcast", diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 288c40783a..d584d28fa8 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -1465,36 +1465,13 @@ - - - - 180 - 23 - - - - Upgrade wallet format - - - - - - - -upgradewallet: Upgrade wallet to latest format on startup. (Note: this is NOT an update of the wallet itself!) - - - true - - - - Rebuild index - + -reindex: Rebuild block chain index from current blk000??.dat files. @@ -1504,7 +1481,7 @@ - + Qt::Vertical diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index bebaf29e66..ffd3458529 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -60,7 +60,6 @@ const TrafficGraphData::GraphRange INITIAL_TRAFFIC_GRAPH_SETTING = TrafficGraphD // Repair parameters const QString RESCAN1("-rescan=1"); const QString RESCAN2("-rescan=2"); -const QString UPGRADEWALLET("-upgradewallet"); const QString REINDEX("-reindex"); namespace { @@ -496,10 +495,8 @@ RPCConsole::RPCConsole(interfaces::Node& node, QWidget* parent, Qt::WindowFlags // Disable wallet repair options that require a wallet (enable them later when a wallet is added) ui->btn_rescan1->setEnabled(false); ui->btn_rescan2->setEnabled(false); - ui->btn_upgradewallet->setEnabled(false); connect(ui->btn_rescan1, &QPushButton::clicked, this, &RPCConsole::walletRescan1); connect(ui->btn_rescan2, &QPushButton::clicked, this, &RPCConsole::walletRescan2); - connect(ui->btn_upgradewallet, &QPushButton::clicked, this, &RPCConsole::walletUpgrade); connect(ui->btn_reindex, &QPushButton::clicked, this, &RPCConsole::walletReindex); // Register RPC timer interface @@ -726,7 +723,6 @@ void RPCConsole::addWallet(WalletModel * const walletModel) // The only loaded wallet ui->btn_rescan1->setEnabled(true); ui->btn_rescan2->setEnabled(true); - ui->btn_upgradewallet->setEnabled(true); QString wallet_path = QString::fromStdString(GetWalletDir().string() + QDir::separator().toLatin1()); QString wallet_name = walletModel->getWalletName().isEmpty() ? "wallet.dat" : walletModel->getWalletName(); ui->wallet_path->setText(wallet_path + wallet_name); @@ -736,7 +732,6 @@ void RPCConsole::addWallet(WalletModel * const walletModel) // No wallet recovery for multiple loaded wallets ui->btn_rescan1->setEnabled(false); ui->btn_rescan2->setEnabled(false); - ui->btn_upgradewallet->setEnabled(false); ui->wallet_path->clear(); } } @@ -750,7 +745,6 @@ void RPCConsole::removeWallet(WalletModel * const walletModel) // Back to the only loaded wallet ui->btn_rescan1->setEnabled(true); ui->btn_rescan2->setEnabled(true); - ui->btn_upgradewallet->setEnabled(true); WalletModel* wallet_model = ui->WalletSelector->itemData(1).value(); QString wallet_path = QString::fromStdString(GetWalletDir().string() + QDir::separator().toLatin1()); QString wallet_name = wallet_model->getWalletName().isEmpty() ? "wallet.dat" : wallet_model->getWalletName(); @@ -759,7 +753,6 @@ void RPCConsole::removeWallet(WalletModel * const walletModel) // No wallet recovery for multiple loaded wallets ui->btn_rescan1->setEnabled(false); ui->btn_rescan2->setEnabled(false); - ui->btn_upgradewallet->setEnabled(false); ui->wallet_path->clear(); } } @@ -821,12 +814,6 @@ void RPCConsole::walletRescan2() buildParameterlist(RESCAN2); } -/** Restart wallet with "-upgradewallet" */ -void RPCConsole::walletUpgrade() -{ - buildParameterlist(UPGRADEWALLET); -} - /** Restart wallet with "-reindex" */ void RPCConsole::walletReindex() { @@ -852,7 +839,6 @@ void RPCConsole::buildParameterlist(QString arg) // Remove existing repair-options args.removeAll(RESCAN1); args.removeAll(RESCAN2); - args.removeAll(UPGRADEWALLET); args.removeAll(REINDEX); // Append repair parameter to command line. diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 0b1bdec604..76456236db 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -108,7 +108,6 @@ public Q_SLOTS: /** Wallet repair options */ void walletRescan1(); void walletRescan2(); - void walletUpgrade(); void walletReindex(); /** Append the message to the message widget */ diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index fc36fd1dcf..4fb93be805 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -182,6 +182,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getspecialtxes", 3, "skip" }, { "getspecialtxes", 4, "verbosity" }, { "disconnectnode", 1, "nodeid" }, + { "upgradewallet", 0, "version" }, // Echo with conversion (For testing only) { "echojson", 0, "arg0" }, { "echojson", 1, "arg1" }, diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 71de74d92b..686d867dfb 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -62,7 +62,6 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const argsman.AddArg("-rescan=", "Rescan the block chain for missing wallet transactions on startup" " (1 = start from wallet creation time, 2 = start from genesis block)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); - argsman.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-wallet=", "Specify wallet path to load at startup. Can be used multiple times to load multiple wallets. Path is to a directory containing wallet data and log files. If the path is not absolute, it is interpreted relative to . This only loads existing wallets and does not create new ones. For backwards compatibility this also accepts names of existing top-level data files in .", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); argsman.AddArg("-walletbackupsdir=", "Specify full path to directory for automatic wallet backups (must exist)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); @@ -129,8 +128,6 @@ bool WalletInit::ParameterInteraction() const return InitError(_("You can not start a masternode with wallet enabled.")); } - const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1; - if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) { LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__); } @@ -146,12 +143,6 @@ bool WalletInit::ParameterInteraction() const gArgs.ForceRemoveArg("rescan"); } - if (is_multiwallet) { - if (gArgs.GetBoolArg("-upgradewallet", false)) { - return InitError(strprintf(_("%s is only allowed with a single wallet file"), "-upgradewallet")); - } - } - if (gArgs.GetBoolArg("-sysperms", false)) return InitError(Untranslated("-sysperms is not allowed in combination with enabled wallet functionality")); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index be1075faa2..5118f7401f 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2748,7 +2748,7 @@ static UniValue loadwallet(const JSONRPCRequest& request) RPCHelpMan{"loadwallet", "\nLoads a wallet from a wallet file or directory." "\nNote that all wallet command-line options used when starting dashd will be" - "\napplied to the new wallet (eg -upgradewallet, rescan, etc).\n", + "\napplied to the new wallet (eg, rescan, etc).\n", { {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."}, {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."}, @@ -4064,6 +4064,42 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) return result; } +static UniValue upgradewallet(const JSONRPCRequest& request) +{ + RPCHelpMan{"upgradewallet", + "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified\n" + "New keys may be generated and a new wallet backup will need to be made.", + { + {"version", RPCArg::Type::NUM, /* default */ strprintf("%d", FEATURE_LATEST), "The version number to upgrade to. Default is the latest wallet version"} + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("upgradewallet", "120200") + + HelpExampleRpc("upgradewallet", "120200") + } + }.Check(request); + + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + if (!wallet) return NullUniValue; + CWallet* const pwallet = wallet.get(); + + RPCTypeCheck(request.params, {UniValue::VNUM}, true); + + EnsureWalletIsUnlocked(pwallet); + + int version = 0; + if (!request.params[0].isNull()) { + version = request.params[0].get_int(); + } + + bilingual_str error; + std::vector warnings; + if (!pwallet->UpgradeWallet(version, error, warnings)) { + throw JSONRPCError(RPC_WALLET_ERROR, error.original); + } + return error.original; +} + // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames @@ -4123,6 +4159,7 @@ static const CRPCCommand commands[] = { "wallet", "signmessage", &signmessage, {"address","message"} }, { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} }, { "wallet", "unloadwallet", &unloadwallet, {"wallet_name", "load_on_startup"} }, + { "wallet", "upgradewallet", &upgradewallet, {"version"} }, { "wallet", "upgradetohd", &upgradetohd, {"mnemonic", "mnemonicpassphrase", "walletpassphrase", "rescan"} }, { "wallet", "walletlock", &walletlock, {} }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a58f57d36d..1d31658637 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4306,27 +4306,9 @@ std::shared_ptr CWallet::Create(interfaces::Chain& chain, const std::st } } - if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) - { - int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); - auto nMinVersion = DEFAULT_USE_HD_WALLET ? FEATURE_LATEST : FEATURE_COMPRPUBKEY; - if (nMaxVersion == 0) // the -upgradewallet without argument case - { - walletInstance->WalletLogPrintf("Performing wallet upgrade to %i\n", nMinVersion); - nMaxVersion = FEATURE_LATEST; - walletInstance->SetMinVersion(nMinVersion); // permanently upgrade the wallet immediately - } - else - walletInstance->WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); - if (nMaxVersion < walletInstance->GetVersion()) - { - return unload_wallet(_("Cannot downgrade wallet")); - } - walletInstance->SetMaxVersion(nMaxVersion); - } - if (fFirstRun) { + walletInstance->SetMaxVersion(FEATURE_LATEST); walletInstance->SetWalletFlags(wallet_creation_flags, false); if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) { // Create new HD chain @@ -4596,6 +4578,27 @@ std::shared_ptr CWallet::Create(interfaces::Chain& chain, const std::st return walletInstance; } +bool CWallet::UpgradeWallet(int version, bilingual_str& error, std::vector& warnings) +{ + int nMaxVersion = version; + auto nMinVersion = DEFAULT_USE_HD_WALLET ? FEATURE_LATEST : FEATURE_COMPRPUBKEY; + if (nMaxVersion == 0) { + WalletLogPrintf("Performing wallet upgrade to %i\n", nMinVersion); + nMaxVersion = FEATURE_LATEST; + SetMinVersion(nMinVersion); // permanently upgrade the wallet immediately + } else { + WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); + } + + if (nMaxVersion < GetVersion()) { + error = Untranslated("Cannot downgrade wallet"); + return false; + } + + SetMaxVersion(nMaxVersion); + return true; +} + void CWallet::postInitProcess() { LOCK(cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 2b7b5dfe59..6848c5cc57 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1241,6 +1241,9 @@ public: LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; + /** Upgrade the wallet */ + bool UpgradeWallet(int version, bilingual_str& error, std::vector& warnings); + /** Get last block processed height */ int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { diff --git a/test/config.ini.in b/test/config.ini.in index be1bfe8752..77c9a720c3 100644 --- a/test/config.ini.in +++ b/test/config.ini.in @@ -16,6 +16,8 @@ RPCAUTH=@abs_top_srcdir@/share/rpcauth/rpcauth.py [components] # Which components are enabled. These are commented out by `configure` if they were disabled when running config. @ENABLE_WALLET_TRUE@ENABLE_WALLET=true +@USE_SQLITE_TRUE@USE_SQLITE=true +@USE_BDB_TRUE@USE_BDB=true @BUILD_BITCOIN_CLI_TRUE@ENABLE_CLI=true @BUILD_BITCOIN_WALLET_TRUE@ENABLE_WALLET_TOOL=true @BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=true diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py index 2844a635a9..e90fe4087b 100755 --- a/test/functional/feature_filelock.py +++ b/test/functional/feature_filelock.py @@ -4,6 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Check that it's not possible to start a second bitcoind instance using the same datadir or wallet.""" import os +import random +import string from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch @@ -27,11 +29,14 @@ class FilelockTest(BitcoinTestFramework): self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg) if self.is_wallet_compiled(): - self.nodes[0].createwallet(self.default_wallet_name) + wallet_name = ''.join([random.choice(string.ascii_lowercase) for _ in range(6)]) + self.nodes[0].createwallet(wallet_name=wallet_name) wallet_dir = os.path.join(datadir, 'wallets') self.log.info("Check that we can't start a second dashd instance using the same wallet") - expected_msg = "Error: Error initializing wallet database environment" - self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + self.default_wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX) + expected_msg = "Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another dashd?" + if self.is_bdb_compiled(): + expected_msg = "Error: Error initializing wallet database environment" + self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-wallet=' + wallet_name, '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX) if __name__ == '__main__': FilelockTest().main() diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index 3265ab97e2..af6416acb8 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -48,6 +48,12 @@ class NotificationsTest(BitcoinTestFramework): super().setup_network() def run_test(self): + if self.is_wallet_compiled(): + # Make the wallets + # Ensures that node 0 and node 1 share the same wallet for the conflicting transaction tests below. + for i, name in enumerate(self.wallet_names): + self.nodes[i].createwallet(wallet_name=name, load_on_startup=True) + self.log.info("test -blocknotify") block_count = 10 blocks = self.nodes[1].generatetoaddress(block_count, self.nodes[1].getnewaddress() if self.is_wallet_compiled() else ADDRESS_BCRT1_UNSPENDABLE) diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 9283143717..101115d690 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -23,6 +23,7 @@ class TestBitcoinCli(BitcoinTestFramework): self.log.info("Compare responses from getwalletinfo RPC and `dash-cli getwalletinfo`") if self.is_wallet_compiled(): + self.nodes[0].createwallet(self.default_wallet_name) cli_response = self.nodes[0].cli.getwalletinfo() rpc_response = self.nodes[0].getwalletinfo() assert_equal(cli_response, rpc_response) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index cf4826b754..8019bba959 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -120,7 +120,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.supports_cli = True self.bind_to_localhost_only = True self.parse_args() - self.default_wallet_name = "" + self.default_wallet_name = "default_wallet" if self.options.is_sqlite_only else "" self.wallet_data_filename = "wallet.dat" self.extra_args_from_options = [] # Optional list of wallet names that can be set in set_test_params to @@ -129,6 +129,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # skipped. If list is truncated, wallet creation is skipped and keys # are not imported. self.wallet_names = None + # By default the wallet is not required. Set to true by skip_if_no_wallet(). + # When False, we ignore wallet_names regardless of what it is. + self.requires_wallet = False self.set_test_params() assert self.wallet_names is None or len(self.wallet_names) <= self.num_nodes if self.options.timeout_factor == 0 : @@ -203,12 +206,21 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): parser.add_argument("--randomseed", type=int, help="set a random seed for deterministically reproducing a previous test run") parser.add_argument('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts') + self.add_options(parser) + self.options = parser.parse_args() + + config = configparser.ConfigParser() + config.read_file(open(self.options.configfile)) + self.config = config + + # Passthrough SQLite-only availability check output as option + self.options.is_sqlite_only = self.is_sqlite_compiled() and not self.is_bdb_compiled() + # Running TestShell in a Jupyter notebook causes an additional -f argument # To keep TestShell from failing with an "unrecognized argument" error, we add a dummy "-f" argument # source: https://stackoverflow.com/questions/48796169/how-to-fix-ipykernel-launcher-py-error-unrecognized-arguments-in-jupyter/56349168#56349168 parser.add_argument("-f", "--fff", help="a dummy argument to fool ipython", default="1") - self.options = parser.parse_args() def setup(self): """Call this method to start up the test framework object with options set.""" @@ -224,9 +236,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.options.cachedir = os.path.abspath(self.options.cachedir) - config = configparser.ConfigParser() - config.read_file(open(self.options.configfile)) - self.config = config + config = self.config + self.options.bitcoind = os.getenv("BITCOIND", default=config["environment"]["BUILDDIR"] + '/src/dashd' + config["environment"]["EXEEXT"]) self.options.bitcoincli = os.getenv("BITCOINCLI", default=config["environment"]["BUILDDIR"] + '/src/dash-cli' + config["environment"]["EXEEXT"]) @@ -398,7 +409,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): extra_args = self.extra_args self.add_nodes(self.num_nodes, extra_args) self.start_nodes() - if self.is_wallet_compiled(): + if self.requires_wallet: self.import_deterministic_coinbase_privkeys() if not self.setup_clean_chain: for n in self.nodes: @@ -866,9 +877,20 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def skip_if_no_wallet(self): """Skip the running test if wallet has not been compiled.""" + self.requires_wallet = True if not self.is_wallet_compiled(): raise SkipTest("wallet has not been compiled.") + def skip_if_no_sqlite(self): + """Skip the running test if sqlite has not been compiled.""" + if not self.is_sqlite_compiled(): + raise SkipTest("sqlite has not been compiled.") + + def skip_if_no_bdb(self): + """Skip the running test if BDB has not been compiled.""" + if not self.is_bdb_compiled(): + raise SkipTest("BDB has not been compiled.") + def skip_if_no_wallet_tool(self): """Skip the running test if dash-wallet has not been compiled.""" if not self.is_wallet_tool_compiled(): @@ -895,6 +917,13 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): """Checks whether the zmq module was compiled.""" return self.config["components"].getboolean("ENABLE_ZMQ") + def is_sqlite_compiled(self): + """Checks whether the wallet module was compiled with Sqlite support.""" + return self.config["components"].getboolean("USE_SQLITE") + + def is_bdb_compiled(self): + """Checks whether the wallet module was compiled with BDB support.""" + return self.config["components"].getboolean("USE_BDB") MASTERNODE_COLLATERAL = 1000 HIGHPERFORMANCE_MASTERNODE_COLLATERAL = 4000 diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 9fc0b097cc..86ec0305b0 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -70,8 +70,11 @@ class ToolWalletTest(BitcoinTestFramework): self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create') self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo') locked_dir = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets") + error = "SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another dashd?" + if self.is_bdb_compiled(): + error = 'Error initializing wallet database environment "{}"!'.format(locked_dir) self.assert_raises_tool_error( - 'Error initializing wallet database environment "{}"!'.format(locked_dir), + error, '-wallet=' + self.default_wallet_name, 'info', ) @@ -103,7 +106,7 @@ class ToolWalletTest(BitcoinTestFramework): Transactions: 0 Address Book: 1 ''') - self.assert_tool_output(out, '-wallet=wallet.dat', 'info') + self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info') self.start_node(0) self.nodes[0].upgradetohd() @@ -233,7 +236,8 @@ class ToolWalletTest(BitcoinTestFramework): self.test_tool_wallet_info_after_transaction() self.test_tool_wallet_create_on_existing_wallet() self.test_getwalletinfo_on_different_wallet() - self.test_salvage() + if self.is_bdb_compiled(): + self.test_salvage() if __name__ == '__main__': ToolWalletTest().main() diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py index 9ce13ecf09..37a839839f 100755 --- a/test/functional/wallet_dump.py +++ b/test/functional/wallet_dump.py @@ -190,9 +190,10 @@ class WalletDumpTest(BitcoinTestFramework): result = self.nodes[0].getaddressinfo(multisig_addr) assert result['ismine'] == True - self.log.info('Check that wallet is flushed') - with self.nodes[0].assert_debug_log(['Flushing wallet.dat'], timeout=20): - self.nodes[0].getnewaddress() + if self.is_bdb_compiled(): + self.log.info('Check that wallet is flushed') + with self.nodes[0].assert_debug_log(['Flushing wallet.dat'], timeout=20): + self.nodes[0].getnewaddress() if __name__ == '__main__': diff --git a/test/functional/wallet_fallbackfee.py b/test/functional/wallet_fallbackfee.py index 1edb400bea..b06f71020c 100755 --- a/test/functional/wallet_fallbackfee.py +++ b/test/functional/wallet_fallbackfee.py @@ -11,6 +11,9 @@ class WalletRBFTest(BitcoinTestFramework): self.num_nodes = 1 self.setup_clean_chain = True + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + def run_test(self): self.nodes[0].generate(101) diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 23faa21d67..ca4402d855 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -29,7 +29,7 @@ class WalletHDTest(BitcoinTestFramework): def run_test(self): # Make sure can't switch off usehd after wallet creation self.stop_node(1) - self.nodes[1].assert_start_raises_init_error(['-usehd=0'], "Error: Error loading : You can't disable HD on an already existing HD wallet") + self.nodes[1].assert_start_raises_init_error(['-usehd=0'], "Error: Error loading %s: You can't disable HD on an already existing HD wallet" % self.default_wallet_name) self.start_node(1) self.connect_nodes(0, 1) diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index b3d1044081..9ff85945a5 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -24,6 +24,7 @@ from test_framework.util import ( FEATURE_LATEST = 120200 got_loading_error = False + def test_load_unload(node, name): global got_loading_error for i in range(10): @@ -37,7 +38,6 @@ def test_load_unload(node, name): got_loading_error = True return - class MultiWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -98,23 +98,32 @@ class MultiWalletTest(BitcoinTestFramework): # sub/w5 - to verify relative wallet path is created correctly # extern/w6 - to verify absolute wallet path is created correctly # w7_symlink - to verify symlinked wallet path is initialized correctly - # w8 - to verify existing wallet file is loaded correctly + # w8 - to verify existing wallet file is loaded correctly. Not tested for SQLite wallets as this is a deprecated BDB behavior. # '' - to verify default wallet file is created correctly - wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', self.default_wallet_name] + to_create = ['w1', 'w2', 'w3', 'w', 'sub/w5', 'w7_symlink'] + in_wallet_dir = to_create.copy() # Wallets in the wallet dir + in_wallet_dir.append('w7') # w7 is not loaded or created, but will be listed by listwalletdir because w7_symlink + to_create.append(os.path.join(self.options.tmpdir, 'extern/w6')) # External, not in the wallet dir, so we need to avoid adding it to in_wallet_dir + to_load = [self.default_wallet_name] + if not self.options.is_sqlite_only: + to_load.append('w8') + wallet_names = to_create + to_load # Wallet names loaded in the wallet + in_wallet_dir += to_load # The loaded wallets are also in the wallet dir if os.name == 'nt': wallet_names.remove('w7_symlink') self.start_node(0) - for wallet_name in wallet_names[:-2]: + for wallet_name in to_create: self.nodes[0].createwallet(wallet_name) - for wallet_name in wallet_names[-2:]: + for wallet_name in to_load: self.nodes[0].loadwallet(wallet_name) - assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8']) + assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir)) assert_equal(set(node.listwallets()), set(wallet_names)) # should raise rpc error if wallet path can't be created - assert_raises_rpc_error(-1, "boost::filesystem::create_directory:", self.nodes[0].createwallet, "w8/bad") + err_code = -4 if self.options.is_sqlite_only else -1 + assert_raises_rpc_error(err_code, "boost::filesystem::create_directory:", self.nodes[0].createwallet, "w8/bad") # check that all requested wallets were created self.stop_node(0) @@ -128,10 +137,13 @@ class MultiWalletTest(BitcoinTestFramework): self.start_node(0, ['-wallet=w1', '-wallet=w1']) self.stop_node(0, 'Warning: Ignoring duplicate -wallet w1.') - # should not initialize if one wallet is a copy of another - shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy')) - exp_stderr = r"Can't open database w8_copy \(duplicates fileid \w+ from w8\)" - self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) + if not self.options.is_sqlite_only: + # Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary + # should not initialize if one wallet is a copy of another + shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy')) + in_wallet_dir.append('w8_copy') + exp_stderr = r"Can't open database w8_copy \(duplicates fileid \w+ from w8\)" + self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) # should not initialize if wallet file is a symlink if os.name != 'nt': @@ -145,10 +157,6 @@ class MultiWalletTest(BitcoinTestFramework): open(not_a_dir, 'a', encoding="utf8").close() self.nodes[0].assert_start_raises_init_error(['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory') - self.log.info("Do not allow -upgradewallet with multiwallet") - self.nodes[0].assert_start_raises_init_error(['-upgradewallet', '-wallet=w1', '-wallet=w2'], "Error: -upgradewallet is only allowed with a single wallet file") - self.nodes[0].assert_start_raises_init_error(['-upgradewallet=1', '-wallet=w1', '-wallet=w2'], "Error: -upgradewallet is only allowed with a single wallet file") - # if wallets/ doesn't exist, datadir should be the default wallet dir wallet_dir2 = data_dir('walletdir') os.rename(wallet_dir(), wallet_dir2) @@ -173,14 +181,17 @@ class MultiWalletTest(BitcoinTestFramework): os.mkdir(competing_wallet_dir) self.restart_node(0, ['-nowallet', '-walletdir=' + competing_wallet_dir]) self.nodes[0].createwallet(self.default_wallet_name) - exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\"!" + if self.options.is_sqlite_only: + exp_stderr = r"Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another dashd?" + else: + exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\S*\"!" self.nodes[1].assert_start_raises_init_error(['-walletdir=' + competing_wallet_dir], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) self.restart_node(0) for wallet_name in wallet_names: self.nodes[0].loadwallet(wallet_name) - assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy']) + assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir)) wallets = [wallet(w) for w in wallet_names] wallet_bad = wallet("bad") @@ -271,18 +282,22 @@ class MultiWalletTest(BitcoinTestFramework): # Fail to load duplicate wallets path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "w1", self.wallet_data_filename) - assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0]) + if self.options.is_sqlite_only: + assert_raises_rpc_error(-4, "Wallet file verification failed. SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another dashd?", self.nodes[0].loadwallet, wallet_names[0]) + else: + assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0]) - # Fail to load duplicate wallets by different ways (directory and filepath) - path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", self.wallet_data_filename) - assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, self.wallet_data_filename) + # This tests the default wallet that BDB makes, so SQLite wallet doesn't need to test this + # Fail to load duplicate wallets by different ways (directory and filepath) + path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", self.wallet_data_filename) + assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, self.wallet_data_filename) - # Fail to load if one wallet is a copy of another - assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') - - # Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304 - assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') + # Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary + # Fail to load if one wallet is a copy of another + assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') + # Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304 + assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') # Fail to load if wallet file is a symlink if os.name != 'nt': @@ -296,6 +311,7 @@ class MultiWalletTest(BitcoinTestFramework): # Successfully create a wallet with a new name loadwallet_name = self.nodes[0].createwallet('w9') + in_wallet_dir.append('w9') assert_equal(loadwallet_name['name'], 'w9') w9 = node.get_wallet_rpc('w9') assert_equal(w9.getwalletinfo()['walletname'], 'w9') @@ -348,7 +364,7 @@ class MultiWalletTest(BitcoinTestFramework): assert_equal(self.nodes[0].listwallets(), ['w1']) assert_equal(w1.getwalletinfo()['walletname'], 'w1') - assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy', 'w9']) + assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir)) # Test backing up and restoring wallets self.log.info("Test wallet backup") @@ -373,22 +389,13 @@ class MultiWalletTest(BitcoinTestFramework): self.start_node(1) wallet = os.path.join(self.options.tmpdir, 'my_wallet') self.nodes[0].createwallet(wallet) - assert_raises_rpc_error(-4, "Error initializing wallet database environment", self.nodes[1].loadwallet, wallet) + if self.options.is_sqlite_only: + exp_stderr = "SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another dashd?" + else: + exp_stderr = "Error initializing wallet database environment" + assert_raises_rpc_error(-4, exp_stderr, self.nodes[1].loadwallet, wallet) self.nodes[0].unloadwallet(wallet) self.nodes[1].loadwallet(wallet) - # Fail to load if wallet is downgraded - shutil.copytree(os.path.join(self.options.data_wallets_dir, 'high_minversion'), wallet_dir('high_minversion')) - self.restart_node(0, extra_args=['-upgradewallet={}'.format(FEATURE_LATEST)]) - assert {'name': 'high_minversion'} in self.nodes[0].listwalletdir()['wallets'] - self.log.info("Fail -upgradewallet that results in downgrade") - assert_raises_rpc_error( - -4, - 'Wallet loading failed. Error loading {}: Wallet requires newer version of {}'.format( - wallet_dir('high_minversion', 'wallet.dat'), "Dash Core"), - lambda: self.nodes[0].loadwallet(filename='high_minversion'), - ) - - if __name__ == '__main__': MultiWalletTest().main() diff --git a/test/functional/wallet_upgradetohd.py b/test/functional/wallet_upgradetohd.py index f3d8ba72e3..12153b940f 100755 --- a/test/functional/wallet_upgradetohd.py +++ b/test/functional/wallet_upgradetohd.py @@ -34,7 +34,7 @@ class WalletUpgradeToHDTest(BitcoinTestFramework): self.log.info("Recover non-HD wallet to check different upgrade paths") node = self.nodes[0] self.stop_node(0) - shutil.copyfile(os.path.join(node.datadir, "non_hd.bak"), os.path.join(node.datadir, "regtest", "wallet.dat")) + shutil.copyfile(os.path.join(node.datadir, "non_hd.bak"), os.path.join(node.datadir, self.chain, self.default_wallet_name, self.wallet_data_filename)) self.start_node(0) assert 'hdchainid' not in node.getwalletinfo() @@ -68,7 +68,7 @@ class WalletUpgradeToHDTest(BitcoinTestFramework): self.log.info("Should no longer be able to start it with HD disabled") self.stop_node(0) - node.assert_start_raises_init_error(['-usehd=0'], "Error: Error loading : You can't disable HD on an already existing HD wallet") + node.assert_start_raises_init_error(['-usehd=0'], "Error: Error loading %s: You can't disable HD on an already existing HD wallet" % self.default_wallet_name) self.start_node(0) balance_after = node.getbalance() diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py index d04bc4ce44..2fe040f5f4 100755 --- a/test/functional/wallet_upgradewallet.py +++ b/test/functional/wallet_upgradewallet.py @@ -32,6 +32,7 @@ class UpgradeWalletTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + self.skip_if_no_bdb() def setup_network(self): self.setup_nodes()