aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am100
-rw-r--r--src/bench/bench_bitcoin.cpp4
-rw-r--r--src/bitcoin-chainstate.cpp262
-rw-r--r--src/init.cpp23
-rw-r--r--src/init/common.cpp2
-rw-r--r--src/net.cpp48
-rw-r--r--src/net.h21
-rw-r--r--src/net_processing.cpp11
-rw-r--r--src/qt/guiutil.cpp29
-rw-r--r--src/script/descriptor.cpp68
-rw-r--r--src/script/script.h3
-rw-r--r--src/script/sign.cpp23
-rw-r--r--src/script/standard.cpp72
-rw-r--r--src/script/standard.h4
-rw-r--r--src/test/fuzz/script_format.cpp5
-rw-r--r--src/test/getarg_tests.cpp18
-rw-r--r--src/test/net_tests.cpp191
-rw-r--r--src/test/timedata_tests.cpp3
-rw-r--r--src/test/util/setup_common.cpp3
-rw-r--r--src/timedata.cpp38
-rw-r--r--src/timedata.h5
-rw-r--r--src/util/system.cpp13
-rw-r--r--src/util/system.h22
-rw-r--r--src/validation.cpp11
-rw-r--r--src/validation.h8
25 files changed, 872 insertions, 115 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 417a611181..8f4cbee62f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -106,6 +106,10 @@ if BUILD_BITCOIN_UTIL
bin_PROGRAMS += bitcoin-util
endif
+if BUILD_BITCOIN_CHAINSTATE
+ bin_PROGRAMS += bitcoin-chainstate
+endif
+
.PHONY: FORCE check-symbols check-security
# bitcoin core #
BITCOIN_CORE_H = \
@@ -769,6 +773,102 @@ bitcoin_util_LDADD = \
$(LIBSECP256K1)
#
+# bitcoin-chainstate binary #
+bitcoin_chainstate_SOURCES = \
+ bitcoin-chainstate.cpp \
+ arith_uint256.cpp \
+ blockfilter.cpp \
+ chain.cpp \
+ chainparamsbase.cpp \
+ chainparams.cpp \
+ clientversion.cpp \
+ coins.cpp \
+ compat/glibcxx_sanity.cpp \
+ compressor.cpp \
+ consensus/merkle.cpp \
+ consensus/tx_check.cpp \
+ consensus/tx_verify.cpp \
+ core_read.cpp \
+ dbwrapper.cpp \
+ deploymentinfo.cpp \
+ deploymentstatus.cpp \
+ flatfile.cpp \
+ fs.cpp \
+ hash.cpp \
+ index/base.cpp \
+ index/blockfilterindex.cpp \
+ index/coinstatsindex.cpp \
+ init/common.cpp \
+ key.cpp \
+ logging.cpp \
+ netaddress.cpp \
+ node/blockstorage.cpp \
+ node/chainstate.cpp \
+ node/coinstats.cpp \
+ node/ui_interface.cpp \
+ policy/feerate.cpp \
+ policy/fees.cpp \
+ policy/packages.cpp \
+ policy/policy.cpp \
+ policy/rbf.cpp \
+ policy/settings.cpp \
+ pow.cpp \
+ primitives/block.cpp \
+ primitives/transaction.cpp \
+ pubkey.cpp \
+ random.cpp \
+ randomenv.cpp \
+ scheduler.cpp \
+ script/interpreter.cpp \
+ script/script.cpp \
+ script/script_error.cpp \
+ script/sigcache.cpp \
+ script/standard.cpp \
+ shutdown.cpp \
+ signet.cpp \
+ support/cleanse.cpp \
+ support/lockedpool.cpp \
+ sync.cpp \
+ threadinterrupt.cpp \
+ timedata.cpp \
+ txdb.cpp \
+ txmempool.cpp \
+ uint256.cpp \
+ util/asmap.cpp \
+ util/bytevectorhash.cpp \
+ util/getuniquepath.cpp \
+ util/hasher.cpp \
+ util/moneystr.cpp \
+ util/rbf.cpp \
+ util/serfloat.cpp \
+ util/settings.cpp \
+ util/strencodings.cpp \
+ util/syscall_sandbox.cpp \
+ util/system.cpp \
+ util/thread.cpp \
+ util/threadnames.cpp \
+ util/time.cpp \
+ util/tokenpipe.cpp \
+ validation.cpp \
+ validationinterface.cpp \
+ versionbits.cpp \
+ warnings.cpp
+bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+bitcoin_chainstate_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+bitcoin_chainstate_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS)
+bitcoin_chainstate_LDADD = \
+ $(LIBBITCOIN_CRYPTO) \
+ $(LIBUNIVALUE) \
+ $(LIBSECP256K1) \
+ $(LIBLEVELDB) \
+ $(LIBLEVELDB_SSE42) \
+ $(LIBMEMENV)
+
+# Required for obj/build.h to be generated first.
+# More details: https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html
+bitcoin_chainstate-clientversion.$(OBJEXT): obj/build.h
+#
+
# bitcoinconsensus library #
if BUILD_BITCOIN_LIBS
include_HEADERS = script/bitcoinconsensus.h
diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp
index 3f8bff4bcf..d6f9c0f8b5 100644
--- a/src/bench/bench_bitcoin.cpp
+++ b/src/bench/bench_bitcoin.cpp
@@ -109,8 +109,8 @@ int main(int argc, char** argv)
args.asymptote = parseAsymptote(argsman.GetArg("-asymptote", ""));
args.is_list_only = argsman.GetBoolArg("-list", false);
args.min_time = std::chrono::milliseconds(argsman.GetIntArg("-min_time", DEFAULT_MIN_TIME_MS));
- args.output_csv = fs::PathFromString(argsman.GetArg("-output_csv", ""));
- args.output_json = fs::PathFromString(argsman.GetArg("-output_json", ""));
+ args.output_csv = argsman.GetPathArg("-output_csv");
+ args.output_json = argsman.GetPathArg("-output_json");
args.regex_filter = argsman.GetArg("-filter", DEFAULT_BENCH_FILTER);
benchmark::BenchRunner::RunAll(args);
diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp
new file mode 100644
index 0000000000..f93197350d
--- /dev/null
+++ b/src/bitcoin-chainstate.cpp
@@ -0,0 +1,262 @@
+// Copyright (c) 2022 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+//
+// The bitcoin-chainstate executable serves to surface the dependencies required
+// by a program wishing to use Bitcoin Core's consensus engine as it is right
+// now.
+//
+// DEVELOPER NOTE: Since this is a "demo-only", experimental, etc. executable,
+// it may diverge from Bitcoin Core's coding style.
+//
+// It is part of the libbitcoinkernel project.
+
+#include <chainparams.h>
+#include <consensus/validation.h>
+#include <core_io.h>
+#include <init/common.h>
+#include <node/blockstorage.h>
+#include <node/chainstate.h>
+#include <scheduler.h>
+#include <script/sigcache.h>
+#include <util/system.h>
+#include <util/thread.h>
+#include <validation.h>
+#include <validationinterface.h>
+
+#include <filesystem>
+#include <functional>
+#include <iosfwd>
+
+const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
+
+int main(int argc, char* argv[])
+{
+ // SETUP: Argument parsing and handling
+ if (argc != 2) {
+ std::cerr
+ << "Usage: " << argv[0] << " DATADIR" << std::endl
+ << "Display DATADIR information, and process hex-encoded blocks on standard input." << std::endl
+ << std::endl
+ << "IMPORTANT: THIS EXECUTABLE IS EXPERIMENTAL, FOR TESTING ONLY, AND EXPECTED TO" << std::endl
+ << " BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl;
+ return 1;
+ }
+ std::filesystem::path abs_datadir = std::filesystem::absolute(argv[1]);
+ std::filesystem::create_directories(abs_datadir);
+ gArgs.ForceSetArg("-datadir", abs_datadir.string());
+
+
+ // SETUP: Misc Globals
+ SelectParams(CBaseChainParams::MAIN);
+ const CChainParams& chainparams = Params();
+
+ init::SetGlobals(); // ECC_Start, etc.
+
+ // Necessary for CheckInputScripts (eventually called by ProcessNewBlock),
+ // which will try the script cache first and fall back to actually
+ // performing the check with the signature cache.
+ InitSignatureCache();
+ InitScriptExecutionCache();
+
+
+ // SETUP: Scheduling and Background Signals
+ CScheduler scheduler{};
+ // Start the lightweight task scheduler thread
+ scheduler.m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { scheduler.serviceQueue(); });
+
+ // Gather some entropy once per minute.
+ scheduler.scheduleEvery(RandAddPeriodic, std::chrono::minutes{1});
+
+ GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
+
+
+ // SETUP: Chainstate
+ ChainstateManager chainman;
+
+ auto rv = node::LoadChainstate(false,
+ std::ref(chainman),
+ nullptr,
+ false,
+ chainparams.GetConsensus(),
+ false,
+ 2 << 20,
+ 2 << 22,
+ (450 << 20) - (2 << 20) - (2 << 22),
+ false,
+ false,
+ []() { return false; });
+ if (rv.has_value()) {
+ std::cerr << "Failed to load Chain state from your datadir." << std::endl;
+ goto epilogue;
+ } else {
+ auto maybe_verify_error = node::VerifyLoadedChainstate(std::ref(chainman),
+ false,
+ false,
+ chainparams.GetConsensus(),
+ DEFAULT_CHECKBLOCKS,
+ DEFAULT_CHECKLEVEL,
+ /*get_unix_time_seconds=*/static_cast<int64_t (*)()>(GetTime));
+ if (maybe_verify_error.has_value()) {
+ std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl;
+ goto epilogue;
+ }
+ }
+
+ for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
+ BlockValidationState state;
+ if (!chainstate->ActivateBestChain(state, nullptr)) {
+ std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl;
+ goto epilogue;
+ }
+ }
+
+ // Main program logic starts here
+ std::cout
+ << "Hello! I'm going to print out some information about your datadir." << std::endl
+ << "\t" << "Path: " << gArgs.GetDataDirNet() << std::endl
+ << "\t" << "Reindexing: " << std::boolalpha << node::fReindex.load() << std::noboolalpha << std::endl
+ << "\t" << "Snapshot Active: " << std::boolalpha << chainman.IsSnapshotActive() << std::noboolalpha << std::endl
+ << "\t" << "Active Height: " << chainman.ActiveHeight() << std::endl
+ << "\t" << "Active IBD: " << std::boolalpha << chainman.ActiveChainstate().IsInitialBlockDownload() << std::noboolalpha << std::endl;
+ {
+ CBlockIndex* tip = chainman.ActiveTip();
+ if (tip) {
+ std::cout << "\t" << tip->ToString() << std::endl;
+ }
+ }
+
+ for (std::string line; std::getline(std::cin, line);) {
+ if (line.empty()) {
+ std::cerr << "Empty line found" << std::endl;
+ break;
+ }
+
+ std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>();
+ CBlock& block = *blockptr;
+
+ if (!DecodeHexBlk(block, line)) {
+ std::cerr << "Block decode failed" << std::endl;
+ break;
+ }
+
+ if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) {
+ std::cerr << "Block does not start with a coinbase" << std::endl;
+ break;
+ }
+
+ uint256 hash = block.GetHash();
+ {
+ LOCK(cs_main);
+ const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
+ if (pindex) {
+ if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) {
+ std::cerr << "duplicate" << std::endl;
+ break;
+ }
+ if (pindex->nStatus & BLOCK_FAILED_MASK) {
+ std::cerr << "duplicate-invalid" << std::endl;
+ break;
+ }
+ }
+ }
+
+ {
+ LOCK(cs_main);
+ const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock);
+ if (pindex) {
+ UpdateUncommittedBlockStructures(block, pindex, chainparams.GetConsensus());
+ }
+ }
+
+ // Adapted from rpc/mining.cpp
+ class submitblock_StateCatcher final : public CValidationInterface
+ {
+ public:
+ uint256 hash;
+ bool found;
+ BlockValidationState state;
+
+ explicit submitblock_StateCatcher(const uint256& hashIn) : hash(hashIn), found(false), state() {}
+
+ protected:
+ void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override
+ {
+ if (block.GetHash() != hash)
+ return;
+ found = true;
+ state = stateIn;
+ }
+ };
+
+ bool new_block;
+ auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
+ RegisterSharedValidationInterface(sc);
+ bool accepted = chainman.ProcessNewBlock(chainparams, blockptr, /* force_processing */ true, /* new_block */ &new_block);
+ UnregisterSharedValidationInterface(sc);
+ if (!new_block && accepted) {
+ std::cerr << "duplicate" << std::endl;
+ break;
+ }
+ if (!sc->found) {
+ std::cerr << "inconclusive" << std::endl;
+ break;
+ }
+ std::cout << sc->state.ToString() << std::endl;
+ switch (sc->state.GetResult()) {
+ case BlockValidationResult::BLOCK_RESULT_UNSET:
+ std::cerr << "initial value. Block has not yet been rejected" << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_CONSENSUS:
+ std::cerr << "invalid by consensus rules (excluding any below reasons)" << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE:
+ std::cerr << "Invalid by a change to consensus rules more recent than SegWit." << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_CACHED_INVALID:
+ std::cerr << "this block was cached as being invalid and we didn't store the reason why" << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_INVALID_HEADER:
+ std::cerr << "invalid proof of work or time too old" << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_MUTATED:
+ std::cerr << "the block's data didn't match the data committed to by the PoW" << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_MISSING_PREV:
+ std::cerr << "We don't have the previous block the checked one is built on" << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_INVALID_PREV:
+ std::cerr << "A block this one builds on is invalid" << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_TIME_FUTURE:
+ std::cerr << "block timestamp was > 2 hours in the future (or our clock is bad)" << std::endl;
+ break;
+ case BlockValidationResult::BLOCK_CHECKPOINT:
+ std::cerr << "the block failed to meet one of our checkpoints" << std::endl;
+ break;
+ }
+ }
+
+epilogue:
+ // Without this precise shutdown sequence, there will be a lot of nullptr
+ // dereferencing and UB.
+ scheduler.stop();
+ if (chainman.m_load_block.joinable()) chainman.m_load_block.join();
+ StopScriptCheckWorkerThreads();
+
+ GetMainSignals().FlushBackgroundCallbacks();
+ {
+ LOCK(cs_main);
+ for (CChainState* chainstate : chainman.GetAll()) {
+ if (chainstate->CanFlushToDisk()) {
+ chainstate->ForceFlushStateToDisk();
+ chainstate->ResetCoinsViews();
+ }
+ }
+ }
+ GetMainSignals().UnregisterBackgroundSignalScheduler();
+
+ UnloadBlockIndex(nullptr, chainman);
+
+ init::UnsetGlobals();
+}
diff --git a/src/init.cpp b/src/init.cpp
index 9813a16563..bad402e56e 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -135,7 +135,7 @@ static const char* BITCOIN_PID_FILENAME = "bitcoind.pid";
static fs::path GetPidFile(const ArgsManager& args)
{
- return AbsPathForConfigVal(fs::PathFromString(args.GetArg("-pid", BITCOIN_PID_FILENAME)));
+ return AbsPathForConfigVal(args.GetPathArg("-pid", BITCOIN_PID_FILENAME));
}
[[nodiscard]] static bool CreatePidFile(const ArgsManager& args)
@@ -462,7 +462,7 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-i2pacceptincoming", "If set and -i2psam is also set then incoming I2P connections are accepted via the SAM proxy. If this is not set but -i2psam is set then only outgoing connections will be made to the I2P network. Ignored if -i2psam is not set. Listening for incoming I2P connections is done through the SAM proxy, not by binding to a local address and port (default: 1)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- argsman.AddArg("-onlynet=<net>", "Make automatic outgoing connections only through network <net> (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-onlynet=<net>", "Make automatic outbound connections only to network <net> (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -1229,10 +1229,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// Read asmap file if configured
std::vector<bool> asmap;
if (args.IsArgSet("-asmap")) {
- fs::path asmap_path = fs::PathFromString(args.GetArg("-asmap", ""));
- if (asmap_path.empty()) {
- asmap_path = fs::PathFromString(DEFAULT_ASMAP_FILENAME);
- }
+ fs::path asmap_path = args.GetPathArg("-asmap", DEFAULT_ASMAP_FILENAME);
if (!asmap_path.is_absolute()) {
asmap_path = gArgs.GetDataDirNet() / asmap_path;
}
@@ -1668,8 +1665,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
LogPrintf("nBestHeight = %d\n", chain_active_height);
if (node.peerman) node.peerman->SetBestHeight(chain_active_height);
- Discover();
-
// Map ports with UPnP or NAT-PMP.
StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP), gArgs.GetBoolArg("-natpmp", DEFAULT_NATPMP));
@@ -1689,6 +1684,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
connOptions.nMaxOutboundLimit = *opt_max_upload;
connOptions.m_peer_connect_timeout = peer_connect_timeout;
+ // Port to bind to if `-bind=addr` is provided without a `:port` suffix.
+ const uint16_t default_bind_port =
+ static_cast<uint16_t>(args.GetIntArg("-port", Params().GetDefaultPort()));
+
const auto BadPortWarning = [](const char* prefix, uint16_t port) {
return strprintf(_("%s request to listen on port %u. This port is considered \"bad\" and "
"thus it is unlikely that any Bitcoin Core peers connect to it. See "
@@ -1701,7 +1700,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
CService bind_addr;
const size_t index = bind_arg.rfind('=');
if (index == std::string::npos) {
- if (Lookup(bind_arg, bind_addr, GetListenPort(), false)) {
+ if (Lookup(bind_arg, bind_addr, default_bind_port, /*fAllowLookup=*/false)) {
connOptions.vBinds.push_back(bind_addr);
if (IsBadPort(bind_addr.GetPort())) {
InitWarning(BadPortWarning("-bind", bind_addr.GetPort()));
@@ -1758,6 +1757,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
StartTorControl(onion_service_target);
}
+ if (connOptions.bind_on_any) {
+ // Only add all IP addresses of the machine if we would be listening on
+ // any address - 0.0.0.0 (IPv4) and :: (IPv6).
+ Discover();
+ }
+
for (const auto& net : args.GetArgs("-whitelist")) {
NetWhitelistPermissions subnet;
bilingual_str error;
diff --git a/src/init/common.cpp b/src/init/common.cpp
index 38c60366e3..688471b35d 100644
--- a/src/init/common.cpp
+++ b/src/init/common.cpp
@@ -81,7 +81,7 @@ void AddLoggingArgs(ArgsManager& argsman)
void SetLoggingOptions(const ArgsManager& args)
{
LogInstance().m_print_to_file = !args.IsArgNegated("-debuglogfile");
- LogInstance().m_file_path = AbsPathForConfigVal(fs::PathFromString(args.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE)));
+ LogInstance().m_file_path = AbsPathForConfigVal(args.GetPathArg("-debuglogfile", DEFAULT_DEBUGLOGFILE));
LogInstance().m_print_to_console = args.GetBoolArg("-printtoconsole", !args.GetBoolArg("-daemon", false));
LogInstance().m_log_timestamps = args.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
LogInstance().m_log_time_micros = args.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
diff --git a/src/net.cpp b/src/net.cpp
index 9bb264a38a..955eec46e3 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -126,6 +126,31 @@ void CConnman::AddAddrFetch(const std::string& strDest)
uint16_t GetListenPort()
{
+ // If -bind= is provided with ":port" part, use that (first one if multiple are provided).
+ for (const std::string& bind_arg : gArgs.GetArgs("-bind")) {
+ CService bind_addr;
+ constexpr uint16_t dummy_port = 0;
+
+ if (Lookup(bind_arg, bind_addr, dummy_port, /*fAllowLookup=*/false)) {
+ if (bind_addr.GetPort() != dummy_port) {
+ return bind_addr.GetPort();
+ }
+ }
+ }
+
+ // Otherwise, if -whitebind= without NetPermissionFlags::NoBan is provided, use that
+ // (-whitebind= is required to have ":port").
+ for (const std::string& whitebind_arg : gArgs.GetArgs("-whitebind")) {
+ NetWhitebindPermissions whitebind;
+ bilingual_str error;
+ if (NetWhitebindPermissions::TryParse(whitebind_arg, whitebind, error)) {
+ if (!NetPermissions::HasFlag(whitebind.m_flags, NetPermissionFlags::NoBan)) {
+ return whitebind.m_service.GetPort();
+ }
+ }
+ }
+
+ // Otherwise, if -port= is provided, use that. Otherwise use the default port.
return static_cast<uint16_t>(gArgs.GetIntArg("-port", Params().GetDefaultPort()));
}
@@ -221,7 +246,17 @@ std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode)
if (IsPeerAddrLocalGood(pnode) && (!addrLocal.IsRoutable() ||
rng.randbits((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3 : 1) == 0))
{
- addrLocal.SetIP(pnode->GetAddrLocal());
+ if (pnode->IsInboundConn()) {
+ // For inbound connections, assume both the address and the port
+ // as seen from the peer.
+ addrLocal = CAddress{pnode->GetAddrLocal(), addrLocal.nServices};
+ } else {
+ // For outbound connections, assume just the address as seen from
+ // the peer and leave the port in `addrLocal` as returned by
+ // `GetLocalAddress()` above. The peer has no way to observe our
+ // listening port when we have initiated the connection.
+ addrLocal.SetIP(pnode->GetAddrLocal());
+ }
}
if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false))
{
@@ -3085,7 +3120,10 @@ uint64_t CConnman::CalculateKeyedNetGroup(const CAddress& ad) const
return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP).Write(vchNetGroup.data(), vchNetGroup.size()).Finalize();
}
-void CaptureMessage(const CAddress& addr, const std::string& msg_type, const Span<const unsigned char>& data, bool is_incoming)
+void CaptureMessageToFile(const CAddress& addr,
+ const std::string& msg_type,
+ Span<const unsigned char> data,
+ bool is_incoming)
{
// Note: This function captures the message at the time of processing,
// not at socket receive/send time.
@@ -3112,3 +3150,9 @@ void CaptureMessage(const CAddress& addr, const std::string& msg_type, const Spa
ser_writedata32(f, size);
f.write(AsBytes(data));
}
+
+std::function<void(const CAddress& addr,
+ const std::string& msg_type,
+ Span<const unsigned char> data,
+ bool is_incoming)>
+ CaptureMessage = CaptureMessageToFile;
diff --git a/src/net.h b/src/net.h
index bbc253e7ff..a38310938b 100644
--- a/src/net.h
+++ b/src/net.h
@@ -31,6 +31,7 @@
#include <condition_variable>
#include <cstdint>
#include <deque>
+#include <functional>
#include <map>
#include <memory>
#include <optional>
@@ -182,7 +183,15 @@ enum class ConnectionType {
/** Convert ConnectionType enum to a string value */
std::string ConnectionTypeAsString(ConnectionType conn_type);
+
+/**
+ * Look up IP addresses from all interfaces on the machine and add them to the
+ * list of local addresses to self-advertise.
+ * The loopback interface is skipped and only the first address from each
+ * interface is used.
+ */
void Discover();
+
uint16_t GetListenPort();
enum
@@ -1272,7 +1281,17 @@ private:
};
/** Dump binary message to file, with timestamp */
-void CaptureMessage(const CAddress& addr, const std::string& msg_type, const Span<const unsigned char>& data, bool is_incoming);
+void CaptureMessageToFile(const CAddress& addr,
+ const std::string& msg_type,
+ Span<const unsigned char> data,
+ bool is_incoming);
+
+/** Defaults to `CaptureMessageToFile()`, but can be overridden by unit tests. */
+extern std::function<void(const CAddress& addr,
+ const std::string& msg_type,
+ Span<const unsigned char> data,
+ bool is_incoming)>
+ CaptureMessage;
struct NodeEvictionCandidate
{
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index f05b4fd8e2..59cd83e493 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -415,7 +415,7 @@ private:
void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable);
/** Send `feefilter` message. */
- void MaybeSendFeefilter(CNode& node, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ void MaybeSendFeefilter(CNode& node, std::chrono::microseconds current_time);
const CChainParams& m_chainparams;
CConnman& m_connman;
@@ -2711,6 +2711,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString());
PushAddress(*peer, addr, insecure_rand);
} else if (IsPeerAddrLocalGood(&pfrom)) {
+ // Override just the address with whatever the peer sees us as.
+ // Leave the port in addr as it was returned by GetLocalAddress()
+ // above, as this is an outbound connection and the peer cannot
+ // observe our listening port.
addr.SetIP(addrMe);
LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString());
PushAddress(*peer, addr, insecure_rand);
@@ -4502,8 +4506,6 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, std::chrono::microseconds current_time)
{
- AssertLockHeld(cs_main);
-
if (m_ignore_incoming_txs) return;
if (!pto.m_tx_relay) return;
if (pto.GetCommonVersion() < FEEFILTER_VERSION) return;
@@ -5050,8 +5052,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
if (!vGetData.empty())
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
-
- MaybeSendFeefilter(*pto, current_time);
} // release cs_main
+ MaybeSendFeefilter(*pto, current_time);
return true;
}
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 9565fa508f..d6706fd009 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -713,23 +713,18 @@ QString ConnectionTypeToQString(ConnectionType conn_type, bool prepend_direction
QString formatDurationStr(std::chrono::seconds dur)
{
- const auto secs = count_seconds(dur);
- QStringList strList;
- int days = secs / 86400;
- int hours = (secs % 86400) / 3600;
- int mins = (secs % 3600) / 60;
- int seconds = secs % 60;
-
- if (days)
- strList.append(QObject::tr("%1 d").arg(days));
- if (hours)
- strList.append(QObject::tr("%1 h").arg(hours));
- if (mins)
- strList.append(QObject::tr("%1 m").arg(mins));
- if (seconds || (!days && !hours && !mins))
- strList.append(QObject::tr("%1 s").arg(seconds));
-
- return strList.join(" ");
+ using days = std::chrono::duration<int, std::ratio<86400>>; // can remove this line after C++20
+ const auto d{std::chrono::duration_cast<days>(dur)};
+ const auto h{std::chrono::duration_cast<std::chrono::hours>(dur - d)};
+ const auto m{std::chrono::duration_cast<std::chrono::minutes>(dur - d - h)};
+ const auto s{std::chrono::duration_cast<std::chrono::seconds>(dur - d - h - m)};
+ QStringList str_list;
+ if (auto d2{d.count()}) str_list.append(QObject::tr("%1 d").arg(d2));
+ if (auto h2{h.count()}) str_list.append(QObject::tr("%1 h").arg(h2));
+ if (auto m2{m.count()}) str_list.append(QObject::tr("%1 m").arg(m2));
+ const auto s2{s.count()};
+ if (s2 || str_list.empty()) str_list.append(QObject::tr("%1 s").arg(s2));
+ return str_list.join(" ");
}
QString formatServicesStr(quint64 mask)
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 798c4b3ea0..23540f6aef 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -802,6 +802,30 @@ public:
bool IsSingleType() const final { return true; }
};
+/** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */
+class MultiADescriptor final : public DescriptorImpl
+{
+ const int m_threshold;
+ const bool m_sorted;
+protected:
+ std::string ToStringExtra() const override { return strprintf("%i", m_threshold); }
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override {
+ CScript ret;
+ std::vector<XOnlyPubKey> xkeys;
+ for (const auto& key : keys) xkeys.emplace_back(key);
+ if (m_sorted) std::sort(xkeys.begin(), xkeys.end());
+ ret << ToByteVector(xkeys[0]) << OP_CHECKSIG;
+ for (size_t i = 1; i < keys.size(); ++i) {
+ ret << ToByteVector(xkeys[i]) << OP_CHECKSIGADD;
+ }
+ ret << m_threshold << OP_NUMEQUAL;
+ return Vector(std::move(ret));
+ }
+public:
+ MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {}
+ bool IsSingleType() const final { return true; }
+};
+
/** A parsed sh(...) descriptor. */
class SHDescriptor final : public DescriptorImpl
{
@@ -1040,7 +1064,6 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
using namespace spanparsing;
auto expr = Expr(sp);
- bool sorted_multi = false;
if (Func("pk", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
if (!pubkey) return nullptr;
@@ -1065,7 +1088,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
error = "Can only have combo() at top level";
return nullptr;
}
- if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr))) {
+ const bool multi = Func("multi", expr);
+ const bool sortedmulti = !multi && Func("sortedmulti", expr);
+ const bool multi_a = !(multi || sortedmulti) && Func("multi_a", expr);
+ const bool sortedmulti_a = !(multi || sortedmulti || multi_a) && Func("sortedmulti_a", expr);
+ if (((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && (multi || sortedmulti)) ||
+ (ctx == ParseScriptContext::P2TR && (multi_a || sortedmulti_a))) {
auto threshold = Expr(expr);
uint32_t thres;
std::vector<std::unique_ptr<PubkeyProvider>> providers;
@@ -1086,9 +1114,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
providers.emplace_back(std::move(pk));
key_exp_index++;
}
- if (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG) {
+ if ((multi || sortedmulti) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG)) {
error = strprintf("Cannot have %u keys in multisig; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTISIG);
return nullptr;
+ } else if ((multi_a || sortedmulti_a) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTI_A)) {
+ error = strprintf("Cannot have %u keys in multi_a; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTI_A);
+ return nullptr;
} else if (thres < 1) {
error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres);
return nullptr;
@@ -1109,10 +1140,17 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
return nullptr;
}
}
- return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi);
- } else if (Func("sortedmulti", expr) || Func("multi", expr)) {
+ if (multi || sortedmulti) {
+ return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sortedmulti);
+ } else {
+ return std::make_unique<MultiADescriptor>(thres, std::move(providers), sortedmulti_a);
+ }
+ } else if (multi || sortedmulti) {
error = "Can only have multi/sortedmulti at top level, in sh(), or in wsh()";
return nullptr;
+ } else if (multi_a || sortedmulti_a) {
+ error = "Can only have multi_a/sortedmulti_a inside tr()";
+ return nullptr;
}
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error);
@@ -1257,6 +1295,21 @@ std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseS
return key_provider;
}
+std::unique_ptr<DescriptorImpl> InferMultiA(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
+{
+ auto match = MatchMultiA(script);
+ if (!match) return {};
+ std::vector<std::unique_ptr<PubkeyProvider>> keys;
+ keys.reserve(match->second.size());
+ for (const auto keyspan : match->second) {
+ if (keyspan.size() != 32) return {};
+ auto key = InferXOnlyPubkey(XOnlyPubKey{keyspan}, ctx, provider);
+ if (!key) return {};
+ keys.push_back(std::move(key));
+ }
+ return std::make_unique<MultiADescriptor>(match->first, std::move(keys));
+}
+
std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider)
{
if (ctx == ParseScriptContext::P2TR && script.size() == 34 && script[0] == 32 && script[33] == OP_CHECKSIG) {
@@ -1264,6 +1317,11 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
return std::make_unique<PKDescriptor>(InferXOnlyPubkey(key, ctx, provider), true);
}
+ if (ctx == ParseScriptContext::P2TR) {
+ auto ret = InferMultiA(script, ctx, provider);
+ if (ret) return ret;
+ }
+
std::vector<std::vector<unsigned char>> data;
TxoutType txntype = Solver(script, data);
diff --git a/src/script/script.h b/src/script/script.h
index 8b7a7bb7b3..a89c987306 100644
--- a/src/script/script.h
+++ b/src/script/script.h
@@ -29,6 +29,9 @@ static const int MAX_OPS_PER_SCRIPT = 201;
// Maximum number of public keys per multisig
static const int MAX_PUBKEYS_PER_MULTISIG = 20;
+/** The limit of keys in OP_CHECKSIGADD-based scripts. It is due to the stack limit in BIP342. */
+static constexpr unsigned int MAX_PUBKEYS_PER_MULTI_A = 999;
+
// Maximum script length in bytes
static const int MAX_SCRIPT_SIZE = 10000;
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 371a937bc8..2e5c49e0b6 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -174,6 +174,29 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu
result = Vector(std::move(sig));
return true;
}
+ return false;
+ }
+
+ // multi_a scripts (<key> OP_CHECKSIG <key> OP_CHECKSIGADD <key> OP_CHECKSIGADD <k> OP_NUMEQUAL)
+ if (auto match = MatchMultiA(script)) {
+ std::vector<std::vector<unsigned char>> sigs;
+ int good_sigs = 0;
+ for (size_t i = 0; i < match->second.size(); ++i) {
+ XOnlyPubKey pubkey{*(match->second.rbegin() + i)};
+ std::vector<unsigned char> sig;
+ bool good_sig = CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion);
+ if (good_sig && good_sigs < match->first) {
+ ++good_sigs;
+ sigs.push_back(std::move(sig));
+ } else {
+ sigs.emplace_back();
+ }
+ }
+ if (good_sigs == match->first) {
+ result = std::move(sigs);
+ return true;
+ }
+ return false;
}
return false;
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index 5fb98cc307..806b3169cd 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -96,51 +96,83 @@ static constexpr bool IsPushdataOp(opcodetype opcode)
return opcode > OP_FALSE && opcode <= OP_PUSHDATA4;
}
-static constexpr bool IsValidMultisigKeyCount(int n_keys)
-{
- return n_keys > 0 && n_keys <= MAX_PUBKEYS_PER_MULTISIG;
-}
-
-static bool GetMultisigKeyCount(opcodetype opcode, valtype data, int& count)
+/** Retrieve a minimally-encoded number in range [min,max] from an (opcode, data) pair,
+ * whether it's OP_n or through a push. */
+static std::optional<int> GetScriptNumber(opcodetype opcode, valtype data, int min, int max)
{
+ int count;
if (IsSmallInteger(opcode)) {
count = CScript::DecodeOP_N(opcode);
- return IsValidMultisigKeyCount(count);
- }
-
- if (IsPushdataOp(opcode)) {
- if (!CheckMinimalPush(data, opcode)) return false;
+ } else if (IsPushdataOp(opcode)) {
+ if (!CheckMinimalPush(data, opcode)) return {};
try {
count = CScriptNum(data, /* fRequireMinimal = */ true).getint();
- return IsValidMultisigKeyCount(count);
} catch (const scriptnum_error&) {
- return false;
+ return {};
}
+ } else {
+ return {};
}
-
- return false;
+ if (count < min || count > max) return {};
+ return count;
}
static bool MatchMultisig(const CScript& script, int& required_sigs, std::vector<valtype>& pubkeys)
{
opcodetype opcode;
valtype data;
- int num_keys;
CScript::const_iterator it = script.begin();
if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false;
- if (!script.GetOp(it, opcode, data) || !GetMultisigKeyCount(opcode, data, required_sigs)) return false;
+ if (!script.GetOp(it, opcode, data)) return false;
+ auto req_sigs = GetScriptNumber(opcode, data, 1, MAX_PUBKEYS_PER_MULTISIG);
+ if (!req_sigs) return false;
+ required_sigs = *req_sigs;
while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) {
pubkeys.emplace_back(std::move(data));
}
- if (!GetMultisigKeyCount(opcode, data, num_keys)) return false;
-
- if (pubkeys.size() != static_cast<unsigned long>(num_keys) || num_keys < required_sigs) return false;
+ auto num_keys = GetScriptNumber(opcode, data, required_sigs, MAX_PUBKEYS_PER_MULTISIG);
+ if (!num_keys) return false;
+ if (pubkeys.size() != static_cast<unsigned long>(*num_keys)) return false;
return (it + 1 == script.end());
}
+std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script)
+{
+ std::vector<Span<const unsigned char>> keyspans;
+
+ // Redundant, but very fast and selective test.
+ if (script.size() == 0 || script[0] != 32 || script.back() != OP_NUMEQUAL) return {};
+
+ // Parse keys
+ auto it = script.begin();
+ while (script.end() - it >= 34) {
+ if (*it != 32) return {};
+ ++it;
+ keyspans.emplace_back(&*it, 32);
+ it += 32;
+ if (*it != (keyspans.size() == 1 ? OP_CHECKSIG : OP_CHECKSIGADD)) return {};
+ ++it;
+ }
+ if (keyspans.size() == 0 || keyspans.size() > MAX_PUBKEYS_PER_MULTI_A) return {};
+
+ // Parse threshold.
+ opcodetype opcode;
+ std::vector<unsigned char> data;
+ if (!script.GetOp(it, opcode, data)) return {};
+ if (it == script.end()) return {};
+ if (*it != OP_NUMEQUAL) return {};
+ ++it;
+ if (it != script.end()) return {};
+ auto threshold = GetScriptNumber(opcode, data, 1, (int)keyspans.size());
+ if (!threshold) return {};
+
+ // Construct result.
+ return std::pair{*threshold, std::move(keyspans)};
+}
+
TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned char>>& vSolutionsRet)
{
vSolutionsRet.clear();
diff --git a/src/script/standard.h b/src/script/standard.h
index eb50421768..75bfe2db38 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -191,6 +191,10 @@ CScript GetScriptForDestination(const CTxDestination& dest);
/** Generate a P2PK script for the given pubkey. */
CScript GetScriptForRawPubKey(const CPubKey& pubkey);
+/** Determine if script is a "multi_a" script. Returns (threshold, keyspans) if so, and nullopt otherwise.
+ * The keyspans refer to bytes in the passed script. */
+std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script LIFETIMEBOUND);
+
/** Generate a multisig script. */
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
diff --git a/src/test/fuzz/script_format.cpp b/src/test/fuzz/script_format.cpp
index 2fa893f812..241bdfe666 100644
--- a/src/test/fuzz/script_format.cpp
+++ b/src/test/fuzz/script_format.cpp
@@ -3,7 +3,9 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
+#include <consensus/consensus.h>
#include <core_io.h>
+#include <policy/policy.h>
#include <script/script.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
@@ -19,6 +21,9 @@ FUZZ_TARGET_INIT(script_format, initialize_script_format)
{
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const CScript script{ConsumeScript(fuzzed_data_provider)};
+ if (script.size() > MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR) {
+ return;
+ }
(void)FormatScript(script);
(void)ScriptToAsmStr(script, /*fAttemptSighashDecode=*/fuzzed_data_provider.ConsumeBool());
diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp
index ef9d72dcde..76a65ec528 100644
--- a/src/test/getarg_tests.cpp
+++ b/src/test/getarg_tests.cpp
@@ -245,6 +245,24 @@ BOOST_AUTO_TEST_CASE(patharg)
ResetArgs(local_args, "-dir=user/.bitcoin/.//");
BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir"), relative_path);
+
+ // Check negated and default argument handling. Specifying an empty argument
+ // is the same as not specifying the argument. This is convenient for
+ // scripting so later command line arguments can override earlier command
+ // line arguments or bitcoin.conf values. Currently the -dir= case cannot be
+ // distinguished from -dir case with no assignment, but #16545 would add the
+ // ability to distinguish these in the future (and treat the no-assign case
+ // like an imperative command or an error).
+ ResetArgs(local_args, "");
+ BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{"default"});
+ ResetArgs(local_args, "-dir=override");
+ BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{"override"});
+ ResetArgs(local_args, "-dir=");
+ BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{"default"});
+ ResetArgs(local_args, "-dir");
+ BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{"default"});
+ ResetArgs(local_args, "-nodir");
+ BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{""});
}
BOOST_AUTO_TEST_CASE(doubledash)
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index 908b030eea..fcb1a80765 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -4,17 +4,23 @@
#include <chainparams.h>
#include <clientversion.h>
+#include <compat.h>
#include <cstdint>
#include <net.h>
+#include <net_processing.h>
#include <netaddress.h>
#include <netbase.h>
+#include <netmessagemaker.h>
#include <serialize.h>
#include <span.h>
#include <streams.h>
#include <test/util/setup_common.h>
+#include <test/util/validation.h>
+#include <timedata.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
+#include <validation.h>
#include <version.h>
#include <boost/test/unit_test.hpp>
@@ -27,7 +33,7 @@
using namespace std::literals;
-BOOST_FIXTURE_TEST_SUITE(net_tests, BasicTestingSetup)
+BOOST_FIXTURE_TEST_SUITE(net_tests, RegTestingSetup)
BOOST_AUTO_TEST_CASE(cnode_listen_port)
{
@@ -607,15 +613,15 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
// set up local addresses; all that's necessary to reproduce the bug is
// that a normal IPv4 address is among the entries, but if this address is
// !IsRoutable the undefined behavior is easier to trigger deterministically
+ in_addr raw_addr;
+ raw_addr.s_addr = htonl(0x7f000001);
+ const CNetAddr mapLocalHost_entry = CNetAddr(raw_addr);
{
LOCK(g_maplocalhost_mutex);
- in_addr ipv4AddrLocal;
- ipv4AddrLocal.s_addr = 0x0100007f;
- CNetAddr addr = CNetAddr(ipv4AddrLocal);
LocalServiceInfo lsi;
lsi.nScore = 23;
lsi.nPort = 42;
- mapLocalHost[addr] = lsi;
+ mapLocalHost[mapLocalHost_entry] = lsi;
}
// create a peer with an IPv4 address
@@ -646,8 +652,79 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test)
// suppress no-checks-run warning; if this test fails, it's by triggering a sanitizer
BOOST_CHECK(1);
+
+ // Cleanup, so that we don't confuse other tests.
+ {
+ LOCK(g_maplocalhost_mutex);
+ mapLocalHost.erase(mapLocalHost_entry);
+ }
}
+BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port)
+{
+ // Test that GetLocalAddrForPeer() properly selects the address to self-advertise:
+ //
+ // 1. GetLocalAddrForPeer() calls GetLocalAddress() which returns an address that is
+ // not routable.
+ // 2. GetLocalAddrForPeer() overrides the address with whatever the peer has told us
+ // he sees us as.
+ // 2.1. For inbound connections we must override both the address and the port.
+ // 2.2. For outbound connections we must override only the address.
+
+ // Pretend that we bound to this port.
+ const uint16_t bind_port = 20001;
+ m_node.args->ForceSetArg("-bind", strprintf("3.4.5.6:%u", bind_port));
+
+ // Our address:port as seen from the peer, completely different from the above.
+ in_addr peer_us_addr;
+ peer_us_addr.s_addr = htonl(0x02030405);
+ const CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK};
+
+ // Create a peer with a routable IPv4 address (outbound).
+ in_addr peer_out_in_addr;
+ peer_out_in_addr.s_addr = htonl(0x01020304);
+ CNode peer_out{/*id=*/0,
+ /*nLocalServicesIn=*/NODE_NETWORK,
+ /*sock=*/nullptr,
+ /*addrIn=*/CAddress{CService{peer_out_in_addr, 8333}, NODE_NETWORK},
+ /*nKeyedNetGroupIn=*/0,
+ /*nLocalHostNonceIn=*/0,
+ /*addrBindIn=*/CAddress{},
+ /*addrNameIn=*/std::string{},
+ /*conn_type_in=*/ConnectionType::OUTBOUND_FULL_RELAY,
+ /*inbound_onion=*/false};
+ peer_out.fSuccessfullyConnected = true;
+ peer_out.SetAddrLocal(peer_us);
+
+ // Without the fix peer_us:8333 is chosen instead of the proper peer_us:bind_port.
+ auto chosen_local_addr = GetLocalAddrForPeer(&peer_out);
+ BOOST_REQUIRE(chosen_local_addr);
+ const CService expected{peer_us_addr, bind_port};
+ BOOST_CHECK(*chosen_local_addr == expected);
+
+ // Create a peer with a routable IPv4 address (inbound).
+ in_addr peer_in_in_addr;
+ peer_in_in_addr.s_addr = htonl(0x05060708);
+ CNode peer_in{/*id=*/0,
+ /*nLocalServicesIn=*/NODE_NETWORK,
+ /*sock=*/nullptr,
+ /*addrIn=*/CAddress{CService{peer_in_in_addr, 8333}, NODE_NETWORK},
+ /*nKeyedNetGroupIn=*/0,
+ /*nLocalHostNonceIn=*/0,
+ /*addrBindIn=*/CAddress{},
+ /*addrNameIn=*/std::string{},
+ /*conn_type_in=*/ConnectionType::INBOUND,
+ /*inbound_onion=*/false};
+ peer_in.fSuccessfullyConnected = true;
+ peer_in.SetAddrLocal(peer_us);
+
+ // Without the fix peer_us:8333 is chosen instead of the proper peer_us:peer_us.GetPort().
+ chosen_local_addr = GetLocalAddrForPeer(&peer_in);
+ BOOST_REQUIRE(chosen_local_addr);
+ BOOST_CHECK(*chosen_local_addr == peer_us);
+
+ m_node.args->ForceSetArg("-bind", "");
+}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network)
{
@@ -728,4 +805,108 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle)
BOOST_CHECK(!IsLocal(addr));
}
+BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
+{
+ // Tests the following scenario:
+ // * -bind=3.4.5.6:20001 is specified
+ // * we make an outbound connection to a peer
+ // * the peer reports he sees us as 2.3.4.5:20002 in the version message
+ // (20002 is a random port assigned by our OS for the outgoing TCP connection,
+ // we cannot accept connections to it)
+ // * we should self-advertise to that peer as 2.3.4.5:20001
+
+ // Pretend that we bound to this port.
+ const uint16_t bind_port = 20001;
+ m_node.args->ForceSetArg("-bind", strprintf("3.4.5.6:%u", bind_port));
+ m_node.args->ForceSetArg("-capturemessages", "1");
+
+ // Our address:port as seen from the peer - 2.3.4.5:20002 (different from the above).
+ in_addr peer_us_addr;
+ peer_us_addr.s_addr = htonl(0x02030405);
+ const CService peer_us{peer_us_addr, 20002};
+
+ // Create a peer with a routable IPv4 address.
+ in_addr peer_in_addr;
+ peer_in_addr.s_addr = htonl(0x01020304);
+ CNode peer{/*id=*/0,
+ /*nLocalServicesIn=*/NODE_NETWORK,
+ /*sock=*/nullptr,
+ /*addrIn=*/CAddress{CService{peer_in_addr, 8333}, NODE_NETWORK},
+ /*nKeyedNetGroupIn=*/0,
+ /*nLocalHostNonceIn=*/0,
+ /*addrBindIn=*/CAddress{},
+ /*addrNameIn=*/std::string{},
+ /*conn_type_in=*/ConnectionType::OUTBOUND_FULL_RELAY,
+ /*inbound_onion=*/false};
+
+ const uint64_t services{NODE_NETWORK | NODE_WITNESS};
+ const int64_t time{0};
+ const CNetMsgMaker msg_maker{PROTOCOL_VERSION};
+
+ // Force CChainState::IsInitialBlockDownload() to return false.
+ // Otherwise PushAddress() isn't called by PeerManager::ProcessMessage().
+ TestChainState& chainstate =
+ *static_cast<TestChainState*>(&m_node.chainman->ActiveChainstate());
+ chainstate.JumpOutOfIbd();
+
+ m_node.peerman->InitializeNode(&peer);
+
+ std::atomic<bool> interrupt_dummy{false};
+ std::chrono::microseconds time_received_dummy{0};
+
+ const auto msg_version =
+ msg_maker.Make(NetMsgType::VERSION, PROTOCOL_VERSION, services, time, services, peer_us);
+ CDataStream msg_version_stream{msg_version.data, SER_NETWORK, PROTOCOL_VERSION};
+
+ m_node.peerman->ProcessMessage(
+ peer, NetMsgType::VERSION, msg_version_stream, time_received_dummy, interrupt_dummy);
+
+ const auto msg_verack = msg_maker.Make(NetMsgType::VERACK);
+ CDataStream msg_verack_stream{msg_verack.data, SER_NETWORK, PROTOCOL_VERSION};
+
+ // Will set peer.fSuccessfullyConnected to true (necessary in SendMessages()).
+ m_node.peerman->ProcessMessage(
+ peer, NetMsgType::VERACK, msg_verack_stream, time_received_dummy, interrupt_dummy);
+
+ // Ensure that peer_us_addr:bind_port is sent to the peer.
+ const CService expected{peer_us_addr, bind_port};
+ bool sent{false};
+
+ const auto CaptureMessageOrig = CaptureMessage;
+ CaptureMessage = [&sent, &expected](const CAddress& addr,
+ const std::string& msg_type,
+ Span<const unsigned char> data,
+ bool is_incoming) -> void {
+ if (!is_incoming && msg_type == "addr") {
+ CDataStream s(data, SER_NETWORK, PROTOCOL_VERSION);
+ std::vector<CAddress> addresses;
+
+ s >> addresses;
+
+ for (const auto& addr : addresses) {
+ if (addr == expected) {
+ sent = true;
+ return;
+ }
+ }
+ }
+ };
+
+ {
+ LOCK(peer.cs_sendProcessing);
+ m_node.peerman->SendMessages(&peer);
+ }
+
+ BOOST_CHECK(sent);
+
+ CaptureMessage = CaptureMessageOrig;
+ chainstate.ResetIbd();
+ m_node.args->ForceSetArg("-capturemessages", "0");
+ m_node.args->ForceSetArg("-bind", "");
+ // PeerManager::ProcessMessage() calls AddTimeData() which changes the internal state
+ // in timedata.cpp and later confuses the test "timedata_tests/addtimedata". Thus reset
+ // that state as it was before our test was run.
+ TestOnlyResetTimeData();
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/timedata_tests.cpp b/src/test/timedata_tests.cpp
index 1dcee23bbb..478d61d5e2 100644
--- a/src/test/timedata_tests.cpp
+++ b/src/test/timedata_tests.cpp
@@ -96,9 +96,10 @@ BOOST_AUTO_TEST_CASE(addtimedata)
// not to fix this because it prevents possible attacks. See the comment in AddTimeData() or issue #4521
// for a more detailed explanation.
MultiAddTimeData(2, 100); // filter median is 100 now, but nTimeOffset will not change
+ // We want this test to end with nTimeOffset==0, otherwise subsequent tests of the suite will fail.
BOOST_CHECK_EQUAL(GetTimeOffset(), 0);
- // We want this test to end with nTimeOffset==0, otherwise subsequent tests of the suite will fail.
+ TestOnlyResetTimeData();
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index c968e4d124..211153f06c 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -180,10 +180,9 @@ ChainTestingSetup::~ChainTestingSetup()
m_node.banman.reset();
m_node.addrman.reset();
m_node.args = nullptr;
- UnloadBlockIndex(m_node.mempool.get(), *m_node.chainman);
+ WITH_LOCK(::cs_main, UnloadBlockIndex(m_node.mempool.get(), *m_node.chainman));
m_node.mempool.reset();
m_node.scheduler.reset();
- m_node.chainman->Reset();
m_node.chainman.reset();
}
diff --git a/src/timedata.cpp b/src/timedata.cpp
index 541580b3ff..06659871e5 100644
--- a/src/timedata.cpp
+++ b/src/timedata.cpp
@@ -39,29 +39,31 @@ int64_t GetAdjustedTime()
#define BITCOIN_TIMEDATA_MAX_SAMPLES 200
+static std::set<CNetAddr> g_sources;
+static CMedianFilter<int64_t> g_time_offsets{BITCOIN_TIMEDATA_MAX_SAMPLES, 0};
+static bool g_warning_emitted;
+
void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample)
{
LOCK(g_timeoffset_mutex);
// Ignore duplicates
- static std::set<CNetAddr> setKnown;
- if (setKnown.size() == BITCOIN_TIMEDATA_MAX_SAMPLES)
+ if (g_sources.size() == BITCOIN_TIMEDATA_MAX_SAMPLES)
return;
- if (!setKnown.insert(ip).second)
+ if (!g_sources.insert(ip).second)
return;
// Add data
- static CMedianFilter<int64_t> vTimeOffsets(BITCOIN_TIMEDATA_MAX_SAMPLES, 0);
- vTimeOffsets.input(nOffsetSample);
- LogPrint(BCLog::NET, "added time data, samples %d, offset %+d (%+d minutes)\n", vTimeOffsets.size(), nOffsetSample, nOffsetSample / 60);
+ g_time_offsets.input(nOffsetSample);
+ LogPrint(BCLog::NET, "added time data, samples %d, offset %+d (%+d minutes)\n", g_time_offsets.size(), nOffsetSample, nOffsetSample / 60);
// There is a known issue here (see issue #4521):
//
- // - The structure vTimeOffsets contains up to 200 elements, after which
+ // - The structure g_time_offsets contains up to 200 elements, after which
// any new element added to it will not increase its size, replacing the
// oldest element.
//
// - The condition to update nTimeOffset includes checking whether the
- // number of elements in vTimeOffsets is odd, which will never happen after
+ // number of elements in g_time_offsets is odd, which will never happen after
// there are 200 elements.
//
// But in this case the 'bug' is protective against some attacks, and may
@@ -71,9 +73,9 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample)
// So we should hold off on fixing this and clean it up as part of
// a timing cleanup that strengthens it in a number of other ways.
//
- if (vTimeOffsets.size() >= 5 && vTimeOffsets.size() % 2 == 1) {
- int64_t nMedian = vTimeOffsets.median();
- std::vector<int64_t> vSorted = vTimeOffsets.sorted();
+ if (g_time_offsets.size() >= 5 && g_time_offsets.size() % 2 == 1) {
+ int64_t nMedian = g_time_offsets.median();
+ std::vector<int64_t> vSorted = g_time_offsets.sorted();
// Only let other nodes change our time by so much
int64_t max_adjustment = std::max<int64_t>(0, gArgs.GetIntArg("-maxtimeadjustment", DEFAULT_MAX_TIME_ADJUSTMENT));
if (nMedian >= -max_adjustment && nMedian <= max_adjustment) {
@@ -81,8 +83,7 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample)
} else {
nTimeOffset = 0;
- static bool fDone;
- if (!fDone) {
+ if (!g_warning_emitted) {
// If nobody has a time different than ours but within 5 minutes of ours, give a warning
bool fMatch = false;
for (const int64_t nOffset : vSorted) {
@@ -90,7 +91,7 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample)
}
if (!fMatch) {
- fDone = true;
+ g_warning_emitted = true;
bilingual_str strMessage = strprintf(_("Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly."), PACKAGE_NAME);
SetMiscWarning(strMessage);
uiInterface.ThreadSafeMessageBox(strMessage, "", CClientUIInterface::MSG_WARNING);
@@ -108,3 +109,12 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample)
}
}
}
+
+void TestOnlyResetTimeData()
+{
+ LOCK(g_timeoffset_mutex);
+ nTimeOffset = 0;
+ g_sources.clear();
+ g_time_offsets = CMedianFilter<int64_t>{BITCOIN_TIMEDATA_MAX_SAMPLES, 0};
+ g_warning_emitted = false;
+}
diff --git a/src/timedata.h b/src/timedata.h
index b165ecde26..2f039d5465 100644
--- a/src/timedata.h
+++ b/src/timedata.h
@@ -75,4 +75,9 @@ int64_t GetTimeOffset();
int64_t GetAdjustedTime();
void AddTimeData(const CNetAddr& ip, int64_t nTime);
+/**
+ * Reset the internal state of GetTimeOffset(), GetAdjustedTime() and AddTimeData().
+ */
+void TestOnlyResetTimeData();
+
#endif // BITCOIN_TIMEDATA_H
diff --git a/src/util/system.cpp b/src/util/system.cpp
index aa9122106b..1ad701c56d 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -387,9 +387,12 @@ std::optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) co
return std::nullopt;
}
-fs::path ArgsManager::GetPathArg(std::string pathlike_arg) const
+fs::path ArgsManager::GetPathArg(std::string arg, const fs::path& default_value) const
{
- auto result = fs::PathFromString(GetArg(pathlike_arg, "")).lexically_normal();
+ if (IsArgNegated(arg)) return fs::path{};
+ std::string path_str = GetArg(arg, "");
+ if (path_str.empty()) return default_value;
+ fs::path result = fs::PathFromString(path_str).lexically_normal();
// Remove trailing slash, if present.
return result.has_filename() ? result : result.parent_path();
}
@@ -516,12 +519,12 @@ bool ArgsManager::InitSettings(std::string& error)
bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp) const
{
- if (IsArgNegated("-settings")) {
+ fs::path settings = GetPathArg("-settings", fs::path{BITCOIN_SETTINGS_FILENAME});
+ if (settings.empty()) {
return false;
}
if (filepath) {
- std::string settings = GetArg("-settings", BITCOIN_SETTINGS_FILENAME);
- *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), fs::PathFromString(temp ? settings + ".tmp" : settings));
+ *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), temp ? settings + ".tmp" : settings);
}
return true;
}
diff --git a/src/util/system.h b/src/util/system.h
index f193c8ac0b..a66b597d41 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -271,16 +271,6 @@ protected:
std::optional<const Command> GetCommand() const;
/**
- * Get a normalized path from a specified pathlike argument
- *
- * It is guaranteed that the returned path has no trailing slashes.
- *
- * @param pathlike_arg Pathlike argument to get a path from (e.g., "-datadir", "-blocksdir" or "-walletdir")
- * @return Normalized path which is get from a specified pathlike argument
- */
- fs::path GetPathArg(std::string pathlike_arg) const;
-
- /**
* Get blocks directory path
*
* @return Blocks path which is network specific
@@ -343,6 +333,18 @@ protected:
std::string GetArg(const std::string& strArg, const std::string& strDefault) const;
/**
+ * Return path argument or default value
+ *
+ * @param arg Argument to get a path from (e.g., "-datadir", "-blocksdir" or "-walletdir")
+ * @param default_value Optional default value to return instead of the empty path.
+ * @return normalized path if argument is set, with redundant "." and ".."
+ * path components and trailing separators removed (see patharg unit test
+ * for examples or implementation for details). If argument is empty or not
+ * set, default_value is returned unchanged.
+ */
+ fs::path GetPathArg(std::string arg, const fs::path& default_value = {}) const;
+
+ /**
* Return integer argument or default value
*
* @param strArg Argument to get (e.g. "-foo")
diff --git a/src/validation.cpp b/src/validation.cpp
index 214112e2bd..c89c293a85 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -4070,7 +4070,7 @@ void CChainState::UnloadBlockIndex()
// block index state
void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman)
{
- LOCK(cs_main);
+ AssertLockHeld(::cs_main);
chainman.Unload();
pindexBestHeader = nullptr;
if (mempool) mempool->clear();
@@ -5056,15 +5056,6 @@ void ChainstateManager::Unload()
m_best_invalid = nullptr;
}
-void ChainstateManager::Reset()
-{
- LOCK(::cs_main);
- m_ibd_chainstate.reset();
- m_snapshot_chainstate.reset();
- m_active_chainstate = nullptr;
- m_snapshot_validated = false;
-}
-
void ChainstateManager::MaybeRebalanceCaches()
{
AssertLockHeld(::cs_main);
diff --git a/src/validation.h b/src/validation.h
index 7766d77a88..cc2247239f 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -138,7 +138,7 @@ extern CBlockIndex *pindexBestHeader;
extern const std::vector<std::string> CHECKLEVEL_DOC;
/** Unload database information */
-void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman);
+void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Run instances of script checking worker threads */
void StartScriptCheckWorkerThreads(int threads_num);
/** Stop all of the script checking worker threads */
@@ -991,17 +991,13 @@ public:
//! Unload block index and chain data before shutdown.
void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
- //! Clear (deconstruct) chainstate data.
- void Reset();
-
//! Check to see if caches are out of balance and if so, call
//! ResizeCoinsCaches() as needed.
void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
~ChainstateManager() {
LOCK(::cs_main);
- UnloadBlockIndex(/* mempool */ nullptr, *this);
- Reset();
+ UnloadBlockIndex(/*mempool=*/nullptr, *this);
}
};