diff options
33 files changed, 426 insertions, 199 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f95d3f818..8381bd2448 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -186,7 +186,7 @@ In general, all pull requests must: the project (for example refactoring for modularisation); - Be well peer reviewed; - Have unit tests and functional tests where appropriate; - - Follow code style guidelines; + - Follow code style guidelines ([C++](doc/developer-notes.md), [functional tests](test/functional/README.md)); - Not break the existing test suite; - Where bugs are fixed, where possible, there should be unit tests demonstrating the bug and also proving the fix. This helps prevent regression. diff --git a/doc/dependencies.md b/doc/dependencies.md index 964c03ba88..6e1d1ee992 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -13,7 +13,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | Expat | [2.2.1](https://libexpat.github.io/) | | No | Yes | | | fontconfig | [2.12.1](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | | | FreeType | [2.7.1](http://download.savannah.gnu.org/releases/freetype) | | No | | | -| GCC | | [4.7+](https://gcc.gnu.org/) | | | | +| GCC | | [4.8+](https://gcc.gnu.org/) | | | | | HarfBuzz-NG | | | | | | | libevent | [2.1.8-stable](https://github.com/libevent/libevent/releases) | 2.0.22 | No | | | | libjpeg | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L75) | diff --git a/doc/release-notes.md b/doc/release-notes.md index 6e4fed0a2d..2c63b1f88e 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -20,7 +20,7 @@ How to Upgrade ============== If you are running an older version, shut it down. Wait until it has completely -shut down (which might take a few minutes for older versions), then run the +shut down (which might take a few minutes for older versions), then run the installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac) or `bitcoind`/`bitcoin-qt` (on Linux). @@ -56,12 +56,31 @@ frequently tested on them. Notable changes =============== +GCC 4.8.x +-------------- +The minimum version of GCC required to compile Bitcoin Core is now 4.8. No effort will be +made to support older versions of GCC. See discussion in issue #11732 for more information. + HD-wallets by default --------------------- Due to a backward-incompatible change in the wallet database, wallets created with version 0.16.0 will be rejected by previous versions. Also, version 0.16.0 will only create hierarchical deterministic (HD) wallets. +Custom wallet directories +--------------------- +The ability to specify a directory other than the default data directory in which to store +wallets has been added. An existing directory can be specified using the `-walletdir=<dir>` +argument. Wallets loaded via `-wallet` arguments must be in this wallet directory. Care should be taken +when choosing a wallet directory location, as if it becomes unavailable during operation, +funds may be lost. + +Default wallet directory change +-------------------------- +On new installations (if the data directory doesn't exist), wallets will now be stored in a +new `wallets/` subdirectory inside the data directory. If this `wallets/` subdirectory +doesn't exist (i.e. on existing nodes), the current datadir root is used instead, as it was. + Low-level RPC changes ---------------------- - The deprecated RPC `getinfo` was removed. It is recommended that the more specific RPCs are used: diff --git a/src/Makefile.am b/src/Makefile.am index 29e4ae56d0..4b65774fc6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -168,6 +168,7 @@ BITCOIN_CORE_H = \ wallet/rpcwallet.h \ wallet/wallet.h \ wallet/walletdb.h \ + wallet/walletutil.h \ warnings.h \ zmq/zmqabstractnotifier.h \ zmq/zmqconfig.h\ @@ -249,6 +250,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/rpcwallet.cpp \ wallet/wallet.cpp \ wallet/walletdb.cpp \ + wallet/walletutil.cpp \ $(BITCOIN_CORE_H) # crypto primitives library diff --git a/src/Makefile.test.include b/src/Makefile.test.include index f037705aaf..06175be3fc 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -80,6 +80,7 @@ BITCOIN_TESTS =\ test/timedata_tests.cpp \ test/torcontrol_tests.cpp \ test/transaction_tests.cpp \ + test/txvalidation_tests.cpp \ test/txvalidationcache_tests.cpp \ test/versionbits_tests.cpp \ test/uint256_tests.cpp \ diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 5e3b76a295..136981b709 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -213,6 +213,9 @@ public: /** Create a simulated `getinfo` request. */ UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override { + if (!args.empty()) { + throw std::runtime_error("-getinfo takes no arguments"); + } UniValue result(UniValue::VARR); result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO)); result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO)); diff --git a/src/compat/byteswap.h b/src/compat/byteswap.h index 8930534721..a6df6ded7a 100644 --- a/src/compat/byteswap.h +++ b/src/compat/byteswap.h @@ -37,7 +37,7 @@ inline uint16_t bswap_16(uint16_t x) { return (x >> 8) | (x << 8); } -#endif // HAVE_DECL_BSWAP16 +#endif // HAVE_DECL_BSWAP16 == 0 #if HAVE_DECL_BSWAP_32 == 0 inline uint32_t bswap_32(uint32_t x) @@ -45,7 +45,7 @@ inline uint32_t bswap_32(uint32_t x) return (((x & 0xff000000U) >> 24) | ((x & 0x00ff0000U) >> 8) | ((x & 0x0000ff00U) << 8) | ((x & 0x000000ffU) << 24)); } -#endif // HAVE_DECL_BSWAP32 +#endif // HAVE_DECL_BSWAP32 == 0 #if HAVE_DECL_BSWAP_64 == 0 inline uint64_t bswap_64(uint64_t x) @@ -59,7 +59,7 @@ inline uint64_t bswap_64(uint64_t x) | ((x & 0x000000000000ff00ull) << 40) | ((x & 0x00000000000000ffull) << 56)); } -#endif // HAVE_DECL_BSWAP64 +#endif // HAVE_DECL_BSWAP64 == 0 #endif // defined(__APPLE__) diff --git a/src/init.cpp b/src/init.cpp index 439eaacfcc..3cc797a17c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1030,11 +1030,6 @@ bool AppInitParameterInteraction() fPruneMode = true; } - RegisterAllCoreRPCCommands(tableRPC); -#ifdef ENABLE_WALLET - RegisterWalletRPC(tableRPC); -#endif - nConnectTimeout = gArgs.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT); if (nConnectTimeout <= 0) nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; @@ -1239,6 +1234,14 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); GetMainSignals().RegisterWithMempoolSignals(mempool); + /* Register RPC commands regardless of -server setting so they will be + * available in the GUI RPC console even if external calls are disabled. + */ + RegisterAllCoreRPCCommands(tableRPC); +#ifdef ENABLE_WALLET + RegisterWalletRPC(tableRPC); +#endif + /* Start the RPC server already. It will be started in "warmup" mode * and not really process calls already (but it will signify connections * that the server is there and will be ready later). Warmup mode will diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 9e4c765101..7f8a8394e6 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -214,7 +214,10 @@ bool Intro::pickDataDirectory() } dataDir = intro.getDataDirectory(); try { - TryCreateDirectories(GUIUtil::qstringToBoostPath(dataDir)); + if (TryCreateDirectories(GUIUtil::qstringToBoostPath(dataDir))) { + // If a new data directory has been created, make wallets subdirectory too + TryCreateDirectories(GUIUtil::qstringToBoostPath(dataDir) / "wallets"); + } break; } catch (const fs::filesystem_error&) { QMessageBox::critical(0, tr(PACKAGE_NAME), diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 6687d62baa..54a6e837c1 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -392,11 +392,37 @@ void RPCExecutor::request(const QString &command) { std::string result; std::string executableCommand = command.toStdString() + "\n"; + + // Catch the console-only-help command before RPC call is executed and reply with help text as-if a RPC reply. + if(executableCommand == "help-console\n") + { + Q_EMIT reply(RPCConsole::CMD_REPLY, QString(("\n" + "This console accepts RPC commands using the standard syntax.\n" + " example: getblockhash 0\n\n" + + "This console can also accept RPC commands using parenthesized syntax.\n" + " example: getblockhash(0)\n\n" + + "Commands may be nested when specified with the parenthesized syntax.\n" + " example: getblock(getblockhash(0) 1)\n\n" + + "A space or a comma can be used to delimit arguments for either syntax.\n" + " example: getblockhash 0\n" + " getblockhash,0\n\n" + + "Named results can be queried with a non-quoted key string in brackets.\n" + " example: getblock(getblockhash(0) true)[tx]\n\n" + + "Results without keys can be queried using an integer in brackets.\n" + " example: getblock(getblockhash(0),true)[tx][0]\n\n"))); + return; + } if(!RPCConsole::RPCExecuteCommandLine(result, executableCommand)) { Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); return; } + Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); } catch (UniValue& objError) @@ -645,6 +671,7 @@ void RPCConsole::setClientModel(ClientModel *model) wordList << ("help " + commandList[i]).c_str(); } + wordList << "help-console"; wordList.sort(); autoCompleter = new QCompleter(wordList, this); autoCompleter->setModelSorting(QCompleter::CaseSensitivelySortedModel); @@ -750,10 +777,11 @@ void RPCConsole::clear(bool clearHistory) message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(tr(PACKAGE_NAME)) + "<br>" + tr("Use up and down arrows to navigate history, and %1 to clear screen.").arg("<b>"+clsKey+"</b>") + "<br>" + - tr("Type <b>help</b> for an overview of available commands.")) + - "<br><span class=\"secwarning\">" + + tr("Type %1 for an overview of available commands.").arg("<b>help</b>") + "<br>" + + tr("For more information on using this console type %1.").arg("<b>help-console</b>") + + "<br><span class=\"secwarning\"><br>" + tr("WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramifications of a command.") + - "</span>", + "</span>"), true); } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index e65959bf0e..3aff1e9fbf 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -970,6 +970,10 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) } } else if (fHaveChain) { throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain"); + } else { + // Make sure we don't block forever if re-sending + // a transaction already in mempool. + promise.set_value(); } } // cs_main diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp new file mode 100644 index 0000000000..2d1eb7b772 --- /dev/null +++ b/src/test/txvalidation_tests.cpp @@ -0,0 +1,61 @@ +// Copyright (c) 2017 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <validation.h> +#include <txmempool.h> +#include <amount.h> +#include <consensus/validation.h> +#include <primitives/transaction.h> +#include <script/script.h> +#include <test/test_bitcoin.h> + +#include <boost/test/unit_test.hpp> + + +BOOST_AUTO_TEST_SUITE(txvalidation_tests) + +/** + * Ensure that the mempool won't accept coinbase transactions. + */ +BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup) +{ + CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG; + CMutableTransaction coinbaseTx; + + coinbaseTx.nVersion = 1; + coinbaseTx.vin.resize(1); + coinbaseTx.vout.resize(1); + coinbaseTx.vin[0].scriptSig = CScript() << OP_11 << OP_EQUAL; + coinbaseTx.vout[0].nValue = 1 * CENT; + coinbaseTx.vout[0].scriptPubKey = scriptPubKey; + + assert(CTransaction(coinbaseTx).IsCoinBase()); + + CValidationState state; + + LOCK(cs_main); + + unsigned int initialPoolSize = mempool.size(); + + BOOST_CHECK_EQUAL( + false, + AcceptToMemoryPool(mempool, state, MakeTransactionRef(coinbaseTx), + nullptr /* pfMissingInputs */, + nullptr /* plTxnReplaced */, + true /* bypass_limits */, + 0 /* nAbsurdFee */)); + + // Check that the transaction hasn't been added to mempool. + BOOST_CHECK_EQUAL(mempool.size(), initialPoolSize); + + // Check that the validation state reflects the unsuccessful attempt. + BOOST_CHECK(state.IsInvalid()); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "coinbase"); + + int nDoS; + BOOST_CHECK_EQUAL(state.IsInvalid(nDoS), true); + BOOST_CHECK_EQUAL(nDoS, 100); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util.cpp b/src/util.cpp index b87dd091be..d58f39e969 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -574,7 +574,10 @@ const fs::path &GetDataDir(bool fNetSpecific) if (fNetSpecific) path /= BaseParams().DataDir(); - fs::create_directories(path); + if (fs::create_directories(path)) { + // This is the first run, create wallets subdirectory too + fs::create_directories(path / "wallets"); + } return path; } diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index c6fd95f250..79ff27279c 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -11,6 +11,7 @@ #include <protocol.h> #include <util.h> #include <utilstrencodings.h> +#include <wallet/walletutil.h> #include <stdint.h> @@ -257,7 +258,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco return fSuccess; } -bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr) +bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr) { LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); LogPrintf("Using wallet %s\n", walletFile); @@ -265,15 +266,15 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataD // Wallet file must be a plain filename without a directory if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) { - errorStr = strprintf(_("Wallet %s resides outside data directory %s"), walletFile, dataDir.string()); + errorStr = strprintf(_("Wallet %s resides outside wallet directory %s"), walletFile, walletDir.string()); return false; } - if (!bitdb.Open(dataDir)) + if (!bitdb.Open(walletDir)) { // try moving the database env out of the way - fs::path pathDatabase = dataDir / "database"; - fs::path pathDatabaseBak = dataDir / strprintf("database.%d.bak", GetTime()); + fs::path pathDatabase = walletDir / "database"; + fs::path pathDatabaseBak = walletDir / strprintf("database.%d.bak", GetTime()); try { fs::rename(pathDatabase, pathDatabaseBak); LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string()); @@ -282,18 +283,18 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataD } // try again - if (!bitdb.Open(dataDir)) { + if (!bitdb.Open(walletDir)) { // if it still fails, it probably means we can't even create the database env - errorStr = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir()); + errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir); return false; } } return true; } -bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc) +bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc) { - if (fs::exists(dataDir / walletFile)) + if (fs::exists(walletDir / walletFile)) { std::string backup_filename; CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename); @@ -303,7 +304,7 @@ bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& data " Original %s saved as %s in %s; if" " your balance or transactions are incorrect you should" " restore from a backup."), - walletFile, backup_filename, dataDir); + walletFile, backup_filename, walletDir); } if (r == CDBEnv::RECOVER_FAIL) { @@ -407,7 +408,7 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb { LOCK(env->cs_db); - if (!env->Open(GetDataDir())) + if (!env->Open(GetWalletDir())) throw std::runtime_error("CDB: Failed to open database environment."); pdb = env->mapDb[strFilename]; @@ -695,7 +696,7 @@ bool CWalletDBWrapper::Backup(const std::string& strDest) env->mapFileUseCount.erase(strFile); // Copy wallet file - fs::path pathSrc = GetDataDir() / strFile; + fs::path pathSrc = GetWalletDir() / strFile; fs::path pathDest(strDest); if (fs::is_directory(pathDest)) pathDest /= strFile; diff --git a/src/wallet/db.h b/src/wallet/db.h index 5c5948aa29..ed2ee65cac 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -167,9 +167,9 @@ public: ideal to be called periodically */ static bool PeriodicFlush(CWalletDBWrapper& dbw); /* verifies the database environment */ - static bool VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr); + static bool VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr); /* verifies the database file */ - static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc); + static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc); public: template <typename K, typename T> diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index ca6dcccf78..67c46df87d 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -9,8 +9,9 @@ #include <util.h> #include <utilmoneystr.h> #include <validation.h> -#include <wallet/wallet.h> #include <wallet/rpcwallet.h> +#include <wallet/wallet.h> +#include <wallet/walletutil.h> std::string GetWalletHelpString(bool showDebug) { @@ -34,6 +35,7 @@ std::string GetWalletHelpString(bool showDebug) strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); strUsage += HelpMessageOpt("-wallet=<file>", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT)); strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST)); + strUsage += HelpMessageOpt("-walletdir=<dir>", _("Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)")); strUsage += HelpMessageOpt("-walletnotify=<cmd>", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)")); strUsage += HelpMessageOpt("-zapwallettxes=<mode>", _("Delete all wallet transactions and only recover those parts of the blockchain through -rescan on startup") + " " + _("(1 = keep tx meta data e.g. account owner and payment request information, 2 = drop tx meta data)")); @@ -191,6 +193,12 @@ bool VerifyWallets() return true; } + if (gArgs.IsArgSet("-walletdir") && !fs::is_directory(GetWalletDir())) { + return InitError(strprintf(_("Error: Specified wallet directory \"%s\" does not exist."), gArgs.GetArg("-walletdir", "").c_str())); + } + + LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); + uiInterface.InitMessage(_("Verifying wallet(s)...")); // Keep track of each wallet absolute path to detect duplicates. @@ -205,7 +213,7 @@ bool VerifyWallets() return InitError(strprintf(_("Error loading wallet %s. Invalid characters in -wallet filename."), walletFile)); } - fs::path wallet_path = fs::absolute(walletFile, GetDataDir()); + fs::path wallet_path = fs::absolute(walletFile, GetWalletDir()); if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) { return InitError(strprintf(_("Error loading wallet %s. -wallet filename must be a regular file."), walletFile)); @@ -216,7 +224,7 @@ bool VerifyWallets() } std::string strError; - if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError)) { + if (!CWalletDB::VerifyEnvironment(walletFile, GetWalletDir().string(), strError)) { return InitError(strError); } @@ -230,7 +238,7 @@ bool VerifyWallets() } std::string strWarning; - bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError); + bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetWalletDir().string(), strWarning, strError); if (!strWarning.empty()) { InitWarning(strWarning); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 23559eb350..9a7861f978 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -26,6 +26,7 @@ #include <wallet/feebumper.h> #include <wallet/wallet.h> #include <wallet/walletdb.h> +#include <wallet/walletutil.h> #include <init.h> // For StartShutdown diff --git a/src/wallet/test/accounting_tests.cpp b/src/wallet/test/accounting_tests.cpp index a9cdc89e6f..b95bb14335 100644 --- a/src/wallet/test/accounting_tests.cpp +++ b/src/wallet/test/accounting_tests.cpp @@ -10,18 +10,16 @@ #include <boost/test/unit_test.hpp> -extern CWallet* pwalletMain; - BOOST_FIXTURE_TEST_SUITE(accounting_tests, WalletTestingSetup) static void -GetResults(std::map<CAmount, CAccountingEntry>& results) +GetResults(CWallet *wallet, std::map<CAmount, CAccountingEntry>& results) { std::list<CAccountingEntry> aes; results.clear(); - BOOST_CHECK(pwalletMain->ReorderTransactions() == DB_LOAD_OK); - pwalletMain->ListAccountCreditDebit("", aes); + BOOST_CHECK(wallet->ReorderTransactions() == DB_LOAD_OK); + wallet->ListAccountCreditDebit("", aes); for (CAccountingEntry& ae : aes) { results[ae.nOrderPos] = ae; @@ -54,7 +52,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade) ae.strOtherAccount = "c"; pwalletMain->AddAccountingEntry(ae); - GetResults(results); + GetResults(pwalletMain.get(), results); BOOST_CHECK(pwalletMain->nOrderPosNext == 3); BOOST_CHECK(2 == results.size()); @@ -70,7 +68,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade) ae.nOrderPos = pwalletMain->IncOrderPosNext(); pwalletMain->AddAccountingEntry(ae); - GetResults(results); + GetResults(pwalletMain.get(), results); BOOST_CHECK(results.size() == 3); BOOST_CHECK(pwalletMain->nOrderPosNext == 4); @@ -102,7 +100,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade) vpwtx[2]->nTimeReceived = (unsigned int)1333333329; vpwtx[2]->nOrderPos = -1; - GetResults(results); + GetResults(pwalletMain.get(), results); BOOST_CHECK(results.size() == 3); BOOST_CHECK(pwalletMain->nOrderPosNext == 6); @@ -120,7 +118,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade) ae.nOrderPos = -1; pwalletMain->AddAccountingEntry(ae); - GetResults(results); + GetResults(pwalletMain.get(), results); BOOST_CHECK(results.size() == 4); BOOST_CHECK(pwalletMain->nOrderPosNext == 7); diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index 995ec640b1..3ee83d2d7c 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -6,9 +6,6 @@ #include <rpc/server.h> #include <wallet/db.h> -#include <wallet/wallet.h> - -std::unique_ptr<CWallet> pwalletMain; WalletTestingSetup::WalletTestingSetup(const std::string& chainName): TestingSetup(chainName) @@ -27,7 +24,6 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName): WalletTestingSetup::~WalletTestingSetup() { UnregisterValidationInterface(pwalletMain.get()); - pwalletMain.reset(); bitdb.Flush(true); bitdb.Reset(); diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index a341bc698a..292d654438 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -7,11 +7,15 @@ #include <test/test_bitcoin.h> +#include <wallet/wallet.h> + /** Testing setup and teardown for wallet. */ struct WalletTestingSetup: public TestingSetup { explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~WalletTestingSetup(); + + std::unique_ptr<CWallet> pwalletMain; }; #endif diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 7ae1e74dbe..80e31a1ce0 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -19,8 +19,6 @@ #include <boost/test/unit_test.hpp> #include <univalue.h> -extern CWallet* pwalletMain; - extern UniValue importmulti(const JSONRPCRequest& request); extern UniValue dumpwallet(const JSONRPCRequest& request); extern UniValue importwallet(const JSONRPCRequest& request); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 66b8dea4ad..5116d6419e 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -814,14 +814,14 @@ bool CWalletDB::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDa return true; } -bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr) +bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr) { - return CDB::VerifyEnvironment(walletFile, dataDir, errorStr); + return CDB::VerifyEnvironment(walletFile, walletDir, errorStr); } -bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr) +bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr) { - return CDB::VerifyDatabaseFile(walletFile, dataDir, warningStr, errorStr, CWalletDB::Recover); + return CDB::VerifyDatabaseFile(walletFile, walletDir, warningStr, errorStr, CWalletDB::Recover); } bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value) diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 0a85fc3bc0..e815bcfeda 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -226,9 +226,9 @@ public: /* Function to determine if a certain KV/key-type is a key (cryptographical key) type */ static bool IsKeyType(const std::string& strType); /* verifies the database environment */ - static bool VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr); + static bool VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr); /* verifies the database file */ - static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr); + static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr); //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp new file mode 100644 index 0000000000..fbb5215a51 --- /dev/null +++ b/src/wallet/walletutil.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2017 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "wallet/walletutil.h" + +fs::path GetWalletDir() +{ + fs::path path; + + if (gArgs.IsArgSet("-walletdir")) { + path = fs::system_complete(gArgs.GetArg("-walletdir", "")); + if (!fs::is_directory(path)) { + // If the path specified doesn't exist, we return the deliberately + // invalid empty string. + path = ""; + } + } else { + path = GetDataDir(); + // If a wallets directory exists, use that, otherwise default to GetDataDir + if (fs::is_directory(path / "wallets")) { + path /= "wallets"; + } + } + + return path; +} diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h new file mode 100644 index 0000000000..a94f286a44 --- /dev/null +++ b/src/wallet/walletutil.h @@ -0,0 +1,13 @@ +// Copyright (c) 2017 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_WALLET_UTIL_H +#define BITCOIN_WALLET_UTIL_H + +#include "util.h" + +//! Get the path of the wallet directory. +fs::path GetWalletDir(); + +#endif // BITCOIN_WALLET_UTIL_H diff --git a/test/functional/bitcoin_cli.py b/test/functional/bitcoin_cli.py index 996cbb8a12..d1cd3b3620 100755 --- a/test/functional/bitcoin_cli.py +++ b/test/functional/bitcoin_cli.py @@ -35,8 +35,11 @@ class TestBitcoinCli(BitcoinTestFramework): assert_equal(["foo", "bar"], self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input=password + "\nfoo\nbar").echo()) assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input="foo").echo) + self.log.info("Make sure that -getinfo with arguments fails") + assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help) + self.log.info("Compare responses from `bitcoin-cli -getinfo` and the RPCs data is retrieved from.") - cli_get_info = self.nodes[0].cli('-getinfo').help() + cli_get_info = self.nodes[0].cli().send_cli('-getinfo') wallet_info = self.nodes[0].getwalletinfo() network_info = self.nodes[0].getnetworkinfo() blockchain_info = self.nodes[0].getblockchaininfo() diff --git a/test/functional/keypool-topup.py b/test/functional/keypool-topup.py index 160a0f7ae5..e7af3c3987 100755 --- a/test/functional/keypool-topup.py +++ b/test/functional/keypool-topup.py @@ -33,7 +33,7 @@ class KeypoolRestoreTest(BitcoinTestFramework): self.stop_node(1) - shutil.copyfile(self.tmpdir + "/node1/regtest/wallet.dat", self.tmpdir + "/wallet.bak") + shutil.copyfile(self.tmpdir + "/node1/regtest/wallets/wallet.dat", self.tmpdir + "/wallet.bak") self.start_node(1, self.extra_args[1]) connect_nodes_bi(self.nodes, 0, 1) @@ -56,7 +56,7 @@ class KeypoolRestoreTest(BitcoinTestFramework): self.stop_node(1) - shutil.copyfile(self.tmpdir + "/wallet.bak", self.tmpdir + "/node1/regtest/wallet.dat") + shutil.copyfile(self.tmpdir + "/wallet.bak", self.tmpdir + "/node1/regtest/wallets/wallet.dat") self.log.info("Verify keypool is restored and balance is correct") diff --git a/test/functional/multiwallet.py b/test/functional/multiwallet.py index 7a0fbce477..06409b6f31 100755 --- a/test/functional/multiwallet.py +++ b/test/functional/multiwallet.py @@ -16,10 +16,10 @@ class MultiWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3']] + self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3', '-wallet=w']] def run_test(self): - assert_equal(set(self.nodes[0].listwallets()), {"w1", "w2", "w3"}) + assert_equal(set(self.nodes[0].listwallets()), {"w1", "w2", "w3", "w"}) self.stop_node(0) @@ -27,23 +27,46 @@ class MultiWalletTest(BitcoinTestFramework): self.assert_start_raises_init_error(0, ['-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.') # should not initialize if wallet file is a directory - os.mkdir(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w11')) + wallet_dir = os.path.join(self.options.tmpdir, 'node0', 'regtest', 'wallets') + os.mkdir(os.path.join(wallet_dir, 'w11')) self.assert_start_raises_init_error(0, ['-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.') # should not initialize if one wallet is a copy of another - shutil.copyfile(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w2'), - os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w22')) + shutil.copyfile(os.path.join(wallet_dir, 'w2'), os.path.join(wallet_dir, 'w22')) self.assert_start_raises_init_error(0, ['-wallet=w2', '-wallet=w22'], 'duplicates fileid') # should not initialize if wallet file is a symlink - os.symlink(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w1'), os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w12')) + os.symlink(os.path.join(wallet_dir, 'w1'), os.path.join(wallet_dir, 'w12')) self.assert_start_raises_init_error(0, ['-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.') + # should not initialize if the specified walletdir does not exist + self.assert_start_raises_init_error(0, ['-walletdir=bad'], 'Error: Specified wallet directory "bad" does not exist.') + + # if wallets/ doesn't exist, datadir should be the default wallet dir + wallet_dir2 = os.path.join(self.options.tmpdir, 'node0', 'regtest', 'walletdir') + os.rename(wallet_dir, wallet_dir2) + self.start_node(0, ['-wallet=w4', '-wallet=w5']) + assert_equal(set(self.nodes[0].listwallets()), {"w4", "w5"}) + w5 = self.nodes[0].get_wallet_rpc("w5") + w5.generate(1) + self.stop_node(0) + + # now if wallets/ exists again, but the rootdir is specified as the walletdir, w4 and w5 should still be loaded + os.rename(wallet_dir2, wallet_dir) + self.start_node(0, ['-wallet=w4', '-wallet=w5', '-walletdir=' + os.path.join(self.options.tmpdir, 'node0', 'regtest')]) + assert_equal(set(self.nodes[0].listwallets()), {"w4", "w5"}) + w5 = self.nodes[0].get_wallet_rpc("w5") + w5_info = w5.getwalletinfo() + assert_equal(w5_info['immature_balance'], 50) + + self.stop_node(0) + self.start_node(0, self.extra_args[0]) w1 = self.nodes[0].get_wallet_rpc("w1") w2 = self.nodes[0].get_wallet_rpc("w2") w3 = self.nodes[0].get_wallet_rpc("w3") + w4 = self.nodes[0].get_wallet_rpc("w") wallet_bad = self.nodes[0].get_wallet_rpc("bad") w1.generate(1) @@ -69,18 +92,22 @@ class MultiWalletTest(BitcoinTestFramework): w3_name = w3.getwalletinfo()['walletname'] assert_equal(w3_name, "w3") - assert_equal({"w1", "w2", "w3"}, {w1_name, w2_name, w3_name}) + w4_name = w4.getwalletinfo()['walletname'] + assert_equal(w4_name, "w") w1.generate(101) assert_equal(w1.getbalance(), 100) assert_equal(w2.getbalance(), 0) assert_equal(w3.getbalance(), 0) + assert_equal(w4.getbalance(), 0) w1.sendtoaddress(w2.getnewaddress(), 1) w1.sendtoaddress(w3.getnewaddress(), 2) + w1.sendtoaddress(w4.getnewaddress(), 3) w1.generate(1) assert_equal(w2.getbalance(), 1) assert_equal(w3.getbalance(), 2) + assert_equal(w4.getbalance(), 3) batch = w1.batch([w1.getblockchaininfo.get_request(), w1.getwalletinfo.get_request()]) assert_equal(batch[0]["result"]["chain"], "regtest") diff --git a/test/functional/sendheaders.py b/test/functional/sendheaders.py index 55bb80ea00..68c0d95b4f 100755 --- a/test/functional/sendheaders.py +++ b/test/functional/sendheaders.py @@ -4,11 +4,13 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test behavior of headers messages to announce blocks. -Setup: +Setup: -- Two nodes, two p2p connections to node0. One p2p connection should only ever - receive inv's (omitted from testing description below, this is our control). - Second node is used for creating reorgs. +- Two nodes: + - node0 is the node-under-test. We create two p2p connections to it. The + first p2p connection is a control and should only ever receive inv's. The + second p2p connection tests the headers sending logic. + - node1 is used to create reorgs. test_null_locators ================== @@ -83,35 +85,45 @@ d. Announce 49 headers that don't connect. e. Announce one more that doesn't connect. Expect: disconnect. """ - -from test_framework.mininode import * -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * from test_framework.blocktools import create_block, create_coinbase +from test_framework.mininode import ( + CBlockHeader, + CInv, + NODE_WITNESS, + NetworkThread, + NodeConnCB, + mininode_lock, + msg_block, + msg_getblocks, + msg_getdata, + msg_getheaders, + msg_headers, + msg_inv, + msg_sendheaders, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + sync_blocks, + wait_until, +) +DIRECT_FETCH_RESPONSE_TIME = 0.05 -direct_fetch_response_time = 0.05 - -class TestNode(NodeConnCB): +class BaseNode(NodeConnCB): def __init__(self): super().__init__() self.block_announced = False self.last_blockhash_announced = None - def clear_last_announcement(self): - with mininode_lock: - self.block_announced = False - self.last_message.pop("inv", None) - self.last_message.pop("headers", None) - - # Request data for a list of block hashes - def get_data(self, block_hashes): + def send_get_data(self, block_hashes): + """Request data for a list of block hashes.""" msg = msg_getdata() for x in block_hashes: msg.inv.append(CInv(2, x)) self.connection.send_message(msg) - def get_headers(self, locator, hashstop): + def send_get_headers(self, locator, hashstop): msg = msg_getheaders() msg.locator.vHave = locator msg.hashstop = hashstop @@ -122,6 +134,27 @@ class TestNode(NodeConnCB): msg.inv = [CInv(2, blockhash)] self.connection.send_message(msg) + def send_header_for_blocks(self, new_blocks): + headers_message = msg_headers() + headers_message.headers = [CBlockHeader(b) for b in new_blocks] + self.send_message(headers_message) + + def send_getblocks(self, locator): + getblocks_message = msg_getblocks() + getblocks_message.locator.vHave = locator + self.send_message(getblocks_message) + + def wait_for_getdata(self, hash_list, timeout=60): + if hash_list == []: + return + + test_function = lambda: "getdata" in self.last_message and [x.hash for x in self.last_message["getdata"].inv] == hash_list + wait_until(test_function, timeout=timeout, lock=mininode_lock) + + def wait_for_block_announcement(self, block_hash, timeout=60): + test_function = lambda: self.last_blockhash_announced == block_hash + wait_until(test_function, timeout=timeout, lock=mininode_lock) + def on_inv(self, conn, message): self.block_announced = True self.last_blockhash_announced = message.inv[-1].hash @@ -132,97 +165,79 @@ class TestNode(NodeConnCB): message.headers[-1].calc_sha256() self.last_blockhash_announced = message.headers[-1].sha256 - # Test whether the last announcement we received had the - # right header or the right inv - # inv and headers should be lists of block hashes + def clear_last_announcement(self): + with mininode_lock: + self.block_announced = False + self.last_message.pop("inv", None) + self.last_message.pop("headers", None) + def check_last_announcement(self, headers=None, inv=None): - expect_headers = headers if headers != None else [] - expect_inv = inv if inv != None else [] + """Test whether the last announcement received had the right header or the right inv. + + inv and headers should be lists of block hashes.""" + test_function = lambda: self.block_announced wait_until(test_function, timeout=60, lock=mininode_lock) + with mininode_lock: self.block_announced = False - success = True compare_inv = [] if "inv" in self.last_message: compare_inv = [x.hash for x in self.last_message["inv"].inv] - if compare_inv != expect_inv: - success = False + if inv is not None: + assert_equal(compare_inv, inv) - hash_headers = [] + compare_headers = [] if "headers" in self.last_message: - # treat headers as a list of block hashes - hash_headers = [ x.sha256 for x in self.last_message["headers"].headers ] - if hash_headers != expect_headers: - success = False + compare_headers = [x.sha256 for x in self.last_message["headers"].headers] + if headers is not None: + assert_equal(compare_headers, headers) self.last_message.pop("inv", None) self.last_message.pop("headers", None) - return success - - def wait_for_getdata(self, hash_list, timeout=60): - if hash_list == []: - return - - test_function = lambda: "getdata" in self.last_message and [x.hash for x in self.last_message["getdata"].inv] == hash_list - wait_until(test_function, timeout=timeout, lock=mininode_lock) - return - - def wait_for_block_announcement(self, block_hash, timeout=60): - test_function = lambda: self.last_blockhash_announced == block_hash - wait_until(test_function, timeout=timeout, lock=mininode_lock) - return - - def send_header_for_blocks(self, new_blocks): - headers_message = msg_headers() - headers_message.headers = [ CBlockHeader(b) for b in new_blocks ] - self.send_message(headers_message) - - def send_getblocks(self, locator): - getblocks_message = msg_getblocks() - getblocks_message.locator.vHave = locator - self.send_message(getblocks_message) class SendHeadersTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - # mine count blocks and return the new tip def mine_blocks(self, count): + """Mine count blocks and return the new tip.""" + # Clear out last block announcement from each p2p listener [x.clear_last_announcement() for x in self.nodes[0].p2ps] self.nodes[0].generate(count) return int(self.nodes[0].getbestblockhash(), 16) - # mine a reorg that invalidates length blocks (replacing them with - # length+1 blocks). - # Note: we clear the state of our p2p connections after the - # to-be-reorged-out blocks are mined, so that we don't break later tests. - # return the list of block hashes newly mined def mine_reorg(self, length): - self.nodes[0].generate(length) # make sure all invalidated blocks are node0's + """Mine a reorg that invalidates length blocks (replacing them with # length+1 blocks). + + Note: we clear the state of our p2p connections after the + to-be-reorged-out blocks are mined, so that we don't break later tests. + return the list of block hashes newly mined.""" + + self.nodes[0].generate(length) # make sure all invalidated blocks are node0's sync_blocks(self.nodes, wait=0.1) for x in self.nodes[0].p2ps: x.wait_for_block_announcement(int(self.nodes[0].getbestblockhash(), 16)) x.clear_last_announcement() tip_height = self.nodes[1].getblockcount() - hash_to_invalidate = self.nodes[1].getblockhash(tip_height-(length-1)) + hash_to_invalidate = self.nodes[1].getblockhash(tip_height - (length - 1)) self.nodes[1].invalidateblock(hash_to_invalidate) - all_hashes = self.nodes[1].generate(length+1) # Must be longer than the orig chain + all_hashes = self.nodes[1].generate(length + 1) # Must be longer than the orig chain sync_blocks(self.nodes, wait=0.1) return [int(x, 16) for x in all_hashes] def run_test(self): # Setup the p2p connections and start up the network thread. - inv_node = self.nodes[0].add_p2p_connection(TestNode()) - # Set nServices to 0 for test_node, so no block download will occur outside of - # direct fetching - test_node = self.nodes[0].add_p2p_connection(TestNode(), services=NODE_WITNESS) + inv_node = self.nodes[0].add_p2p_connection(BaseNode()) + # Make sure NODE_NETWORK is not set for test_node, so no block download + # will occur outside of direct fetching + test_node = self.nodes[0].add_p2p_connection(BaseNode(), services=NODE_WITNESS) - NetworkThread().start() # Start up network handling in another thread + NetworkThread().start() # Start up network handling in another thread # Test logic begins here inv_node.wait_for_verack() @@ -232,27 +247,32 @@ class SendHeadersTest(BitcoinTestFramework): inv_node.sync_with_ping() test_node.sync_with_ping() - self.test_null_locators(test_node) + self.test_null_locators(test_node, inv_node) self.test_nonnull_locators(test_node, inv_node) - def test_null_locators(self, test_node): + def test_null_locators(self, test_node, inv_node): tip = self.nodes[0].getblockheader(self.nodes[0].generate(1)[0]) tip_hash = int(tip["hash"], 16) + inv_node.check_last_announcement(inv=[tip_hash], headers=[]) + test_node.check_last_announcement(inv=[tip_hash], headers=[]) + self.log.info("Verify getheaders with null locator and valid hashstop returns headers.") test_node.clear_last_announcement() - test_node.get_headers(locator=[], hashstop=tip_hash) - assert_equal(test_node.check_last_announcement(headers=[tip_hash]), True) + test_node.send_get_headers(locator=[], hashstop=tip_hash) + test_node.check_last_announcement(headers=[tip_hash]) self.log.info("Verify getheaders with null locator and invalid hashstop does not return headers.") block = create_block(int(tip["hash"], 16), create_coinbase(tip["height"] + 1), tip["mediantime"] + 1) block.solve() test_node.send_header_for_blocks([block]) test_node.clear_last_announcement() - test_node.get_headers(locator=[], hashstop=int(block.hash, 16)) + test_node.send_get_headers(locator=[], hashstop=int(block.hash, 16)) test_node.sync_with_ping() assert_equal(test_node.block_announced, False) + inv_node.clear_last_announcement() test_node.send_message(msg_block(block)) + inv_node.check_last_announcement(inv=[int(block.hash, 16)], headers=[]) def test_nonnull_locators(self, test_node, inv_node): tip = int(self.nodes[0].getbestblockhash(), 16) @@ -263,30 +283,30 @@ class SendHeadersTest(BitcoinTestFramework): for i in range(4): old_tip = tip tip = self.mine_blocks(1) - assert_equal(inv_node.check_last_announcement(inv=[tip]), True) - assert_equal(test_node.check_last_announcement(inv=[tip]), True) + inv_node.check_last_announcement(inv=[tip], headers=[]) + test_node.check_last_announcement(inv=[tip], headers=[]) # Try a few different responses; none should affect next announcement if i == 0: # first request the block - test_node.get_data([tip]) + test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 1: # next try requesting header and block - test_node.get_headers(locator=[old_tip], hashstop=tip) - test_node.get_data([tip]) + test_node.send_get_headers(locator=[old_tip], hashstop=tip) + test_node.send_get_data([tip]) test_node.wait_for_block(tip) - test_node.clear_last_announcement() # since we requested headers... + test_node.clear_last_announcement() # since we requested headers... elif i == 2: # this time announce own block via headers height = self.nodes[0].getblockcount() last_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] block_time = last_time + 1 - new_block = create_block(tip, create_coinbase(height+1), block_time) + new_block = create_block(tip, create_coinbase(height + 1), block_time) new_block.solve() test_node.send_header_for_blocks([new_block]) test_node.wait_for_getdata([new_block.sha256]) test_node.send_message(msg_block(new_block)) - test_node.sync_with_ping() # make sure this block is processed + test_node.sync_with_ping() # make sure this block is processed inv_node.clear_last_announcement() test_node.clear_last_announcement() @@ -297,15 +317,15 @@ class SendHeadersTest(BitcoinTestFramework): # commence and keep working. test_node.send_message(msg_sendheaders()) prev_tip = int(self.nodes[0].getbestblockhash(), 16) - test_node.get_headers(locator=[prev_tip], hashstop=0) + test_node.send_get_headers(locator=[prev_tip], hashstop=0) test_node.sync_with_ping() # Now that we've synced headers, headers announcements should work tip = self.mine_blocks(1) - assert_equal(inv_node.check_last_announcement(inv=[tip]), True) - assert_equal(test_node.check_last_announcement(headers=[tip]), True) + inv_node.check_last_announcement(inv=[tip], headers=[]) + test_node.check_last_announcement(headers=[tip]) - height = self.nodes[0].getblockcount()+1 + height = self.nodes[0].getblockcount() + 1 block_time += 10 # Advance far enough ahead for i in range(10): # Mine i blocks, and alternate announcing either via @@ -314,7 +334,7 @@ class SendHeadersTest(BitcoinTestFramework): # with block header, even though the blocks are never requested for j in range(2): blocks = [] - for b in range(i+1): + for b in range(i + 1): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 @@ -328,7 +348,7 @@ class SendHeadersTest(BitcoinTestFramework): test_node.send_header_for_blocks(blocks) # Test that duplicate inv's won't result in duplicate # getdata requests, or duplicate headers announcements - [ inv_node.send_block_inv(x.sha256) for x in blocks ] + [inv_node.send_block_inv(x.sha256) for x in blocks] test_node.wait_for_getdata([x.sha256 for x in blocks]) inv_node.sync_with_ping() else: @@ -339,7 +359,7 @@ class SendHeadersTest(BitcoinTestFramework): # getdata requests (the check is further down) inv_node.send_header_for_blocks(blocks) inv_node.sync_with_ping() - [ test_node.send_message(msg_block(x)) for x in blocks ] + [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() inv_node.sync_with_ping() # This block should not be announced to the inv node (since it also @@ -347,8 +367,8 @@ class SendHeadersTest(BitcoinTestFramework): assert "inv" not in inv_node.last_message assert "headers" not in inv_node.last_message tip = self.mine_blocks(1) - assert_equal(inv_node.check_last_announcement(inv=[tip]), True) - assert_equal(test_node.check_last_announcement(headers=[tip]), True) + inv_node.check_last_announcement(inv=[tip], headers=[]) + test_node.check_last_announcement(headers=[tip]) height += 1 block_time += 1 @@ -362,16 +382,16 @@ class SendHeadersTest(BitcoinTestFramework): # First try mining a reorg that can propagate with header announcement new_block_hashes = self.mine_reorg(length=7) tip = new_block_hashes[-1] - assert_equal(inv_node.check_last_announcement(inv=[tip]), True) - assert_equal(test_node.check_last_announcement(headers=new_block_hashes), True) + inv_node.check_last_announcement(inv=[tip], headers=[]) + test_node.check_last_announcement(headers=new_block_hashes) - block_time += 8 + block_time += 8 # Mine a too-large reorg, which should be announced with a single inv new_block_hashes = self.mine_reorg(length=8) tip = new_block_hashes[-1] - assert_equal(inv_node.check_last_announcement(inv=[tip]), True) - assert_equal(test_node.check_last_announcement(inv=[tip]), True) + inv_node.check_last_announcement(inv=[tip], headers=[]) + test_node.check_last_announcement(inv=[tip], headers=[]) block_time += 9 @@ -379,42 +399,42 @@ class SendHeadersTest(BitcoinTestFramework): fork_point = int(fork_point, 16) # Use getblocks/getdata - test_node.send_getblocks(locator = [fork_point]) - assert_equal(test_node.check_last_announcement(inv=new_block_hashes), True) - test_node.get_data(new_block_hashes) + test_node.send_getblocks(locator=[fork_point]) + test_node.check_last_announcement(inv=new_block_hashes, headers=[]) + test_node.send_get_data(new_block_hashes) test_node.wait_for_block(new_block_hashes[-1]) for i in range(3): # Mine another block, still should get only an inv tip = self.mine_blocks(1) - assert_equal(inv_node.check_last_announcement(inv=[tip]), True) - assert_equal(test_node.check_last_announcement(inv=[tip]), True) + inv_node.check_last_announcement(inv=[tip], headers=[]) + test_node.check_last_announcement(inv=[tip], headers=[]) if i == 0: # Just get the data -- shouldn't cause headers announcements to resume - test_node.get_data([tip]) + test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 1: # Send a getheaders message that shouldn't trigger headers announcements # to resume (best header sent will be too old) - test_node.get_headers(locator=[fork_point], hashstop=new_block_hashes[1]) - test_node.get_data([tip]) + test_node.send_get_headers(locator=[fork_point], hashstop=new_block_hashes[1]) + test_node.send_get_data([tip]) test_node.wait_for_block(tip) elif i == 2: - test_node.get_data([tip]) + test_node.send_get_data([tip]) test_node.wait_for_block(tip) # This time, try sending either a getheaders to trigger resumption - # of headers announcements, or mine a new block and inv it, also + # of headers announcements, or mine a new block and inv it, also # triggering resumption of headers announcements. if j == 0: - test_node.get_headers(locator=[tip], hashstop=0) + test_node.send_get_headers(locator=[tip], hashstop=0) test_node.sync_with_ping() else: test_node.send_block_inv(tip) test_node.sync_with_ping() # New blocks should now be announced with header tip = self.mine_blocks(1) - assert_equal(inv_node.check_last_announcement(inv=[tip]), True) - assert_equal(test_node.check_last_announcement(headers=[tip]), True) + inv_node.check_last_announcement(inv=[tip], headers=[]) + test_node.check_last_announcement(headers=[tip]) self.log.info("Part 3: success!") @@ -434,7 +454,7 @@ class SendHeadersTest(BitcoinTestFramework): height += 1 inv_node.send_message(msg_block(blocks[-1])) - inv_node.sync_with_ping() # Make sure blocks are processed + inv_node.sync_with_ping() # Make sure blocks are processed test_node.last_message.pop("getdata", None) test_node.send_header_for_blocks(blocks) test_node.sync_with_ping() @@ -453,9 +473,9 @@ class SendHeadersTest(BitcoinTestFramework): test_node.send_header_for_blocks(blocks) test_node.sync_with_ping() - test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=direct_fetch_response_time) + test_node.wait_for_getdata([x.sha256 for x in blocks], timeout=DIRECT_FETCH_RESPONSE_TIME) - [ test_node.send_message(msg_block(x)) for x in blocks ] + [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() @@ -484,13 +504,13 @@ class SendHeadersTest(BitcoinTestFramework): # both blocks (same work as tip) test_node.send_header_for_blocks(blocks[1:2]) test_node.sync_with_ping() - test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=direct_fetch_response_time) + test_node.wait_for_getdata([x.sha256 for x in blocks[0:2]], timeout=DIRECT_FETCH_RESPONSE_TIME) # Announcing 16 more headers should trigger direct fetch for 14 more # blocks test_node.send_header_for_blocks(blocks[2:18]) test_node.sync_with_ping() - test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=direct_fetch_response_time) + test_node.wait_for_getdata([x.sha256 for x in blocks[2:16]], timeout=DIRECT_FETCH_RESPONSE_TIME) # Announcing 1 more header should not trigger any response test_node.last_message.pop("getdata", None) @@ -502,7 +522,7 @@ class SendHeadersTest(BitcoinTestFramework): self.log.info("Part 4: success!") # Now deliver all those blocks we announced. - [ test_node.send_message(msg_block(x)) for x in blocks ] + [test_node.send_message(msg_block(x)) for x in blocks] self.log.info("Part 5: Testing handling of unconnecting headers") # First we test that receipt of an unconnecting header doesn't prevent @@ -524,7 +544,7 @@ class SendHeadersTest(BitcoinTestFramework): test_node.wait_for_getheaders() test_node.send_header_for_blocks(blocks) test_node.wait_for_getdata([x.sha256 for x in blocks]) - [ test_node.send_message(msg_block(x)) for x in blocks ] + [test_node.send_message(msg_block(x)) for x in blocks] test_node.sync_with_ping() assert_equal(int(self.nodes[0].getbestblockhash(), 16), blocks[1].sha256) @@ -532,7 +552,7 @@ class SendHeadersTest(BitcoinTestFramework): # Now we test that if we repeatedly don't send connecting headers, we # don't go into an infinite loop trying to get them to connect. MAX_UNCONNECTING_HEADERS = 10 - for j in range(MAX_UNCONNECTING_HEADERS+1): + for j in range(MAX_UNCONNECTING_HEADERS + 1): blocks.append(create_block(tip, create_coinbase(height), block_time)) blocks[-1].solve() tip = blocks[-1].sha256 @@ -554,11 +574,11 @@ class SendHeadersTest(BitcoinTestFramework): # Now try to see how many unconnecting headers we can send # before we get disconnected. Should be 5*MAX_UNCONNECTING_HEADERS - for i in range(5*MAX_UNCONNECTING_HEADERS - 1): + for i in range(5 * MAX_UNCONNECTING_HEADERS - 1): # Send a header that doesn't connect, check that we get a getheaders. with mininode_lock: test_node.last_message.pop("getheaders", None) - test_node.send_header_for_blocks([blocks[i%len(blocks)]]) + test_node.send_header_for_blocks([blocks[i % len(blocks)]]) test_node.wait_for_getheaders() # Eventually this stops working. diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 8df50474f3..4590b4c650 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -432,7 +432,7 @@ class BitcoinTestFramework(): self.disable_mocktime() for i in range(MAX_NODES): os.remove(log_filename(self.options.cachedir, i, "debug.log")) - os.remove(log_filename(self.options.cachedir, i, "db.log")) + os.remove(log_filename(self.options.cachedir, i, "wallets/db.log")) os.remove(log_filename(self.options.cachedir, i, "peers.dat")) os.remove(log_filename(self.options.cachedir, i, "fee_estimates.dat")) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ca36426a0a..d953e1585c 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -300,7 +300,11 @@ def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_cove if len(test_list) > 1 and jobs > 1: # Populate cache - subprocess.check_output([tests_dir + 'create_cache.py'] + flags + ["--tmpdir=%s/cache" % tmpdir]) + try: + subprocess.check_output([tests_dir + 'create_cache.py'] + flags + ["--tmpdir=%s/cache" % tmpdir]) + except Exception as e: + print(e.output) + raise e #Run Tests job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags) diff --git a/test/functional/wallet-hd.py b/test/functional/wallet-hd.py index 9b6ce68609..d21656a971 100755 --- a/test/functional/wallet-hd.py +++ b/test/functional/wallet-hd.py @@ -73,7 +73,7 @@ class WalletHDTest(BitcoinTestFramework): # otherwise node1 would auto-recover all funds in flag the keypool keys as used shutil.rmtree(os.path.join(tmpdir, "node1/regtest/blocks")) shutil.rmtree(os.path.join(tmpdir, "node1/regtest/chainstate")) - shutil.copyfile(os.path.join(tmpdir, "hd.bak"), os.path.join(tmpdir, "node1/regtest/wallet.dat")) + shutil.copyfile(os.path.join(tmpdir, "hd.bak"), os.path.join(tmpdir, "node1/regtest/wallets/wallet.dat")) self.start_node(1) # Assert that derivation is deterministic diff --git a/test/functional/walletbackup.py b/test/functional/walletbackup.py index 85a149793e..8ef5620cd8 100755 --- a/test/functional/walletbackup.py +++ b/test/functional/walletbackup.py @@ -90,9 +90,9 @@ class WalletBackupTest(BitcoinTestFramework): self.stop_node(2) def erase_three(self): - os.remove(self.options.tmpdir + "/node0/regtest/wallet.dat") - os.remove(self.options.tmpdir + "/node1/regtest/wallet.dat") - os.remove(self.options.tmpdir + "/node2/regtest/wallet.dat") + os.remove(self.options.tmpdir + "/node0/regtest/wallets/wallet.dat") + os.remove(self.options.tmpdir + "/node1/regtest/wallets/wallet.dat") + os.remove(self.options.tmpdir + "/node2/regtest/wallets/wallet.dat") def run_test(self): self.log.info("Generating initial blockchain") @@ -154,9 +154,9 @@ class WalletBackupTest(BitcoinTestFramework): shutil.rmtree(self.options.tmpdir + "/node2/regtest/chainstate") # Restore wallets from backup - shutil.copyfile(tmpdir + "/node0/wallet.bak", tmpdir + "/node0/regtest/wallet.dat") - shutil.copyfile(tmpdir + "/node1/wallet.bak", tmpdir + "/node1/regtest/wallet.dat") - shutil.copyfile(tmpdir + "/node2/wallet.bak", tmpdir + "/node2/regtest/wallet.dat") + shutil.copyfile(tmpdir + "/node0/wallet.bak", tmpdir + "/node0/regtest/wallets/wallet.dat") + shutil.copyfile(tmpdir + "/node1/wallet.bak", tmpdir + "/node1/regtest/wallets/wallet.dat") + shutil.copyfile(tmpdir + "/node2/wallet.bak", tmpdir + "/node2/regtest/wallets/wallet.dat") self.log.info("Re-starting nodes") self.start_three() @@ -192,10 +192,10 @@ class WalletBackupTest(BitcoinTestFramework): # Backup to source wallet file must fail sourcePaths = [ - tmpdir + "/node0/regtest/wallet.dat", - tmpdir + "/node0/./regtest/wallet.dat", - tmpdir + "/node0/regtest/", - tmpdir + "/node0/regtest"] + tmpdir + "/node0/regtest/wallets/wallet.dat", + tmpdir + "/node0/./regtest/wallets/wallet.dat", + tmpdir + "/node0/regtest/wallets/", + tmpdir + "/node0/regtest/wallets"] for sourcePath in sourcePaths: assert_raises_rpc_error(-4, "backup failed", self.nodes[0].backupwallet, sourcePath) |