aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--depends/packages/qt.mk1
-rw-r--r--doc/productivity.md6
-rw-r--r--src/Makefile.test.include7
-rw-r--r--src/bitcoin-cli.cpp163
-rw-r--r--src/index/blockfilterindex.h1
-rw-r--r--src/init.cpp36
-rw-r--r--src/net_processing.cpp111
-rw-r--r--src/net_processing.h4
-rw-r--r--src/node/context.h9
-rw-r--r--src/protocol.cpp4
-rw-r--r--src/protocol.h15
-rw-r--r--src/rpc/blockchain.cpp6
-rw-r--r--src/rpc/blockchain.h2
-rw-r--r--src/rpc/mining.cpp21
-rw-r--r--src/rpc/request.cpp10
-rw-r--r--src/rpc/request.h2
-rw-r--r--src/test/blockfilter_index_tests.cpp8
-rw-r--r--src/test/denialofservice_tests.cpp10
-rw-r--r--src/test/fuzz/coins_view.cpp294
-rw-r--r--src/test/fuzz/fuzz.cpp2
-rw-r--r--src/test/fuzz/process_message.cpp4
-rw-r--r--src/test/fuzz/util.h12
-rw-r--r--src/test/miner_tests.cpp2
-rw-r--r--src/test/util/mining.cpp2
-rw-r--r--src/test/util/setup_common.cpp10
-rw-r--r--src/test/validation_block_tests.cpp14
-rw-r--r--src/validation.cpp47
-rw-r--r--src/validation.h89
-rw-r--r--src/wallet/test/wallet_tests.cpp6
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py51
-rwxr-xr-xtest/functional/p2p_blockfilters.py52
-rwxr-xr-xtest/functional/test_framework/messages.py53
-rwxr-xr-xtest/functional/test_framework/mininode.py3
34 files changed, 830 insertions, 229 deletions
diff --git a/Makefile.am b/Makefile.am
index 43790f1c23..45dab3930d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -41,7 +41,7 @@ OSX_DEPLOY_SCRIPT=$(top_srcdir)/contrib/macdeploy/macdeployqtplus
OSX_FANCY_PLIST=$(top_srcdir)/contrib/macdeploy/fancy.plist
OSX_INSTALLER_ICONS=$(top_srcdir)/src/qt/res/icons/bitcoin.icns
OSX_PLIST=$(top_builddir)/share/qt/Info.plist #not installed
-OSX_QT_TRANSLATIONS = da,de,es,hu,ru,uk,zh_CN,zh_TW
+OSX_QT_TRANSLATIONS = ar,bg,ca,cs,da,de,es,fa,fi,fr,gd,gl,he,hu,it,ja,ko,lt,lv,pl,pt,ru,sk,sl,sv,uk,zh_CN,zh_TW
DIST_CONTRIB = \
$(top_srcdir)/contrib/linearize/linearize-data.py \
diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk
index 366b1d0c42..631851855a 100644
--- a/depends/packages/qt.mk
+++ b/depends/packages/qt.mk
@@ -10,6 +10,7 @@ $(package)_build_subdir=qtbase
$(package)_qt_libs=corelib network widgets gui plugins testlib
$(package)_patches=fix_qt_pkgconfig.patch mac-qmake.conf fix_configure_mac.patch fix_no_printer.patch fix_rcc_determinism.patch fix_riscv64_arch.patch xkb-default.patch no-xlib.patch fix_android_qmake_conf.patch fix_android_jni_static.patch
+# Update OSX_QT_TRANSLATIONS when this is updated
$(package)_qttranslations_file_name=qttranslations-$($(package)_suffix)
$(package)_qttranslations_sha256_hash=fb5a47799754af73d3bf501fe513342cfe2fc37f64e80df5533f6110e804220c
diff --git a/doc/productivity.md b/doc/productivity.md
index 1bf3d9afb5..555f0afe3c 100644
--- a/doc/productivity.md
+++ b/doc/productivity.md
@@ -12,7 +12,7 @@ Table of Contents
* [Multiple working directories with `git worktrees`](#multiple-working-directories-with-git-worktrees)
* [Interactive "dummy rebases" for fixups and execs with `git merge-base`](#interactive-dummy-rebases-for-fixups-and-execs-with-git-merge-base)
* [Writing code](#writing-code)
- * [Format C/C++/Protobuf diffs with `clang-format-diff.py`](#format-ccprotobuf-diffs-with-clang-format-diffpy)
+ * [Format C/C++ diffs with `clang-format-diff.py`](#format-cc-diffs-with-clang-format-diffpy)
* [Format Python diffs with `yapf-diff.py`](#format-python-diffs-with-yapf-diffpy)
* [Rebasing/Merging code](#rebasingmerging-code)
* [More conflict context with `merge.conflictstyle diff3`](#more-conflict-context-with-mergeconflictstyle-diff3)
@@ -118,13 +118,13 @@ You can also set up [upstream refspecs](#reference-prs-easily-with-refspecs) to
Writing code
------------
-### Format C/C++/Protobuf diffs with `clang-format-diff.py`
+### Format C/C++ diffs with `clang-format-diff.py`
See [contrib/devtools/README.md](/contrib/devtools/README.md#clang-format-diff.py).
### Format Python diffs with `yapf-diff.py`
-Usage is exactly the same as [`clang-format-diff.py`](#format-ccprotobuf-diffs-with-clang-format-diffpy). You can get it [here](https://github.com/MarcoFalke/yapf-diff).
+Usage is exactly the same as [`clang-format-diff.py`](#format-cc-diffs-with-clang-format-diffpy). You can get it [here](https://github.com/MarcoFalke/yapf-diff).
Rebasing/Merging code
-------------
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 6ba0e9f270..2480cdadbb 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -31,6 +31,7 @@ FUZZ_TARGETS = \
test/fuzz/chain \
test/fuzz/checkqueue \
test/fuzz/coins_deserialize \
+ test/fuzz/coins_view \
test/fuzz/cuckoocache \
test/fuzz/decode_tx \
test/fuzz/descriptor_parse \
@@ -466,6 +467,12 @@ test_fuzz_coins_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_coins_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_coins_deserialize_SOURCES = test/fuzz/deserialize.cpp
+test_fuzz_coins_view_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_coins_view_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_coins_view_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_coins_view_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_coins_view_SOURCES = test/fuzz/coins_view.cpp
+
test_fuzz_cuckoocache_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_cuckoocache_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_cuckoocache_LDADD = $(FUZZ_SUITE_LD_COMMON)
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index cdaabd6fab..45a586cd12 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -9,6 +9,7 @@
#include <chainparamsbase.h>
#include <clientversion.h>
+#include <optional.h>
#include <rpc/client.h>
#include <rpc/protocol.h>
#include <rpc/request.h>
@@ -250,7 +251,7 @@ public:
UniValue ProcessReply(const UniValue &batch_in) override
{
UniValue result(UniValue::VOBJ);
- std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in, batch_in.size());
+ const std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in);
// Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass them on;
// getwalletinfo() and getbalances() are allowed to fail if there is no wallet.
if (!batch[ID_NETWORKINFO]["error"].isNull()) {
@@ -304,7 +305,7 @@ public:
}
};
-static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, const std::vector<std::string>& args)
+static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const Optional<std::string>& rpcwallet = {})
{
std::string host;
// In preference order, we choose the following for the port:
@@ -369,14 +370,12 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co
// check if we should use a special wallet endpoint
std::string endpoint = "/";
- if (!gArgs.GetArgs("-rpcwallet").empty()) {
- std::string walletName = gArgs.GetArg("-rpcwallet", "");
- char *encodedURI = evhttp_uriencode(walletName.data(), walletName.size(), false);
+ if (rpcwallet) {
+ char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
if (encodedURI) {
- endpoint = "/wallet/"+ std::string(encodedURI);
+ endpoint = "/wallet/" + std::string(encodedURI);
free(encodedURI);
- }
- else {
+ } else {
throw CConnectionFailed("uri-encode failed");
}
}
@@ -418,6 +417,65 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co
return reply;
}
+/**
+ * ConnectAndCallRPC wraps CallRPC with -rpcwait and an exception handler.
+ *
+ * @param[in] rh Pointer to RequestHandler.
+ * @param[in] strMethod Reference to const string method to forward to CallRPC.
+ * @param[in] rpcwallet Reference to const optional string wallet name to forward to CallRPC.
+ * @returns the RPC response as a UniValue object.
+ * @throws a CConnectionFailed std::runtime_error if connection failed or RPC server still in warmup.
+ */
+static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const Optional<std::string>& rpcwallet = {})
+{
+ UniValue response(UniValue::VOBJ);
+ // Execute and handle connection failures with -rpcwait.
+ const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
+ do {
+ try {
+ response = CallRPC(rh, strMethod, args, rpcwallet);
+ if (fWait) {
+ const UniValue& error = find_value(response, "error");
+ if (!error.isNull() && error["code"].get_int() == RPC_IN_WARMUP) {
+ throw CConnectionFailed("server in warmup");
+ }
+ }
+ break; // Connection succeeded, no need to retry.
+ } catch (const CConnectionFailed&) {
+ if (fWait) {
+ UninterruptibleSleep(std::chrono::milliseconds{1000});
+ } else {
+ throw;
+ }
+ }
+ } while (fWait);
+ return response;
+}
+
+/**
+ * GetWalletBalances calls listwallets; if more than one wallet is loaded, it then
+ * fetches mine.trusted balances for each loaded wallet and pushes them to `result`.
+ *
+ * @param result Reference to UniValue object the wallet names and balances are pushed to.
+ */
+static void GetWalletBalances(UniValue& result)
+{
+ std::unique_ptr<BaseRequestHandler> rh{MakeUnique<DefaultRequestHandler>()};
+ const UniValue listwallets = ConnectAndCallRPC(rh.get(), "listwallets", /* args=*/{});
+ if (!find_value(listwallets, "error").isNull()) return;
+ const UniValue& wallets = find_value(listwallets, "result");
+ if (wallets.size() <= 1) return;
+
+ UniValue balances(UniValue::VOBJ);
+ for (const UniValue& wallet : wallets.getValues()) {
+ const std::string wallet_name = wallet.get_str();
+ const UniValue getbalances = ConnectAndCallRPC(rh.get(), "getbalances", /* args=*/{}, wallet_name);
+ const UniValue& balance = find_value(getbalances, "result")["mine"]["trusted"];
+ balances.pushKV(wallet_name, balance);
+ }
+ result.pushKV("balances", balances);
+}
+
static int CommandLineRPC(int argc, char *argv[])
{
std::string strPrint;
@@ -474,9 +532,8 @@ static int CommandLineRPC(int argc, char *argv[])
}
std::unique_ptr<BaseRequestHandler> rh;
std::string method;
- if (gArgs.GetBoolArg("-getinfo", false)) {
+ if (gArgs.IsArgSet("-getinfo")) {
rh.reset(new GetinfoRequestHandler());
- method = "";
} else {
rh.reset(new DefaultRequestHandler());
if (args.size() < 1) {
@@ -485,62 +542,46 @@ static int CommandLineRPC(int argc, char *argv[])
method = args[0];
args.erase(args.begin()); // Remove trailing method name from arguments vector
}
-
- // Execute and handle connection failures with -rpcwait
- const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
- do {
- try {
- const UniValue reply = CallRPC(rh.get(), method, args);
-
- // Parse reply
- const UniValue& result = find_value(reply, "result");
- const UniValue& error = find_value(reply, "error");
-
- if (!error.isNull()) {
- // Error
- int code = error["code"].get_int();
- if (fWait && code == RPC_IN_WARMUP)
- throw CConnectionFailed("server in warmup");
- strPrint = "error: " + error.write();
- nRet = abs(code);
- if (error.isObject())
- {
- UniValue errCode = find_value(error, "code");
- UniValue errMsg = find_value(error, "message");
- strPrint = errCode.isNull() ? "" : "error code: "+errCode.getValStr()+"\n";
-
- if (errMsg.isStr())
- strPrint += "error message:\n"+errMsg.get_str();
-
- if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
- strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line.";
- }
- }
- } else {
- // Result
- if (result.isNull())
- strPrint = "";
- else if (result.isStr())
- strPrint = result.get_str();
- else
- strPrint = result.write(2);
+ Optional<std::string> wallet_name{};
+ if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", "");
+ const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name);
+
+ // Parse reply
+ UniValue result = find_value(reply, "result");
+ const UniValue& error = find_value(reply, "error");
+ if (!error.isNull()) {
+ // Error
+ strPrint = "error: " + error.write();
+ nRet = abs(error["code"].get_int());
+ if (error.isObject()) {
+ const UniValue& errCode = find_value(error, "code");
+ const UniValue& errMsg = find_value(error, "message");
+ strPrint = errCode.isNull() ? "" : ("error code: " + errCode.getValStr() + "\n");
+
+ if (errMsg.isStr()) {
+ strPrint += ("error message:\n" + errMsg.get_str());
+ }
+ if (errCode.isNum() && errCode.get_int() == RPC_WALLET_NOT_SPECIFIED) {
+ strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line.";
}
- // Connection succeeded, no need to retry.
- break;
}
- catch (const CConnectionFailed&) {
- if (fWait)
- UninterruptibleSleep(std::chrono::milliseconds{1000});
- else
- throw;
+ } else {
+ if (gArgs.IsArgSet("-getinfo") && !gArgs.IsArgSet("-rpcwallet")) {
+ GetWalletBalances(result); // fetch multiwallet balances and append to result
}
- } while (fWait);
- }
- catch (const std::exception& e) {
+ // Result
+ if (result.isNull()) {
+ strPrint = "";
+ } else if (result.isStr()) {
+ strPrint = result.get_str();
+ } else {
+ strPrint = result.write(2);
+ }
+ }
+ } catch (const std::exception& e) {
strPrint = std::string("error: ") + e.what();
nRet = EXIT_FAILURE;
- }
- catch (...) {
+ } catch (...) {
PrintExceptionContinue(nullptr, "CommandLineRPC()");
throw;
}
diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h
index 7ca43540c7..317f8c0e40 100644
--- a/src/index/blockfilterindex.h
+++ b/src/index/blockfilterindex.h
@@ -39,6 +39,7 @@ private:
size_t WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& filter);
Mutex m_cs_headers_cache;
+ /** cache of block hash to filter header, to avoid disk access when responding to getcfcheckpt. */
std::unordered_map<uint256, uint256, FilterHeaderHasher> m_headers_cache GUARDED_BY(m_cs_headers_cache);
protected:
diff --git a/src/init.cpp b/src/init.cpp
index 025ae06520..37e6251295 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -244,9 +244,9 @@ void Shutdown(NodeContext& node)
}
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
- {
+ if (node.chainman) {
LOCK(cs_main);
- for (CChainState* chainstate : g_chainman.GetAll()) {
+ for (CChainState* chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
}
@@ -271,9 +271,9 @@ void Shutdown(NodeContext& node)
// up with our current chain to avoid any strange pruning edge cases and make
// next startup faster by avoiding rescan.
- {
+ if (node.chainman) {
LOCK(cs_main);
- for (CChainState* chainstate : g_chainman.GetAll()) {
+ for (CChainState* chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
@@ -299,7 +299,8 @@ void Shutdown(NodeContext& node)
globalVerifyHandle.reset();
ECC_Stop();
node.args = nullptr;
- if (node.mempool) node.mempool = nullptr;
+ node.mempool = nullptr;
+ node.chainman = nullptr;
node.scheduler.reset();
try {
@@ -689,7 +690,7 @@ static void CleanupBlockRevFiles()
}
}
-static void ThreadImport(std::vector<fs::path> vImportFiles)
+static void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles)
{
const CChainParams& chainparams = Params();
util::ThreadRename("loadblk");
@@ -741,9 +742,9 @@ static void ThreadImport(std::vector<fs::path> vImportFiles)
// scan for better chains in the block chain database, that are not yet connected in the active best chain
// We can't hold cs_main during ActivateBestChain even though we're accessing
- // the g_chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
+ // the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
// the relevant pointers before the ABC call.
- for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
+ for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, chainparams, nullptr)) {
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
@@ -1377,8 +1378,11 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
// which are all started after this, may use it from the node context.
assert(!node.mempool);
node.mempool = &::mempool;
+ assert(!node.chainman);
+ node.chainman = &g_chainman;
+ ChainstateManager& chainman = EnsureChainman(node);
- node.peer_logic.reset(new PeerLogicValidation(node.connman.get(), node.banman.get(), *node.scheduler, *node.mempool));
+ node.peer_logic.reset(new PeerLogicValidation(node.connman.get(), node.banman.get(), *node.scheduler, *node.chainman, *node.mempool));
RegisterValidationInterface(node.peer_logic.get());
// sanitize comments per BIP-0014, format user agent and check total size
@@ -1557,7 +1561,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
const int64_t load_block_index_start_time = GetTimeMillis();
try {
LOCK(cs_main);
- g_chainman.InitializeChainstate();
+ chainman.InitializeChainstate();
UnloadBlockIndex();
// new CBlockTreeDB tries to delete the existing file, which
@@ -1578,7 +1582,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
// block file from disk.
// Note that it also sets fReindex based on the disk flag!
// From here on out fReindex and fReset mean something different!
- if (!LoadBlockIndex(chainparams)) {
+ if (!chainman.LoadBlockIndex(chainparams)) {
if (ShutdownRequested()) break;
strLoadError = _("Error loading block database");
break;
@@ -1612,7 +1616,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
bool failed_chainstate_init = false;
- for (CChainState* chainstate : g_chainman.GetAll()) {
+ for (CChainState* chainstate : chainman.GetAll()) {
LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
chainstate->InitCoinsDB(
/* cache_size_bytes */ nCoinDBCache,
@@ -1667,7 +1671,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
bool failed_rewind{false};
// Can't hold cs_main while calling RewindBlockIndex, so retrieve the relevant
// chainstates beforehand.
- for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
+ for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
if (!fReset) {
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
// It both disconnects blocks based on the chainstate, and drops block data in
@@ -1692,7 +1696,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
try {
LOCK(cs_main);
- for (CChainState* chainstate : g_chainman.GetAll()) {
+ for (CChainState* chainstate : chainman.GetAll()) {
if (!is_coinsview_empty(chainstate)) {
uiInterface.InitMessage(_("Verifying blocks...").translated);
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
@@ -1798,7 +1802,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
if (!fReindex) {
LOCK(cs_main);
- for (CChainState* chainstate : g_chainman.GetAll()) {
+ for (CChainState* chainstate : chainman.GetAll()) {
uiInterface.InitMessage(_("Pruning blockstore...").translated);
chainstate->PruneAndFlush();
}
@@ -1841,7 +1845,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
vImportFiles.push_back(strFile);
}
- threadGroup.create_thread(std::bind(&ThreadImport, vImportFiles));
+ threadGroup.create_thread([=, &chainman] { ThreadImport(chainman, vImportFiles); });
// Wait for genesis block to be processed
{
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index ac5300990f..159036a237 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -129,6 +129,8 @@ static constexpr unsigned int INVENTORY_BROADCAST_MAX = 7 * INVENTORY_BROADCAST_
static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
/** Maximum feefilter broadcast delay after significant change. */
static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60;
+/** Maximum number of cf hashes that may be requested with one getcfheaders. See BIP 157. */
+static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000;
struct COrphanTx {
// When modifying, adapt the copy of this definition in tests/DoS_tests.
@@ -1153,9 +1155,10 @@ static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Para
(GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT);
}
-PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, BanMan* banman, CScheduler& scheduler, CTxMemPool& pool)
+PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, BanMan* banman, CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool)
: connman(connmanIn),
m_banman(banman),
+ m_chainman(chainman),
m_mempool(pool),
m_stale_tip_check_time(0)
{
@@ -1738,7 +1741,7 @@ inline void static SendBlockTransactions(const CBlock& block, const BlockTransac
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp));
}
-bool static ProcessHeadersMessage(CNode* pfrom, CConnman* connman, CTxMemPool& mempool, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool via_compact_block)
+bool static ProcessHeadersMessage(CNode* pfrom, CConnman* connman, ChainstateManager& chainman, CTxMemPool& mempool, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool via_compact_block)
{
const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
size_t nCount = headers.size();
@@ -1798,7 +1801,7 @@ bool static ProcessHeadersMessage(CNode* pfrom, CConnman* connman, CTxMemPool& m
}
BlockValidationState state;
- if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast)) {
+ if (!chainman.ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast)) {
if (state.IsInvalid()) {
MaybePunishNodeForBlock(pfrom->GetId(), state, via_compact_block, "invalid header received");
return false;
@@ -1988,14 +1991,16 @@ void static ProcessOrphanTx(CConnman* connman, CTxMemPool& mempool, std::set<uin
* @param[in] pfrom The peer that we received the request from
* @param[in] chain_params Chain parameters
* @param[in] filter_type The filter type the request is for. Must be basic filters.
+ * @param[in] start_height The start height for the request
* @param[in] stop_hash The stop_hash for the request
+ * @param[in] max_height_diff The maximum number of items permitted to request, as specified in BIP 157
* @param[out] stop_index The CBlockIndex for the stop_hash block, if the request can be serviced.
* @param[out] filter_index The filter index, if the request can be serviced.
* @return True if the request can be serviced.
*/
static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_params,
- BlockFilterType filter_type,
- const uint256& stop_hash,
+ BlockFilterType filter_type, uint32_t start_height,
+ const uint256& stop_hash, uint32_t max_height_diff,
const CBlockIndex*& stop_index,
BlockFilterIndex*& filter_index)
{
@@ -2022,6 +2027,21 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
}
}
+ uint32_t stop_height = stop_index->nHeight;
+ if (start_height > stop_height) {
+ LogPrint(BCLog::NET, "peer %d sent invalid getcfilters/getcfheaders with " /* Continued */
+ "start height %d and stop height %d\n",
+ pfrom->GetId(), start_height, stop_height);
+ pfrom->fDisconnect = true;
+ return false;
+ }
+ if (stop_height - start_height >= max_height_diff) {
+ LogPrint(BCLog::NET, "peer %d requested too many cfilters/cfheaders: %d / %d\n",
+ pfrom->GetId(), stop_height - start_height + 1, max_height_diff);
+ pfrom->fDisconnect = true;
+ return false;
+ }
+
filter_index = GetBlockFilterIndex(filter_type);
if (!filter_index) {
LogPrint(BCLog::NET, "Filter index for supported type %s not found\n", BlockFilterTypeName(filter_type));
@@ -2032,6 +2052,61 @@ static bool PrepareBlockFilterRequest(CNode* pfrom, const CChainParams& chain_pa
}
/**
+ * Handle a cfheaders request.
+ *
+ * May disconnect from the peer in the case of a bad request.
+ *
+ * @param[in] pfrom The peer that we received the request from
+ * @param[in] vRecv The raw message received
+ * @param[in] chain_params Chain parameters
+ * @param[in] connman Pointer to the connection manager
+ */
+static void ProcessGetCFHeaders(CNode* pfrom, CDataStream& vRecv, const CChainParams& chain_params,
+ CConnman* connman)
+{
+ uint8_t filter_type_ser;
+ uint32_t start_height;
+ uint256 stop_hash;
+
+ vRecv >> filter_type_ser >> start_height >> stop_hash;
+
+ const BlockFilterType filter_type = static_cast<BlockFilterType>(filter_type_ser);
+
+ const CBlockIndex* stop_index;
+ BlockFilterIndex* filter_index;
+ if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, start_height, stop_hash,
+ MAX_GETCFHEADERS_SIZE, stop_index, filter_index)) {
+ return;
+ }
+
+ uint256 prev_header;
+ if (start_height > 0) {
+ const CBlockIndex* const prev_block =
+ stop_index->GetAncestor(static_cast<int>(start_height - 1));
+ if (!filter_index->LookupFilterHeader(prev_block, prev_header)) {
+ LogPrint(BCLog::NET, "Failed to find block filter header in index: filter_type=%s, block_hash=%s\n",
+ BlockFilterTypeName(filter_type), prev_block->GetBlockHash().ToString());
+ return;
+ }
+ }
+
+ std::vector<uint256> filter_hashes;
+ if (!filter_index->LookupFilterHashRange(start_height, stop_index, filter_hashes)) {
+ LogPrint(BCLog::NET, "Failed to find block filter hashes in index: filter_type=%s, start_height=%d, stop_hash=%s\n",
+ BlockFilterTypeName(filter_type), start_height, stop_hash.ToString());
+ return;
+ }
+
+ CSerializedNetMsg msg = CNetMsgMaker(pfrom->GetSendVersion())
+ .Make(NetMsgType::CFHEADERS,
+ filter_type_ser,
+ stop_index->GetBlockHash(),
+ prev_header,
+ filter_hashes);
+ connman->PushMessage(pfrom, std::move(msg));
+}
+
+/**
* Handle a getcfcheckpt request.
*
* May disconnect from the peer in the case of a bad request.
@@ -2053,7 +2128,8 @@ static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainPa
const CBlockIndex* stop_index;
BlockFilterIndex* filter_index;
- if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, stop_hash,
+ if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, /*start_height=*/0, stop_hash,
+ /*max_height_diff=*/std::numeric_limits<uint32_t>::max(),
stop_index, filter_index)) {
return;
}
@@ -2081,7 +2157,7 @@ static void ProcessGetCFCheckPt(CNode* pfrom, CDataStream& vRecv, const CChainPa
connman->PushMessage(pfrom, std::move(msg));
}
-bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CTxMemPool& mempool, CConnman* connman, BanMan* banman, const std::atomic<bool>& interruptMsgProc)
+bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, ChainstateManager& chainman, CTxMemPool& mempool, CConnman* connman, BanMan* banman, const std::atomic<bool>& interruptMsgProc)
{
LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(msg_type), vRecv.size(), pfrom->GetId());
if (gArgs.IsArgSet("-dropmessagestest") && GetRand(gArgs.GetArg("-dropmessagestest", 0)) == 0)
@@ -2848,7 +2924,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
const CBlockIndex *pindex = nullptr;
BlockValidationState state;
- if (!ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) {
+ if (!chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) {
if (state.IsInvalid()) {
MaybePunishNodeForBlock(pfrom->GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock");
return true;
@@ -2992,7 +3068,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
} // cs_main
if (fProcessBLOCKTXN)
- return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, mempool, connman, banman, interruptMsgProc);
+ return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, chainman, mempool, connman, banman, interruptMsgProc);
if (fRevertToHeaderProcessing) {
// Headers received from HB compact block peers are permitted to be
@@ -3000,7 +3076,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
// the peer if the header turns out to be for an invalid block.
// Note that if a peer tries to build on an invalid chain, that
// will be detected and the peer will be banned.
- return ProcessHeadersMessage(pfrom, connman, mempool, {cmpctblock.header}, chainparams, /*via_compact_block=*/true);
+ return ProcessHeadersMessage(pfrom, connman, chainman, mempool, {cmpctblock.header}, chainparams, /*via_compact_block=*/true);
}
if (fBlockReconstructed) {
@@ -3020,7 +3096,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
// we have a chain with at least nMinimumChainWork), and we ignore
// compact blocks with less work than our tip, it is safe to treat
// reconstructed compact blocks as having been requested.
- ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock);
+ chainman.ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock);
if (fNewBlock) {
pfrom->nLastBlockTime = GetTime();
} else {
@@ -3110,7 +3186,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
// disk-space attacks), but this should be safe due to the
// protections in the compact block handler -- see related comment
// in compact block optimistic reconstruction handling.
- ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock);
+ chainman.ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock);
if (fNewBlock) {
pfrom->nLastBlockTime = GetTime();
} else {
@@ -3144,7 +3220,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
}
- return ProcessHeadersMessage(pfrom, connman, mempool, headers, chainparams, /*via_compact_block=*/false);
+ return ProcessHeadersMessage(pfrom, connman, chainman, mempool, headers, chainparams, /*via_compact_block=*/false);
}
if (msg_type == NetMsgType::BLOCK)
@@ -3173,7 +3249,7 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
mapBlockSource.emplace(hash, std::make_pair(pfrom->GetId(), true));
}
bool fNewBlock = false;
- ProcessNewBlock(chainparams, pblock, forceProcessing, &fNewBlock);
+ chainman.ProcessNewBlock(chainparams, pblock, forceProcessing, &fNewBlock);
if (fNewBlock) {
pfrom->nLastBlockTime = GetTime();
} else {
@@ -3390,6 +3466,11 @@ bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRec
return true;
}
+ if (msg_type == NetMsgType::GETCFHEADERS) {
+ ProcessGetCFHeaders(pfrom, vRecv, chainparams, connman);
+ return true;
+ }
+
if (msg_type == NetMsgType::GETCFCHECKPT) {
ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman);
return true;
@@ -3534,7 +3615,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter
bool fRet = false;
try
{
- fRet = ProcessMessage(pfrom, msg_type, vRecv, msg.m_time, chainparams, m_mempool, connman, m_banman, interruptMsgProc);
+ fRet = ProcessMessage(pfrom, msg_type, vRecv, msg.m_time, chainparams, m_chainman, m_mempool, connman, m_banman, interruptMsgProc);
if (interruptMsgProc)
return false;
if (!pfrom->vRecvGetData.empty())
diff --git a/src/net_processing.h b/src/net_processing.h
index 4033c85d07..ec758c7537 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -12,6 +12,7 @@
#include <validationinterface.h>
class CTxMemPool;
+class ChainstateManager;
extern RecursiveMutex cs_main;
extern RecursiveMutex g_cs_orphans;
@@ -27,12 +28,13 @@ class PeerLogicValidation final : public CValidationInterface, public NetEventsI
private:
CConnman* const connman;
BanMan* const m_banman;
+ ChainstateManager& m_chainman;
CTxMemPool& m_mempool;
bool CheckIfBanned(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
public:
- PeerLogicValidation(CConnman* connman, BanMan* banman, CScheduler& scheduler, CTxMemPool& pool);
+ PeerLogicValidation(CConnman* connman, BanMan* banman, CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool);
/**
* Overridden from CValidationInterface.
diff --git a/src/node/context.h b/src/node/context.h
index 566ff170be..c45d9e6689 100644
--- a/src/node/context.h
+++ b/src/node/context.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_NODE_CONTEXT_H
#define BITCOIN_NODE_CONTEXT_H
+#include <cassert>
#include <memory>
#include <vector>
@@ -13,6 +14,7 @@ class BanMan;
class CConnman;
class CScheduler;
class CTxMemPool;
+class ChainstateManager;
class PeerLogicValidation;
namespace interfaces {
class Chain;
@@ -33,6 +35,7 @@ struct NodeContext {
std::unique_ptr<CConnman> connman;
CTxMemPool* mempool{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
std::unique_ptr<PeerLogicValidation> peer_logic;
+ ChainstateManager* chainman{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
std::unique_ptr<BanMan> banman;
ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
std::unique_ptr<interfaces::Chain> chain;
@@ -46,4 +49,10 @@ struct NodeContext {
~NodeContext();
};
+inline ChainstateManager& EnsureChainman(const NodeContext& node)
+{
+ assert(node.chainman);
+ return *node.chainman;
+}
+
#endif // BITCOIN_NODE_CONTEXT_H
diff --git a/src/protocol.cpp b/src/protocol.cpp
index e929cff110..243111c449 100644
--- a/src/protocol.cpp
+++ b/src/protocol.cpp
@@ -40,6 +40,8 @@ const char *SENDCMPCT="sendcmpct";
const char *CMPCTBLOCK="cmpctblock";
const char *GETBLOCKTXN="getblocktxn";
const char *BLOCKTXN="blocktxn";
+const char *GETCFHEADERS="getcfheaders";
+const char *CFHEADERS="cfheaders";
const char *GETCFCHECKPT="getcfcheckpt";
const char *CFCHECKPT="cfcheckpt";
} // namespace NetMsgType
@@ -73,6 +75,8 @@ const static std::string allNetMessageTypes[] = {
NetMsgType::CMPCTBLOCK,
NetMsgType::GETBLOCKTXN,
NetMsgType::BLOCKTXN,
+ NetMsgType::GETCFHEADERS,
+ NetMsgType::CFHEADERS,
NetMsgType::GETCFCHECKPT,
NetMsgType::CFCHECKPT,
};
diff --git a/src/protocol.h b/src/protocol.h
index 8092ad7c13..9527dce960 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -226,6 +226,19 @@ extern const char* GETBLOCKTXN;
*/
extern const char* BLOCKTXN;
/**
+ * getcfheaders requests a compact filter header and the filter hashes for a
+ * range of blocks, which can then be used to reconstruct the filter headers
+ * for those blocks.
+ * Only available with service bit NODE_COMPACT_FILTERS as described by
+ * BIP 157 & 158.
+ */
+extern const char* GETCFHEADERS;
+/**
+ * cfheaders is a response to a getcfheaders request containing a filter header
+ * and a vector of filter hashes for each subsequent block in the requested range.
+ */
+extern const char* CFHEADERS;
+/**
* getcfcheckpt requests evenly spaced compact filter headers, enabling
* parallelized download and validation of the headers between them.
* Only available with service bit NODE_COMPACT_FILTERS as described by
@@ -235,8 +248,6 @@ extern const char* GETCFCHECKPT;
/**
* cfcheckpt is a response to a getcfcheckpt request containing a vector of
* evenly spaced filter headers for blocks on the requested chain.
- * Only available with service bit NODE_COMPACT_FILTERS as described by
- * BIP 157 & 158.
*/
extern const char* CFCHECKPT;
}; // namespace NetMsgType
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index e5c25681d0..7d43de6646 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -71,6 +71,12 @@ CTxMemPool& EnsureMemPool(const util::Ref& context)
return *node.mempool;
}
+ChainstateManager& EnsureChainman(const util::Ref& context)
+{
+ NodeContext& node = EnsureNodeContext(context);
+ return EnsureChainman(node);
+}
+
/* Calculate the difficulty for a given block index.
*/
double GetDifficulty(const CBlockIndex* blockindex)
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index 453d0bc650..5c9a43b13e 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -16,6 +16,7 @@ extern RecursiveMutex cs_main;
class CBlock;
class CBlockIndex;
class CTxMemPool;
+class ChainstateManager;
class UniValue;
struct NodeContext;
namespace util {
@@ -52,5 +53,6 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
NodeContext& EnsureNodeContext(const util::Ref& context);
CTxMemPool& EnsureMemPool(const util::Ref& context);
+ChainstateManager& EnsureChainman(const util::Ref& context);
#endif
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index bcaed1ef88..3612f14bbf 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -101,7 +101,7 @@ static UniValue getnetworkhashps(const JSONRPCRequest& request)
return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1);
}
-static bool GenerateBlock(CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash)
+static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash)
{
block_hash.SetNull();
@@ -124,14 +124,15 @@ static bool GenerateBlock(CBlock& block, uint64_t& max_tries, unsigned int& extr
}
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
- if (!ProcessNewBlock(chainparams, shared_pblock, true, nullptr))
+ if (!chainman.ProcessNewBlock(chainparams, shared_pblock, true, nullptr)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
+ }
block_hash = block.GetHash();
return true;
}
-static UniValue generateBlocks(const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
+static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries)
{
int nHeightEnd = 0;
int nHeight = 0;
@@ -151,7 +152,7 @@ static UniValue generateBlocks(const CTxMemPool& mempool, const CScript& coinbas
CBlock *pblock = &pblocktemplate->block;
uint256 block_hash;
- if (!GenerateBlock(*pblock, nMaxTries, nExtraNonce, block_hash)) {
+ if (!GenerateBlock(chainman, *pblock, nMaxTries, nExtraNonce, block_hash)) {
break;
}
@@ -228,8 +229,9 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
}
const CTxMemPool& mempool = EnsureMemPool(request.context);
+ ChainstateManager& chainman = EnsureChainman(request.context);
- return generateBlocks(mempool, coinbase_script, num_blocks, max_tries);
+ return generateBlocks(chainman, mempool, coinbase_script, num_blocks, max_tries);
}
static UniValue generatetoaddress(const JSONRPCRequest& request)
@@ -266,10 +268,11 @@ static UniValue generatetoaddress(const JSONRPCRequest& request)
}
const CTxMemPool& mempool = EnsureMemPool(request.context);
+ ChainstateManager& chainman = EnsureChainman(request.context);
CScript coinbase_script = GetScriptForDestination(destination);
- return generateBlocks(mempool, coinbase_script, nGenerate, nMaxTries);
+ return generateBlocks(chainman, mempool, coinbase_script, nGenerate, nMaxTries);
}
static UniValue generateblock(const JSONRPCRequest& request)
@@ -370,7 +373,7 @@ static UniValue generateblock(const JSONRPCRequest& request)
uint64_t max_tries{1000000};
unsigned int extra_nonce{0};
- if (!GenerateBlock(block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {
+ if (!GenerateBlock(EnsureChainman(request.context), block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) {
throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block.");
}
@@ -947,7 +950,7 @@ static UniValue submitblock(const JSONRPCRequest& request)
bool new_block;
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
RegisterSharedValidationInterface(sc);
- bool accepted = ProcessNewBlock(Params(), blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block);
+ bool accepted = EnsureChainman(request.context).ProcessNewBlock(Params(), blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block);
UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
return "duplicate";
@@ -986,7 +989,7 @@ static UniValue submitheader(const JSONRPCRequest& request)
}
BlockValidationState state;
- ProcessNewBlockHeaders({h}, state, Params());
+ EnsureChainman(request.context).ProcessNewBlockHeaders({h}, state, Params());
if (state.IsValid()) return NullUniValue;
if (state.IsError()) {
throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString());
diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp
index 56cac6661e..7fef45f50e 100644
--- a/src/rpc/request.cpp
+++ b/src/rpc/request.cpp
@@ -130,20 +130,20 @@ void DeleteAuthCookie()
}
}
-std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num)
+std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in)
{
if (!in.isArray()) {
throw std::runtime_error("Batch must be an array");
}
+ const size_t num {in.size()};
std::vector<UniValue> batch(num);
- for (size_t i=0; i<in.size(); ++i) {
- const UniValue &rec = in[i];
+ for (const UniValue& rec : in.getValues()) {
if (!rec.isObject()) {
- throw std::runtime_error("Batch member must be object");
+ throw std::runtime_error("Batch member must be an object");
}
size_t id = rec["id"].get_int();
if (id >= num) {
- throw std::runtime_error("Batch member id larger than size");
+ throw std::runtime_error("Batch member id is larger than batch size");
}
batch[id] = rec;
}
diff --git a/src/rpc/request.h b/src/rpc/request.h
index 0a15b48de4..02ec5393a7 100644
--- a/src/rpc/request.h
+++ b/src/rpc/request.h
@@ -26,7 +26,7 @@ bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */
void DeleteAuthCookie();
/** Parse JSON-RPC batch reply into a vector */
-std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num);
+std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in);
class JSONRPCRequest
{
diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp
index e5043f6816..7dff2e6e86 100644
--- a/src/test/blockfilter_index_tests.cpp
+++ b/src/test/blockfilter_index_tests.cpp
@@ -94,7 +94,7 @@ bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
CBlockHeader header = block->GetBlockHeader();
BlockValidationState state;
- if (!ProcessNewBlockHeaders({header}, state, Params(), &pindex)) {
+ if (!EnsureChainman(m_node).ProcessNewBlockHeaders({header}, state, Params(), &pindex)) {
return false;
}
}
@@ -171,7 +171,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
uint256 chainA_last_header = last_header;
for (size_t i = 0; i < 2; i++) {
const auto& block = chainA[i];
- BOOST_REQUIRE(ProcessNewBlock(Params(), block, true, nullptr));
+ BOOST_REQUIRE(EnsureChainman(m_node).ProcessNewBlock(Params(), block, true, nullptr));
}
for (size_t i = 0; i < 2; i++) {
const auto& block = chainA[i];
@@ -189,7 +189,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
uint256 chainB_last_header = last_header;
for (size_t i = 0; i < 3; i++) {
const auto& block = chainB[i];
- BOOST_REQUIRE(ProcessNewBlock(Params(), block, true, nullptr));
+ BOOST_REQUIRE(EnsureChainman(m_node).ProcessNewBlock(Params(), block, true, nullptr));
}
for (size_t i = 0; i < 3; i++) {
const auto& block = chainB[i];
@@ -220,7 +220,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
// Reorg back to chain A.
for (size_t i = 2; i < 4; i++) {
const auto& block = chainA[i];
- BOOST_REQUIRE(ProcessNewBlock(Params(), block, true, nullptr));
+ BOOST_REQUIRE(EnsureChainman(m_node).ProcessNewBlock(Params(), block, true, nullptr));
}
// Check that chain A and B blocks can be retrieved.
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index 75b38670c9..348b170536 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -78,7 +78,7 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)
BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
{
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, *m_node.scheduler, *m_node.mempool);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, *m_node.scheduler, *m_node.chainman, *m_node.mempool);
// Mock an outbound peer
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
@@ -148,7 +148,7 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidat
BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
{
auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, *m_node.scheduler, *m_node.mempool);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, *m_node.scheduler, *m_node.chainman, *m_node.mempool);
const Consensus::Params& consensusParams = Params().GetConsensus();
constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS;
@@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE(DoS_banning)
{
auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler, *m_node.mempool);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);
banman->ClearBanned();
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
@@ -276,7 +276,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore)
{
auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler, *m_node.mempool);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);
banman->ClearBanned();
gArgs.ForceSetArg("-banscore", "111"); // because 11 is my favorite number
@@ -323,7 +323,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
{
auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
auto connman = MakeUnique<CConnman>(0x1337, 0x1337);
- auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler, *m_node.mempool);
+ auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);
banman->ClearBanned();
int64_t nStartTime = GetTime();
diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp
new file mode 100644
index 0000000000..52dd62a145
--- /dev/null
+++ b/src/test/fuzz/coins_view.cpp
@@ -0,0 +1,294 @@
+// Copyright (c) 2020 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 <amount.h>
+#include <chainparams.h>
+#include <chainparamsbase.h>
+#include <coins.h>
+#include <consensus/tx_verify.h>
+#include <consensus/validation.h>
+#include <key.h>
+#include <node/coinstats.h>
+#include <policy/policy.h>
+#include <primitives/transaction.h>
+#include <pubkey.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+#include <validation.h>
+
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace {
+const Coin EMPTY_COIN{};
+
+bool operator==(const Coin& a, const Coin& b)
+{
+ if (a.IsSpent() && b.IsSpent()) return true;
+ return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.out == b.out;
+}
+} // namespace
+
+void initialize()
+{
+ static const ECCVerifyHandle ecc_verify_handle;
+ ECC_Start();
+ SelectParams(CBaseChainParams::REGTEST);
+}
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
+ CCoinsView backend_coins_view;
+ CCoinsViewCache coins_view_cache{&backend_coins_view};
+ COutPoint random_out_point;
+ Coin random_coin;
+ CMutableTransaction random_mutable_transaction;
+ while (fuzzed_data_provider.ConsumeBool()) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 9)) {
+ case 0: {
+ if (random_coin.IsSpent()) {
+ break;
+ }
+ Coin coin = random_coin;
+ bool expected_code_path = false;
+ const bool possible_overwrite = fuzzed_data_provider.ConsumeBool();
+ try {
+ coins_view_cache.AddCoin(random_out_point, std::move(coin), possible_overwrite);
+ expected_code_path = true;
+ } catch (const std::logic_error& e) {
+ if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) {
+ assert(!possible_overwrite);
+ expected_code_path = true;
+ }
+ }
+ assert(expected_code_path);
+ break;
+ }
+ case 1: {
+ (void)coins_view_cache.Flush();
+ break;
+ }
+ case 2: {
+ coins_view_cache.SetBestBlock(ConsumeUInt256(fuzzed_data_provider));
+ break;
+ }
+ case 3: {
+ Coin move_to;
+ (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
+ break;
+ }
+ case 4: {
+ coins_view_cache.Uncache(random_out_point);
+ break;
+ }
+ case 5: {
+ if (fuzzed_data_provider.ConsumeBool()) {
+ backend_coins_view = CCoinsView{};
+ }
+ coins_view_cache.SetBackend(backend_coins_view);
+ break;
+ }
+ case 6: {
+ const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
+ if (!opt_out_point) {
+ break;
+ }
+ random_out_point = *opt_out_point;
+ break;
+ }
+ case 7: {
+ const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
+ if (!opt_coin) {
+ break;
+ }
+ random_coin = *opt_coin;
+ break;
+ }
+ case 8: {
+ const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
+ if (!opt_mutable_transaction) {
+ break;
+ }
+ random_mutable_transaction = *opt_mutable_transaction;
+ break;
+ }
+ case 9: {
+ CCoinsMap coins_map;
+ while (fuzzed_data_provider.ConsumeBool()) {
+ CCoinsCacheEntry coins_cache_entry;
+ coins_cache_entry.flags = fuzzed_data_provider.ConsumeIntegral<unsigned char>();
+ if (fuzzed_data_provider.ConsumeBool()) {
+ coins_cache_entry.coin = random_coin;
+ } else {
+ const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
+ if (!opt_coin) {
+ break;
+ }
+ coins_cache_entry.coin = *opt_coin;
+ }
+ coins_map.emplace(random_out_point, std::move(coins_cache_entry));
+ }
+ bool expected_code_path = false;
+ try {
+ coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock());
+ expected_code_path = true;
+ } catch (const std::logic_error& e) {
+ if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) {
+ expected_code_path = true;
+ }
+ }
+ assert(expected_code_path);
+ break;
+ }
+ }
+ }
+
+ {
+ const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point);
+ const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
+ const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
+ const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
+ Coin coin_using_get_coin;
+ const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin);
+ if (exists_using_get_coin) {
+ assert(coin_using_get_coin == coin_using_access_coin);
+ }
+ assert((exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin && exists_using_get_coin) ||
+ (!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin && !exists_using_get_coin));
+ const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point);
+ if (exists_using_have_coin_in_backend) {
+ assert(exists_using_have_coin);
+ }
+ Coin coin_using_backend_get_coin;
+ if (backend_coins_view.GetCoin(random_out_point, coin_using_backend_get_coin)) {
+ assert(exists_using_have_coin_in_backend);
+ assert(coin_using_get_coin == coin_using_backend_get_coin);
+ } else {
+ assert(!exists_using_have_coin_in_backend);
+ }
+ }
+
+ {
+ bool expected_code_path = false;
+ try {
+ (void)coins_view_cache.Cursor();
+ } catch (const std::logic_error&) {
+ expected_code_path = true;
+ }
+ assert(expected_code_path);
+ (void)coins_view_cache.DynamicMemoryUsage();
+ (void)coins_view_cache.EstimateSize();
+ (void)coins_view_cache.GetBestBlock();
+ (void)coins_view_cache.GetCacheSize();
+ (void)coins_view_cache.GetHeadBlocks();
+ (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction});
+ }
+
+ {
+ const CCoinsViewCursor* coins_view_cursor = backend_coins_view.Cursor();
+ assert(coins_view_cursor == nullptr);
+ (void)backend_coins_view.EstimateSize();
+ (void)backend_coins_view.GetBestBlock();
+ (void)backend_coins_view.GetHeadBlocks();
+ }
+
+ if (fuzzed_data_provider.ConsumeBool()) {
+ switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 6)) {
+ case 0: {
+ const CTransaction transaction{random_mutable_transaction};
+ bool is_spent = false;
+ for (const CTxOut& tx_out : transaction.vout) {
+ if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
+ is_spent = true;
+ }
+ }
+ if (is_spent) {
+ // Avoid:
+ // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
+ break;
+ }
+ bool expected_code_path = false;
+ const int height = fuzzed_data_provider.ConsumeIntegral<int>();
+ const bool possible_overwrite = fuzzed_data_provider.ConsumeBool();
+ try {
+ AddCoins(coins_view_cache, transaction, height, possible_overwrite);
+ expected_code_path = true;
+ } catch (const std::logic_error& e) {
+ if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) {
+ assert(!possible_overwrite);
+ expected_code_path = true;
+ }
+ }
+ assert(expected_code_path);
+ break;
+ }
+ case 1: {
+ (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
+ break;
+ }
+ case 2: {
+ TxValidationState state;
+ CAmount tx_fee_out;
+ const CTransaction transaction{random_mutable_transaction};
+ if (ContainsSpentInput(transaction, coins_view_cache)) {
+ // Avoid:
+ // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed.
+ break;
+ }
+ try {
+ (void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out);
+ assert(MoneyRange(tx_fee_out));
+ } catch (const std::runtime_error&) {
+ }
+ break;
+ }
+ case 3: {
+ const CTransaction transaction{random_mutable_transaction};
+ if (ContainsSpentInput(transaction, coins_view_cache)) {
+ // Avoid:
+ // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
+ break;
+ }
+ (void)GetP2SHSigOpCount(transaction, coins_view_cache);
+ break;
+ }
+ case 4: {
+ const CTransaction transaction{random_mutable_transaction};
+ if (ContainsSpentInput(transaction, coins_view_cache)) {
+ // Avoid:
+ // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
+ break;
+ }
+ const int flags = fuzzed_data_provider.ConsumeIntegral<int>();
+ if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
+ // Avoid:
+ // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
+ break;
+ }
+ (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
+ break;
+ }
+ case 5: {
+ CCoinsStats stats;
+ bool expected_code_path = false;
+ try {
+ (void)GetUTXOStats(&coins_view_cache, stats);
+ } catch (const std::logic_error&) {
+ expected_code_path = true;
+ }
+ assert(expected_code_path);
+ break;
+ }
+ case 6: {
+ (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
+ break;
+ }
+ }
+ }
+}
diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp
index 6e2188fe86..82e1d55c0b 100644
--- a/src/test/fuzz/fuzz.cpp
+++ b/src/test/fuzz/fuzz.cpp
@@ -19,8 +19,6 @@ static bool read_stdin(std::vector<uint8_t>& data)
ssize_t length = 0;
while ((length = read(STDIN_FILENO, buffer, 1024)) > 0) {
data.insert(data.end(), buffer, buffer + length);
-
- if (data.size() > (1 << 20)) return false;
}
return length == 0;
}
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index c03365199a..665a6224b4 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -29,7 +29,7 @@
#include <string>
#include <vector>
-bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CTxMemPool& mempool, CConnman* connman, BanMan* banman, const std::atomic<bool>& interruptMsgProc);
+bool ProcessMessage(CNode* pfrom, const std::string& msg_type, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, ChainstateManager& chainman, CTxMemPool& mempool, CConnman* connman, BanMan* banman, const std::atomic<bool>& interruptMsgProc);
namespace {
@@ -74,7 +74,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
p2p_node.SetSendVersion(PROTOCOL_VERSION);
g_setup->m_node.peer_logic->InitializeNode(&p2p_node);
try {
- (void)ProcessMessage(&p2p_node, random_message_type, random_bytes_data_stream, GetTimeMillis(), Params(), *g_setup->m_node.mempool, g_setup->m_node.connman.get(), g_setup->m_node.banman.get(), std::atomic<bool>{false});
+ (void)ProcessMessage(&p2p_node, random_message_type, random_bytes_data_stream, GetTimeMillis(), Params(), *g_setup->m_node.chainman, *g_setup->m_node.mempool, g_setup->m_node.connman.get(), g_setup->m_node.banman.get(), std::atomic<bool>{false});
} catch (const std::ios_base::failure&) {
}
SyncWithValidationInterfaceQueue();
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index f72d9380eb..9d0fb02128 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -8,6 +8,7 @@
#include <amount.h>
#include <arith_uint256.h>
#include <attributes.h>
+#include <coins.h>
#include <consensus/consensus.h>
#include <primitives/transaction.h>
#include <script/script.h>
@@ -149,4 +150,15 @@ NODISCARD bool AdditionOverflow(const T i, const T j) noexcept
return std::numeric_limits<T>::max() - i < j;
}
+NODISCARD inline bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) noexcept
+{
+ for (const CTxIn& tx_in : tx.vin) {
+ const Coin& coin = inputs.AccessCoin(tx_in.prevout);
+ if (coin.IsSpent()) {
+ return true;
+ }
+ }
+ return false;
+}
+
#endif // BITCOIN_TEST_FUZZ_UTIL_H
diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp
index 9f3ca87206..57eee94330 100644
--- a/src/test/miner_tests.cpp
+++ b/src/test/miner_tests.cpp
@@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
pblock->nNonce = blockinfo[i].nonce;
}
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
- BOOST_CHECK(ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
+ BOOST_CHECK(EnsureChainman(m_node).ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
pblock->hashPrevBlock = pblock->GetHash();
}
diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp
index 1df6844062..dac7f1a07b 100644
--- a/src/test/util/mining.cpp
+++ b/src/test/util/mining.cpp
@@ -31,7 +31,7 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
assert(block->nNonce);
}
- bool processed{ProcessNewBlock(Params(), block, true, nullptr)};
+ bool processed{EnsureChainman(node).ProcessNewBlock(Params(), block, true, nullptr)};
assert(processed);
return CTxIn{block->vtx[0]->GetHash(), 0};
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index 86dac55b2b..3b7a7c8d12 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -134,7 +134,8 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
pblocktree.reset(new CBlockTreeDB(1 << 20, true));
- g_chainman.InitializeChainstate();
+ m_node.chainman = &::g_chainman;
+ m_node.chainman->InitializeChainstate();
::ChainstateActive().InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
assert(!::ChainstateActive().CanFlushToDisk());
@@ -160,7 +161,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
m_node.mempool->setSanityCheck(1.0);
m_node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME);
m_node.connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests.
- m_node.peer_logic = MakeUnique<PeerLogicValidation>(m_node.connman.get(), m_node.banman.get(), *m_node.scheduler, *m_node.mempool);
+ m_node.peer_logic = MakeUnique<PeerLogicValidation>(m_node.connman.get(), m_node.banman.get(), *m_node.scheduler, *m_node.chainman, *m_node.mempool);
{
CConnman::Options options;
options.m_msgproc = m_node.peer_logic.get();
@@ -181,7 +182,8 @@ TestingSetup::~TestingSetup()
m_node.mempool = nullptr;
m_node.scheduler.reset();
UnloadBlockIndex();
- g_chainman.Reset();
+ m_node.chainman->Reset();
+ m_node.chainman = nullptr;
pblocktree.reset();
}
@@ -226,7 +228,7 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa
while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce;
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
- ProcessNewBlock(chainparams, shared_pblock, true, nullptr);
+ EnsureChainman(m_node).ProcessNewBlock(chainparams, shared_pblock, true, nullptr);
CBlock result = block;
return result;
diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp
index 899f054b83..45e0c5484e 100644
--- a/src/test/validation_block_tests.cpp
+++ b/src/test/validation_block_tests.cpp
@@ -163,10 +163,10 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
std::transform(blocks.begin(), blocks.end(), std::back_inserter(headers), [](std::shared_ptr<const CBlock> b) { return b->GetBlockHeader(); });
// Process all the headers so we understand the toplogy of the chain
- BOOST_CHECK(ProcessNewBlockHeaders(headers, state, Params()));
+ BOOST_CHECK(EnsureChainman(m_node).ProcessNewBlockHeaders(headers, state, Params()));
// Connect the genesis block and drain any outstanding events
- BOOST_CHECK(ProcessNewBlock(Params(), std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored));
+ BOOST_CHECK(EnsureChainman(m_node).ProcessNewBlock(Params(), std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored));
SyncWithValidationInterfaceQueue();
// subscribe to events (this subscriber will validate event ordering)
@@ -183,18 +183,18 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
// will subscribe to events generated during block validation and assert on ordering invariance
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
- threads.emplace_back([&blocks]() {
+ threads.emplace_back([&]() {
bool ignored;
FastRandomContext insecure;
for (int i = 0; i < 1000; i++) {
auto block = blocks[insecure.randrange(blocks.size() - 1)];
- ProcessNewBlock(Params(), block, true, &ignored);
+ EnsureChainman(m_node).ProcessNewBlock(Params(), block, true, &ignored);
}
// to make sure that eventually we process the full chain - do it here
for (auto block : blocks) {
if (block->vtx.size() == 1) {
- bool processed = ProcessNewBlock(Params(), block, true, &ignored);
+ bool processed = EnsureChainman(m_node).ProcessNewBlock(Params(), block, true, &ignored);
assert(processed);
}
}
@@ -232,8 +232,8 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
{
bool ignored;
- auto ProcessBlock = [&ignored](std::shared_ptr<const CBlock> block) -> bool {
- return ProcessNewBlock(Params(), block, /* fForceProcessing */ true, /* fNewBlock */ &ignored);
+ auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool {
+ return EnsureChainman(m_node).ProcessNewBlock(Params(), block, /* fForceProcessing */ true, /* fNewBlock */ &ignored);
};
// Process all mined blocks
diff --git a/src/validation.cpp b/src/validation.cpp
index a9dfa5c171..dbdf5028fd 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -196,8 +196,8 @@ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& loc
std::unique_ptr<CBlockTreeDB> pblocktree;
// See definition for documentation
-static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight);
-static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight);
+static void FindFilesToPruneManual(ChainstateManager& chainman, std::set<int>& setFilesToPrune, int nManualPruneHeight);
+static void FindFilesToPrune(ChainstateManager& chainman, std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight);
bool CheckInputScripts(const CTransaction& tx, TxValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr);
static FILE* OpenUndoFile(const FlatFilePos &pos, bool fReadOnly = false);
static FlatFileSeq BlockFileSeq();
@@ -2282,11 +2282,11 @@ bool CChainState::FlushStateToDisk(
if (nManualPruneHeight > 0) {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
- FindFilesToPruneManual(setFilesToPrune, nManualPruneHeight);
+ FindFilesToPruneManual(g_chainman, setFilesToPrune, nManualPruneHeight);
} else {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
- FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight());
+ FindFilesToPrune(g_chainman, setFilesToPrune, chainparams.PruneAfterHeight());
fCheckForPruning = false;
}
if (!setFilesToPrune.empty()) {
@@ -3691,13 +3691,14 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationS
}
// Exposed wrapper for AcceptBlockHeader
-bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex)
+bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex)
{
+ AssertLockNotHeld(cs_main);
{
LOCK(cs_main);
for (const CBlockHeader& header : headers) {
CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast
- bool accepted = g_chainman.m_blockman.AcceptBlockHeader(
+ bool accepted = m_blockman.AcceptBlockHeader(
header, state, chainparams, &pindex);
::ChainstateActive().CheckBlockIndex(chainparams.GetConsensus());
@@ -3819,7 +3820,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
return true;
}
-bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool *fNewBlock)
+bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock)
{
AssertLockNotHeld(cs_main);
@@ -3895,12 +3896,12 @@ uint64_t CalculateCurrentUsage()
return retval;
}
-/* Prune a block file (modify associated database entries)*/
-void PruneOneBlockFile(const int fileNumber)
+void ChainstateManager::PruneOneBlockFile(const int fileNumber)
{
+ AssertLockHeld(cs_main);
LOCK(cs_LastBlockFile);
- for (const auto& entry : g_chainman.BlockIndex()) {
+ for (const auto& entry : m_blockman.m_block_index) {
CBlockIndex* pindex = entry.second;
if (pindex->nFile == fileNumber) {
pindex->nStatus &= ~BLOCK_HAVE_DATA;
@@ -3914,12 +3915,12 @@ void PruneOneBlockFile(const int fileNumber)
// to be downloaded again in order to consider its chain, at which
// point it would be considered as a candidate for
// m_blocks_unlinked or setBlockIndexCandidates.
- auto range = g_chainman.m_blockman.m_blocks_unlinked.equal_range(pindex->pprev);
+ auto range = m_blockman.m_blocks_unlinked.equal_range(pindex->pprev);
while (range.first != range.second) {
std::multimap<CBlockIndex *, CBlockIndex *>::iterator _it = range.first;
range.first++;
if (_it->second == pindex) {
- g_chainman.m_blockman.m_blocks_unlinked.erase(_it);
+ m_blockman.m_blocks_unlinked.erase(_it);
}
}
}
@@ -3941,7 +3942,7 @@ void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune)
}
/* Calculate the block/rev files to delete based on height specified by user with RPC command pruneblockchain */
-static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight)
+static void FindFilesToPruneManual(ChainstateManager& chainman, std::set<int>& setFilesToPrune, int nManualPruneHeight)
{
assert(fPruneMode && nManualPruneHeight > 0);
@@ -3955,7 +3956,7 @@ static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPr
for (int fileNumber = 0; fileNumber < nLastBlockFile; fileNumber++) {
if (vinfoBlockFile[fileNumber].nSize == 0 || vinfoBlockFile[fileNumber].nHeightLast > nLastBlockWeCanPrune)
continue;
- PruneOneBlockFile(fileNumber);
+ chainman.PruneOneBlockFile(fileNumber);
setFilesToPrune.insert(fileNumber);
count++;
}
@@ -3988,7 +3989,7 @@ void PruneBlockFilesManual(int nManualPruneHeight)
*
* @param[out] setFilesToPrune The set of file indices that can be unlinked will be returned
*/
-static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight)
+static void FindFilesToPrune(ChainstateManager& chainman, std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight)
{
LOCK2(cs_main, cs_LastBlockFile);
if (::ChainActive().Tip() == nullptr || nPruneTarget == 0) {
@@ -4030,7 +4031,7 @@ static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfte
if (vinfoBlockFile[fileNumber].nHeightLast > nLastBlockWeCanPrune)
continue;
- PruneOneBlockFile(fileNumber);
+ chainman.PruneOneBlockFile(fileNumber);
// Queue up the files for removal
setFilesToPrune.insert(fileNumber);
nCurrentUsage -= nBytesToPrune;
@@ -4154,9 +4155,9 @@ void BlockManager::Unload() {
m_block_index.clear();
}
-bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+bool static LoadBlockIndexDB(ChainstateManager& chainman, const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
- if (!g_chainman.m_blockman.LoadBlockIndex(
+ if (!chainman.m_blockman.LoadBlockIndex(
chainparams.GetConsensus(), *pblocktree,
::ChainstateActive().setBlockIndexCandidates)) {
return false;
@@ -4182,8 +4183,7 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_RE
// Check presence of blk files
LogPrintf("Checking all blk files are present...\n");
std::set<int> setBlkDataFiles;
- for (const std::pair<const uint256, CBlockIndex*>& item : g_chainman.BlockIndex())
- {
+ for (const std::pair<const uint256, CBlockIndex*>& item : chainman.BlockIndex()) {
CBlockIndex* pindex = item.second;
if (pindex->nStatus & BLOCK_HAVE_DATA) {
setBlkDataFiles.insert(pindex->nFile);
@@ -4600,14 +4600,15 @@ void UnloadBlockIndex()
fHavePruned = false;
}
-bool LoadBlockIndex(const CChainParams& chainparams)
+bool ChainstateManager::LoadBlockIndex(const CChainParams& chainparams)
{
+ AssertLockHeld(cs_main);
// Load block index from databases
bool needs_init = fReindex;
if (!fReindex) {
- bool ret = LoadBlockIndexDB(chainparams);
+ bool ret = LoadBlockIndexDB(*this, chainparams);
if (!ret) return false;
- needs_init = g_chainman.m_blockman.m_block_index.empty();
+ needs_init = m_blockman.m_block_index.empty();
}
if (needs_init) {
diff --git a/src/validation.h b/src/validation.h
index cbab65e79e..8112e38704 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -43,6 +43,7 @@ class CConnman;
class CScriptCheck;
class CBlockPolicyEstimator;
class CTxMemPool;
+class ChainstateManager;
class TxValidationState;
struct ChainTxData;
@@ -149,41 +150,6 @@ extern bool fPruneMode;
/** Number of MiB of block files that we're trying to stay below. */
extern uint64_t nPruneTarget;
-/**
- * Process an incoming block. This only returns after the best known valid
- * block is made active. Note that it does not, however, guarantee that the
- * specific block passed to it has been checked for validity!
- *
- * If you want to *possibly* get feedback on whether pblock is valid, you must
- * install a CValidationInterface (see validationinterface.h) - this will have
- * its BlockChecked method called whenever *any* block completes validation.
- *
- * Note that we guarantee that either the proof-of-work is valid on pblock, or
- * (and possibly also) BlockChecked will have been called.
- *
- * May not be called in a
- * validationinterface callback.
- *
- * @param[in] pblock The block we want to process.
- * @param[in] fForceProcessing Process this block even if unrequested; used for non-network block sources and whitelisted peers.
- * @param[out] fNewBlock A boolean which is set to indicate if the block was first received via this call
- * @returns If the block was processed, independently of block validity
- */
-bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock) LOCKS_EXCLUDED(cs_main);
-
-/**
- * Process incoming block headers.
- *
- * May not be called in a
- * validationinterface callback.
- *
- * @param[in] block The block headers themselves
- * @param[out] state This may be set to an Error state if any error occurred processing them
- * @param[in] chainparams The params for the chain we want to connect to
- * @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers
- */
-bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main);
-
/** Open a block file (blk?????.dat) */
FILE* OpenBlockFile(const FlatFilePos &pos, bool fReadOnly = false);
/** Translation to a filesystem path */
@@ -192,9 +158,6 @@ fs::path GetBlockPosFilename(const FlatFilePos &pos);
void LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp = nullptr);
/** Ensures we have a genesis block in the block tree, possibly writing one to disk. */
bool LoadGenesisBlock(const CChainParams& chainparams);
-/** Load the block tree and coins database from disk,
- * initializing state if we're running with -reindex. */
-bool LoadBlockIndex(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Unload database information */
void UnloadBlockIndex();
/** Run an instance of the script checking thread */
@@ -217,11 +180,6 @@ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex* pin
uint64_t CalculateCurrentUsage();
/**
- * Mark one block file as pruned.
- */
-void PruneOneBlockFile(const int fileNumber) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
-
-/**
* Actually unlink the specified files
*/
void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune);
@@ -493,9 +451,6 @@ enum class CoinsCacheSizeState
OK = 0
};
-// Defined below, but needed for `friend` usage in CChainState.
-class ChainstateManager;
-
/**
* CChainState stores and provides an API to update our local knowledge of the
* current best chain.
@@ -870,6 +825,47 @@ public:
CChain& ValidatedChain() const { return ValidatedChainstate().m_chain; }
CBlockIndex* ValidatedTip() const { return ValidatedChain().Tip(); }
+ /**
+ * Process an incoming block. This only returns after the best known valid
+ * block is made active. Note that it does not, however, guarantee that the
+ * specific block passed to it has been checked for validity!
+ *
+ * If you want to *possibly* get feedback on whether pblock is valid, you must
+ * install a CValidationInterface (see validationinterface.h) - this will have
+ * its BlockChecked method called whenever *any* block completes validation.
+ *
+ * Note that we guarantee that either the proof-of-work is valid on pblock, or
+ * (and possibly also) BlockChecked will have been called.
+ *
+ * May not be called in a
+ * validationinterface callback.
+ *
+ * @param[in] pblock The block we want to process.
+ * @param[in] fForceProcessing Process this block even if unrequested; used for non-network block sources and whitelisted peers.
+ * @param[out] fNewBlock A boolean which is set to indicate if the block was first received via this call
+ * @returns If the block was processed, independently of block validity
+ */
+ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock) LOCKS_EXCLUDED(cs_main);
+
+ /**
+ * Process incoming block headers.
+ *
+ * May not be called in a
+ * validationinterface callback.
+ *
+ * @param[in] block The block headers themselves
+ * @param[out] state This may be set to an Error state if any error occurred processing them
+ * @param[in] chainparams The params for the chain we want to connect to
+ * @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers
+ */
+ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main);
+
+ //! Mark one block file as pruned (modify associated database entries)
+ void PruneOneBlockFile(const int fileNumber) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
+ //! Load the block tree and coins database from disk, initializing state if we're running with -reindex
+ bool LoadBlockIndex(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
//! Unload block index and chain data before shutdown.
void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
@@ -877,6 +873,7 @@ public:
void Reset();
};
+/** DEPRECATED! Please use node.chainman instead. May only be used in validation.cpp internally */
extern ChainstateManager g_chainman GUARDED_BY(::cs_main);
/** @returns the most-work valid chainstate. */
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index eff24025f4..3654420eb2 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -118,7 +118,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Prune the older block file.
{
LOCK(cs_main);
- PruneOneBlockFile(oldTip->GetBlockPos().nFile);
+ EnsureChainman(m_node).PruneOneBlockFile(oldTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
@@ -144,7 +144,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Prune the remaining block file.
{
LOCK(cs_main);
- PruneOneBlockFile(newTip->GetBlockPos().nFile);
+ EnsureChainman(m_node).PruneOneBlockFile(newTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({newTip->GetBlockPos().nFile});
@@ -181,7 +181,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
// Prune the older block file.
{
LOCK(cs_main);
- PruneOneBlockFile(oldTip->GetBlockPos().nFile);
+ EnsureChainman(m_node).PruneOneBlockFile(oldTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 1c94305220..7530e7daf6 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -67,6 +67,7 @@ class TestBitcoinCli(BitcoinTestFramework):
if self.is_wallet_compiled():
self.log.info("Test -getinfo and bitcoin-cli getwalletinfo return expected wallet info")
assert_equal(cli_get_info['balance'], BALANCE)
+ assert 'balances' not in cli_get_info.keys()
wallet_info = self.nodes[0].getwalletinfo()
assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize'])
assert_equal(cli_get_info['unlocked_until'], wallet_info['unlocked_until'])
@@ -76,42 +77,60 @@ class TestBitcoinCli(BitcoinTestFramework):
# Setup to test -getinfo and -rpcwallet= with multiple wallets.
wallets = ['', 'Encrypted', 'secret']
- amounts = [Decimal('59.999928'), Decimal(9), Decimal(31)]
+ amounts = [BALANCE + Decimal('9.999928'), Decimal(9), Decimal(31)]
self.nodes[0].createwallet(wallet_name=wallets[1])
self.nodes[0].createwallet(wallet_name=wallets[2])
w1 = self.nodes[0].get_wallet_rpc(wallets[0])
w2 = self.nodes[0].get_wallet_rpc(wallets[1])
w3 = self.nodes[0].get_wallet_rpc(wallets[2])
w1.walletpassphrase(password, self.rpc_timeout)
+ w2.encryptwallet(password)
w1.sendtoaddress(w2.getnewaddress(), amounts[1])
w1.sendtoaddress(w3.getnewaddress(), amounts[2])
# Mine a block to confirm; adds a block reward (50 BTC) to the default wallet.
self.nodes[0].generate(1)
- self.log.info("Test -getinfo with multiple wallets loaded returns no balance")
- assert_equal(set(self.nodes[0].listwallets()), set(wallets))
- assert 'balance' not in self.nodes[0].cli('-getinfo').send_cli().keys()
-
self.log.info("Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance")
for i in range(len(wallets)):
- cli_get_info = self.nodes[0].cli('-getinfo').send_cli('-rpcwallet={}'.format(wallets[i]))
+ cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli()
+ assert 'balances' not in cli_get_info.keys()
assert_equal(cli_get_info['balance'], amounts[i])
- self.log.info("Test -getinfo with multiple wallets and -rpcwallet=non-existing-wallet returns no balance")
- assert 'balance' not in self.nodes[0].cli('-getinfo').send_cli('-rpcwallet=does-not-exist').keys()
+ self.log.info("Test -getinfo with multiple wallets and -rpcwallet=non-existing-wallet returns no balances")
+ cli_get_info_keys = self.nodes[0].cli('-getinfo', '-rpcwallet=does-not-exist').send_cli().keys()
+ assert 'balance' not in cli_get_info_keys
+ assert 'balances' not in cli_get_info_keys
- self.log.info("Test -getinfo after unloading all wallets except a non-default one returns its balance")
+ self.log.info("Test -getinfo with multiple wallets returns all loaded wallet names and balances")
+ assert_equal(set(self.nodes[0].listwallets()), set(wallets))
+ cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
+ assert 'balance' not in cli_get_info.keys()
+ assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets, amounts)})
+
+ # Unload the default wallet and re-verify.
self.nodes[0].unloadwallet(wallets[0])
+ assert wallets[0] not in self.nodes[0].listwallets()
+ cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
+ assert 'balance' not in cli_get_info.keys()
+ assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets[1:], amounts[1:])})
+
+ self.log.info("Test -getinfo after unloading all wallets except a non-default one returns its balance")
self.nodes[0].unloadwallet(wallets[2])
assert_equal(self.nodes[0].listwallets(), [wallets[1]])
- assert_equal(self.nodes[0].cli('-getinfo').send_cli()['balance'], amounts[1])
-
- self.log.info("Test -getinfo -rpcwallet=remaining-non-default-wallet returns its balance")
- assert_equal(self.nodes[0].cli('-getinfo').send_cli('-rpcwallet={}'.format(wallets[1]))['balance'], amounts[1])
-
- self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balance")
- assert 'balance' not in self.nodes[0].cli('-getinfo').send_cli('-rpcwallet={}'.format(wallets[2])).keys()
+ cli_get_info = self.nodes[0].cli('-getinfo').send_cli()
+ assert 'balances' not in cli_get_info.keys()
+ assert_equal(cli_get_info['balance'], amounts[1])
+
+ self.log.info("Test -getinfo with -rpcwallet=remaining-non-default-wallet returns only its balance")
+ cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[1])).send_cli()
+ assert 'balances' not in cli_get_info.keys()
+ assert_equal(cli_get_info['balance'], amounts[1])
+
+ self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balances")
+ cli_get_info = self.nodes[0].cli('-getinfo', '-rpcwallet={}'.format(wallets[2])).send_cli()
+ assert 'balance' not in cli_get_info_keys
+ assert 'balances' not in cli_get_info_keys
else:
self.log.info("*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped")
self.nodes[0].generate(1) # maintain block parity with the wallet_compiled conditional branch
diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py
index 4d00a6dc07..9ff76b4b3d 100755
--- a/test/functional/p2p_blockfilters.py
+++ b/test/functional/p2p_blockfilters.py
@@ -5,12 +5,16 @@
"""Tests NODE_COMPACT_FILTERS (BIP 157/158).
Tests that a node configured with -blockfilterindex and -peerblockfilters can serve
-cfcheckpts.
+cfheaders and cfcheckpts.
"""
from test_framework.messages import (
FILTER_TYPE_BASIC,
+ hash256,
msg_getcfcheckpt,
+ msg_getcfheaders,
+ ser_uint256,
+ uint256_from_str,
)
from test_framework.mininode import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
@@ -100,12 +104,45 @@ class CompactFiltersTest(BitcoinTestFramework):
[int(header, 16) for header in (stale_cfcheckpt,)]
)
+ self.log.info("Check that peers can fetch cfheaders on active chain.")
+ request = msg_getcfheaders(
+ filter_type=FILTER_TYPE_BASIC,
+ start_height=1,
+ stop_hash=int(main_block_hash, 16)
+ )
+ node0.send_and_ping(request)
+ response = node0.last_message['cfheaders']
+ assert_equal(len(response.hashes), 1000)
+ assert_equal(
+ compute_last_header(response.prev_header, response.hashes),
+ int(main_cfcheckpt, 16)
+ )
+
+ self.log.info("Check that peers can fetch cfheaders on stale chain.")
+ request = msg_getcfheaders(
+ filter_type=FILTER_TYPE_BASIC,
+ start_height=1,
+ stop_hash=int(stale_block_hash, 16)
+ )
+ node0.send_and_ping(request)
+ response = node0.last_message['cfheaders']
+ assert_equal(len(response.hashes), 1000)
+ assert_equal(
+ compute_last_header(response.prev_header, response.hashes),
+ int(stale_cfcheckpt, 16)
+ )
+
self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.")
requests = [
msg_getcfcheckpt(
filter_type=FILTER_TYPE_BASIC,
stop_hash=int(main_block_hash, 16)
),
+ msg_getcfheaders(
+ filter_type=FILTER_TYPE_BASIC,
+ start_height=1000,
+ stop_hash=int(main_block_hash, 16)
+ ),
]
for request in requests:
node1 = self.nodes[1].add_p2p_connection(P2PInterface())
@@ -114,6 +151,12 @@ class CompactFiltersTest(BitcoinTestFramework):
self.log.info("Check that invalid requests result in disconnection.")
requests = [
+ # Requesting too many filter headers results in disconnection.
+ msg_getcfheaders(
+ filter_type=FILTER_TYPE_BASIC,
+ start_height=0,
+ stop_hash=int(tip_hash, 16)
+ ),
# Requesting unknown filter type results in disconnection.
msg_getcfcheckpt(
filter_type=255,
@@ -130,5 +173,12 @@ class CompactFiltersTest(BitcoinTestFramework):
node0.send_message(request)
node0.wait_for_disconnect()
+def compute_last_header(prev_header, hashes):
+ """Compute the last filter header from a starting header and a sequence of filter hashes."""
+ header = ser_uint256(prev_header)
+ for filter_hash in hashes:
+ header = hash256(ser_uint256(filter_hash) + header)
+ return uint256_from_str(header)
+
if __name__ == '__main__':
CompactFiltersTest().main()
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 6c9c8a7397..d178e79541 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -1516,6 +1516,59 @@ class msg_no_witness_blocktxn(msg_blocktxn):
def serialize(self):
return self.block_transactions.serialize(with_witness=False)
+class msg_getcfheaders:
+ __slots__ = ("filter_type", "start_height", "stop_hash")
+ msgtype = b"getcfheaders"
+
+ def __init__(self, filter_type, start_height, stop_hash):
+ self.filter_type = filter_type
+ self.start_height = start_height
+ self.stop_hash = stop_hash
+
+ def deserialize(self, f):
+ self.filter_type = struct.unpack("<B", f.read(1))[0]
+ self.start_height = struct.unpack("<I", f.read(4))[0]
+ self.stop_hash = deser_uint256(f)
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<B", self.filter_type)
+ r += struct.pack("<I", self.start_height)
+ r += ser_uint256(self.stop_hash)
+ return r
+
+ def __repr__(self):
+ return "msg_getcfheaders(filter_type={:#x}, start_height={}, stop_hash={:x})".format(
+ self.filter_type, self.start_height, self.stop_hash)
+
+class msg_cfheaders:
+ __slots__ = ("filter_type", "stop_hash", "prev_header", "hashes")
+ msgtype = b"cfheaders"
+
+ def __init__(self, filter_type=None, stop_hash=None, prev_header=None, hashes=None):
+ self.filter_type = filter_type
+ self.stop_hash = stop_hash
+ self.prev_header = prev_header
+ self.hashes = hashes
+
+ def deserialize(self, f):
+ self.filter_type = struct.unpack("<B", f.read(1))[0]
+ self.stop_hash = deser_uint256(f)
+ self.prev_header = deser_uint256(f)
+ self.hashes = deser_uint256_vector(f)
+
+ def serialize(self):
+ r = b""
+ r += struct.pack("<B", self.filter_type)
+ r += ser_uint256(self.stop_hash)
+ r += ser_uint256(self.prev_header)
+ r += ser_uint256_vector(self.hashes)
+ return r
+
+ def __repr__(self):
+ return "msg_cfheaders(filter_type={:#x}, stop_hash={:x})".format(
+ self.filter_type, self.stop_hash)
+
class msg_getcfcheckpt:
__slots__ = ("filter_type", "stop_hash")
msgtype = b"getcfcheckpt"
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index 95a63717d6..4609703057 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -31,6 +31,7 @@ from test_framework.messages import (
msg_block,
MSG_BLOCK,
msg_blocktxn,
+ msg_cfheaders,
msg_cfcheckpt,
msg_cmpctblock,
msg_feefilter,
@@ -68,6 +69,7 @@ MESSAGEMAP = {
b"addr": msg_addr,
b"block": msg_block,
b"blocktxn": msg_blocktxn,
+ b"cfheaders": msg_cfheaders,
b"cfcheckpt": msg_cfcheckpt,
b"cmpctblock": msg_cmpctblock,
b"feefilter": msg_feefilter,
@@ -330,6 +332,7 @@ class P2PInterface(P2PConnection):
def on_addr(self, message): pass
def on_block(self, message): pass
def on_blocktxn(self, message): pass
+ def on_cfheaders(self, message): pass
def on_cfcheckpt(self, message): pass
def on_cmpctblock(self, message): pass
def on_feefilter(self, message): pass