aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--doc/dependencies.md2
-rw-r--r--doc/release-notes.md21
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/bitcoin-cli.cpp3
-rw-r--r--src/compat/byteswap.h6
-rw-r--r--src/init.cpp13
-rw-r--r--src/qt/intro.cpp5
-rw-r--r--src/qt/rpcconsole.cpp34
-rw-r--r--src/rpc/rawtransaction.cpp4
-rw-r--r--src/test/txvalidation_tests.cpp61
-rw-r--r--src/util.cpp5
-rw-r--r--src/wallet/db.cpp25
-rw-r--r--src/wallet/db.h4
-rw-r--r--src/wallet/init.cpp16
-rw-r--r--src/wallet/rpcwallet.cpp1
-rw-r--r--src/wallet/test/accounting_tests.cpp16
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp4
-rw-r--r--src/wallet/test/wallet_test_fixture.h4
-rw-r--r--src/wallet/test/wallet_tests.cpp2
-rw-r--r--src/wallet/walletdb.cpp8
-rw-r--r--src/wallet/walletdb.h4
-rw-r--r--src/wallet/walletutil.cpp27
-rw-r--r--src/wallet/walletutil.h13
-rwxr-xr-xtest/functional/bitcoin_cli.py5
-rwxr-xr-xtest/functional/keypool-topup.py4
-rwxr-xr-xtest/functional/multiwallet.py41
-rwxr-xr-xtest/functional/sendheaders.py262
-rwxr-xr-xtest/functional/test_framework/test_framework.py2
-rwxr-xr-xtest/functional/test_runner.py6
-rwxr-xr-xtest/functional/wallet-hd.py2
-rwxr-xr-xtest/functional/walletbackup.py20
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)