diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 100 | ||||
-rw-r--r-- | src/bench/bench_bitcoin.cpp | 4 | ||||
-rw-r--r-- | src/bitcoin-chainstate.cpp | 262 | ||||
-rw-r--r-- | src/init.cpp | 23 | ||||
-rw-r--r-- | src/init/common.cpp | 2 | ||||
-rw-r--r-- | src/net.cpp | 48 | ||||
-rw-r--r-- | src/net.h | 21 | ||||
-rw-r--r-- | src/net_processing.cpp | 11 | ||||
-rw-r--r-- | src/qt/guiutil.cpp | 29 | ||||
-rw-r--r-- | src/script/descriptor.cpp | 68 | ||||
-rw-r--r-- | src/script/script.h | 3 | ||||
-rw-r--r-- | src/script/sign.cpp | 23 | ||||
-rw-r--r-- | src/script/standard.cpp | 72 | ||||
-rw-r--r-- | src/script/standard.h | 4 | ||||
-rw-r--r-- | src/test/fuzz/script_format.cpp | 5 | ||||
-rw-r--r-- | src/test/getarg_tests.cpp | 18 | ||||
-rw-r--r-- | src/test/net_tests.cpp | 191 | ||||
-rw-r--r-- | src/test/timedata_tests.cpp | 3 | ||||
-rw-r--r-- | src/test/util/setup_common.cpp | 3 | ||||
-rw-r--r-- | src/timedata.cpp | 38 | ||||
-rw-r--r-- | src/timedata.h | 5 | ||||
-rw-r--r-- | src/util/system.cpp | 13 | ||||
-rw-r--r-- | src/util/system.h | 22 | ||||
-rw-r--r-- | src/validation.cpp | 11 | ||||
-rw-r--r-- | src/validation.h | 8 |
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; @@ -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); } }; |