diff --git a/src/init.cpp b/src/init.cpp index 863c90c4f..0fabdf83a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1,3080 +1,3113 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif #include <init.h> #include <addrman.h> #include <amount.h> #include <avalanche/avalanche.h> #include <avalanche/processor.h> #include <avalanche/proof.h> // For AVALANCHE_LEGACY_PROOF_DEFAULT #include <avalanche/validation.h> #include <banman.h> #include <blockdb.h> #include <blockfilter.h> #include <chain.h> #include <chainparams.h> #include <checkpoints.h> #include <compat/sanity.h> #include <config.h> #include <consensus/validation.h> #include <currencyunit.h> #include <flatfile.h> #include <fs.h> #include <hash.h> #include <httprpc.h> #include <httpserver.h> #include <index/blockfilterindex.h> #include <index/txindex.h> #include <interfaces/chain.h> #include <interfaces/node.h> #include <key.h> #include <miner.h> #include <net.h> #include <net_permissions.h> #include <net_processing.h> #include <netbase.h> #include <network.h> #include <node/context.h> #include <node/ui_interface.h> #include <policy/mempool.h> #include <policy/policy.h> #include <policy/settings.h> #include <rpc/blockchain.h> #include <rpc/register.h> #include <rpc/server.h> #include <rpc/util.h> #include <scheduler.h> #include <script/scriptcache.h> #include <script/sigcache.h> #include <script/standard.h> #include <shutdown.h> #include <sync.h> #include <timedata.h> #include <torcontrol.h> #include <txdb.h> #include <txmempool.h> #include <util/asmap.h> #include <util/check.h> #include <util/moneystr.h> #include <util/string.h> #include <util/threadnames.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> #include <walletinitinterface.h> #include <boost/algorithm/string/replace.hpp> #include <boost/signals2/signal.hpp> #include <boost/thread/thread.hpp> #if ENABLE_ZMQ #include <zmq/zmqabstractnotifier.h> #include <zmq/zmqnotificationinterface.h> #include <zmq/zmqrpc.h> #endif #ifndef WIN32 #include <attributes.h> #include <cerrno> #include <csignal> #include <sys/stat.h> #endif #include <cstdint> #include <cstdio> #include <functional> #include <set> static const bool DEFAULT_PROXYRANDOMIZE = true; static const bool DEFAULT_REST_ENABLE = false; static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false; #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for accessing // block files don't count towards the fd_set size limit anyway. #define MIN_CORE_FILEDESCRIPTORS 0 #else #define MIN_CORE_FILEDESCRIPTORS 150 #endif static const char *DEFAULT_ASMAP_FILENAME = "ip_asn.map"; /** * The PID file facilities. */ static const char *BITCOIN_PID_FILENAME = "bitcoind.pid"; static fs::path GetPidFile(const ArgsManager &args) { return AbsPathForConfigVal( fs::path(args.GetArg("-pid", BITCOIN_PID_FILENAME))); } NODISCARD static bool CreatePidFile(const ArgsManager &args) { fsbridge::ofstream file{GetPidFile(args)}; if (file) { #ifdef WIN32 tfm::format(file, "%d\n", GetCurrentProcessId()); #else tfm::format(file, "%d\n", getpid()); #endif return true; } else { return InitError(strprintf(_("Unable to create the PID file '%s': %s"), GetPidFile(args).string(), std::strerror(errno))); } } ////////////////////////////////////////////////////////////////////////////// // // Shutdown // // // Thread management and startup/shutdown: // // The network-processing threads are all part of a thread group created by // AppInit() or the Qt main() function. // // A clean exit happens when StartShutdown() or the SIGTERM signal handler sets // fRequestShutdown, which makes main thread's WaitForShutdown() interrupts the // thread group. // And then, WaitForShutdown() makes all other on-going threads in the thread // group join the main thread. // Shutdown() is then called to clean up database connections, and stop other // threads that should only be stopped after the main network-processing threads // have exited. // // Shutdown for Qt is very similar, only it uses a QTimer to detect // ShutdownRequested() getting set, and then does the normal Qt shutdown thing. // static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; static std::thread g_load_block; static boost::thread_group threadGroup; void Interrupt(NodeContext &node) { InterruptHTTPServer(); InterruptHTTPRPC(); InterruptRPC(); InterruptREST(); InterruptTorControl(); InterruptMapPort(); if (g_avalanche) { // Avalanche needs to be stopped before we interrupt the thread group as // the scheduler will stop working then. g_avalanche->stopEventLoop(); } if (node.connman) { node.connman->Interrupt(); } if (g_txindex) { g_txindex->Interrupt(); } ForEachBlockFilterIndex([](BlockFilterIndex &index) { index.Interrupt(); }); } void Shutdown(NodeContext &node) { static Mutex g_shutdown_mutex; TRY_LOCK(g_shutdown_mutex, lock_shutdown); if (!lock_shutdown) { return; } LogPrintf("%s: In progress...\n", __func__); Assert(node.args); /// Note: Shutdown() must be able to handle cases in which initialization /// failed part of the way, for example if the data directory was found to /// be locked. Be sure that anything that writes files or flushes caches /// only does this if the respective module was initialized. util::ThreadRename("shutoff"); if (node.mempool) { node.mempool->AddTransactionsUpdated(1); } StopHTTPRPC(); StopREST(); StopRPC(); StopHTTPServer(); for (const auto &client : node.chain_clients) { client->flush(); } StopMapPort(); // Because avalanche and the network depend on each other, it is important // to shut them down in this order: // 1. Stop avalanche event loop. // 2. Shutdown network processing. // 3. Destroy avalanche::Processor. // 4. Destroy CConnman if (g_avalanche) { g_avalanche->stopEventLoop(); } // Because these depend on each-other, we make sure that neither can be // using the other before destroying them. if (node.peerman) { UnregisterValidationInterface(node.peerman.get()); } // Follow the lock order requirements: // * CheckForStaleTipAndEvictPeers locks cs_main before indirectly calling // GetExtraOutboundCount which locks cs_vNodes. // * ProcessMessage locks cs_main and g_cs_orphans before indirectly calling // ForEachNode which locks cs_vNodes. // * CConnman::Stop calls DeleteNode, which calls FinalizeNode, which locks // cs_main and calls EraseOrphansFor, which locks g_cs_orphans. // // Thus the implicit locking order requirement is: // (1) cs_main, (2) g_cs_orphans, (3) cs_vNodes. if (node.connman) { node.connman->StopThreads(); LOCK2(::cs_main, ::g_cs_orphans); node.connman->StopNodes(); } StopTorControl(); // After everything has been shut down, but before things get flushed, stop // the CScheduler/checkqueue, threadGroup and load block thread. if (node.scheduler) { node.scheduler->stop(); } if (g_load_block.joinable()) { g_load_block.join(); } threadGroup.interrupt_all(); threadGroup.join_all(); // After the threads that potentially access these pointers have been // stopped, destruct and reset all to nullptr. node.peerman.reset(); // Destroy various global instances g_avalanche.reset(); node.connman.reset(); node.banman.reset(); if (node.mempool && node.mempool->IsLoaded() && node.args->GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { DumpMempool(*node.mempool); } // FlushStateToDisk generates a ChainStateFlushed callback, which we should // avoid missing if (node.chainman) { LOCK(cs_main); for (CChainState *chainstate : node.chainman->GetAll()) { if (chainstate->CanFlushToDisk()) { chainstate->ForceFlushStateToDisk(); } } } // After there are no more peers/RPC left to give us new data which may // generate CValidationInterface callbacks, flush them... GetMainSignals().FlushBackgroundCallbacks(); // Stop and delete all indexes only after flushing background callbacks. if (g_txindex) { g_txindex->Stop(); g_txindex.reset(); } ForEachBlockFilterIndex([](BlockFilterIndex &index) { index.Stop(); }); DestroyAllBlockFilterIndexes(); // Any future callbacks will be dropped. This should absolutely be safe - if // missing a callback results in an unrecoverable situation, unclean // shutdown would too. The only reason to do the above flushes is to let the // wallet catch up with our current chain to avoid any strange pruning edge // cases and make next startup faster by avoiding rescan. if (node.chainman) { LOCK(cs_main); for (CChainState *chainstate : node.chainman->GetAll()) { if (chainstate->CanFlushToDisk()) { chainstate->ForceFlushStateToDisk(); chainstate->ResetCoinsViews(); } } pblocktree.reset(); } for (const auto &client : node.chain_clients) { client->stop(); } #if ENABLE_ZMQ if (g_zmq_notification_interface) { UnregisterValidationInterface(g_zmq_notification_interface); delete g_zmq_notification_interface; g_zmq_notification_interface = nullptr; } #endif node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); globalVerifyHandle.reset(); ECC_Stop(); node.mempool.reset(); node.chainman = nullptr; node.scheduler.reset(); try { if (!fs::remove(GetPidFile(*node.args))) { LogPrintf("%s: Unable to remove PID file: File does not exist\n", __func__); } } catch (const fs::filesystem_error &e) { LogPrintf("%s: Unable to remove PID file: %s\n", __func__, fsbridge::get_filesystem_error_message(e)); } node.args = nullptr; LogPrintf("%s: done\n", __func__); } /** * Signal handlers are very limited in what they are allowed to do. * The execution context the handler is invoked in is not guaranteed, * so we restrict handler operations to just touching variables: */ #ifndef WIN32 static void HandleSIGTERM(int) { StartShutdown(); } static void HandleSIGHUP(int) { LogInstance().m_reopen_file = true; } #else static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) { StartShutdown(); Sleep(INFINITE); return true; } #endif #ifndef WIN32 static void registerSignalHandler(int signal, void (*handler)(int)) { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(signal, &sa, NULL); } #endif static boost::signals2::connection rpc_notify_block_change_connection; static void OnRPCStarted() { rpc_notify_block_change_connection = uiInterface.NotifyBlockTip_connect( std::bind(RPCNotifyBlockChange, std::placeholders::_2)); } static void OnRPCStopped() { rpc_notify_block_change_connection.disconnect(); RPCNotifyBlockChange(nullptr); g_best_block_cv.notify_all(); LogPrint(BCLog::RPC, "RPC stopped.\n"); } void SetupServerArgs(NodeContext &node) { assert(!node.args); node.args = &gArgs; ArgsManager &argsman = *node.args; SetupHelpOptions(argsman); SetupCurrencyUnitOptions(argsman); // server-only for now argsman.AddArg("-help-debug", "Print help message with debugging options and exit", false, OptionsCategory::DEBUG_TEST); const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); const auto regtestBaseParams = CreateBaseChainParams(CBaseChainParams::REGTEST); const auto defaultChainParams = CreateChainParams(CBaseChainParams::MAIN); const auto testnetChainParams = CreateChainParams(CBaseChainParams::TESTNET); const auto regtestChainParams = CreateChainParams(CBaseChainParams::REGTEST); // Hidden Options std::vector<std::string> hidden_args = { "-dbcrashratio", "-forcecompactdb", "-parkdeepreorg", "-automaticunparking", "-replayprotectionactivationtime", "-enableminerfund", // GUI args. These will be overwritten by SetupUIArgs for the GUI "-allowselfsignedrootcertificates", "-choosedatadir", "-lang=<lang>", "-min", "-resetguisettings", "-rootcertificates=<file>", "-splash", "-uiplatform", // TODO remove after the November 2020 upgrade "-axionactivationtime"}; // Set all of the args and their help // When adding new options to the categories, please keep and ensure // alphabetical ordering. Do not translate _(...) -help-debug options, Many // technical terms, and only a very small audience, so is unnecessary stress // to translators. argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if defined(HAVE_SYSTEM) argsman.AddArg( "-alertnotify=<cmd>", "Execute command when a relevant alert is received or we see " "a really long fork (%s in cmd is replaced by message)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif argsman.AddArg( "-assumevalid=<hex>", strprintf( "If this block is in the chain assume that it and its ancestors " "are valid and potentially skip their script verification (0 to " "verify all, default: %s, testnet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat " "files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if defined(HAVE_SYSTEM) argsman.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is " "replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif argsman.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact " "block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-blocksonly", strprintf("Whether to reject transactions from network peers. " "Automatic broadcast and rebroadcast of any transactions " "from inbound peers is disabled, unless the peer has the " "'forcerelay' permission. RPC transactions are" " not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-conf=<file>", strprintf("Specify path to read-only configuration file. Relative " "paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", DEFAULT_DB_BATCH_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg( "-dbcache=<n>", strprintf("Set database cache size in MiB (%d to %d, default: %d)", MIN_DB_CACHE_MB, MAX_DB_CACHE_MB, DEFAULT_DB_CACHE_MB), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths " "will be prefixed by a net-specific datadir " "location. (0 to disable; default: %s)", DEFAULT_DEBUGLOGFILE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-feefilter", strprintf("Tell other nodes to filter invs to us by our " "mempool min fee (default: %d)", DEFAULT_FEEFILTER), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg( "-finalizationdelay=<n>", strprintf("Set the minimum amount of time to wait between a " "block header reception and the block finalization. " "Unit is seconds (default: %d)", DEFAULT_MIN_FINALIZATION_DELAY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path " "(only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxreorgdepth=<n>", strprintf("Configure at what depth blocks are considered " "final (default: %d). Use -1 to disable.", DEFAULT_MAX_REORG_DEPTH), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-loadblock=<file>", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> " "megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in " "memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer " "than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-minimumchainwork=<hex>", strprintf( "Minimum work assumed to exist on a valid chain in hex " "(default: %s, testnet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg( "-par=<n>", strprintf("Set the number of script verification threads (%u to %d, 0 " "= auto, <0 = leave that many cores free, default: %d)", -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load " "on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed " "by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) " "of old blocks. This allows the pruneblockchain RPC to be " "called to delete specific blocks, and enables automatic " "pruning of old blocks if a target size in MiB is provided. " "This mode is incompatible with -txindex and -rescan. " "Warning: Reverting this setting requires re-downloading the " "entire blockchain. (default: 0 = disable pruning blocks, 1 " "= allow manual pruning via RPC, >=%u = automatically prune " "block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When " "in pruning mode or if blocks on disk might be corrupted, use " "full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-settings=<file>", strprintf( "Specify path to dynamic settings data file. Can be disabled with " "-nosettings. File is written at runtime and not meant to be " "edited by users (use %s instead for custom settings). Relative " "paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if HAVE_SYSTEM argsman.AddArg("-startupnotify=<cmd>", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif #ifndef WIN32 argsman.AddArg( "-sysperms", "Create new files with system default permissions, instead of umask " "077 (only effective with disabled wallet functionality)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #else hidden_args.emplace_back("-sysperms"); #endif argsman.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the " "getrawtransaction rpc call (default: %d)", DEFAULT_TXINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-blockfilterindex=<type>", strprintf("Maintain an index of compact filters by block " "(default: %s, values: %s).", DEFAULT_BLOCKFILTERINDEX, ListBlockFilterTypes()) + " If <type> is not supplied or if <type> = 1, indexes for " "all known types are enabled.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-usecashaddr", "Use Cash Address for destination encoding instead of base58 " "(activate by default on Jan, 14)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg( "-addnode=<ip>", "Add a node to connect to and attempt to keep the connection " "open (see the `addnode` RPC command help for more info)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the " "peers (default: %s). Relative paths will be " "prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually " "configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-bind=<addr>", - "Bind to given address and always listen on it. Use " - "[host]:port notation for IPv6", - ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, - OptionsCategory::CONNECTION); + argsman.AddArg( + "-bind=<addr>[:<port>][=onion]", + strprintf("Bind to given address and always listen on it (default: " + "0.0.0.0). Use [host]:port notation for IPv6. Append =onion " + "to tag any incoming connections to that address and port as " + "incoming Tor connections (default: 127.0.0.1:%u=onion, " + "testnet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", + defaultBaseParams->OnionServiceTargetPort(), + testnetBaseParams->OnionServiceTargetPort(), + regtestBaseParams->OnionServiceTargetPort()), + ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, + OptionsCategory::CONNECTION); argsman.AddArg( "-connect=<ip>", "Connect only to the specified node(s); -connect=0 disables automatic " "connections (the rules for this peer are the same as for -addnode)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg( "-discover", "Discover own IP addresses (default: 1 when listening and no " "-externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and " "-connect (default: %d)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-dnsseed", "Query for peer addresses via DNS lookup, if low on addresses " "(default: 1 unless -connect used)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-externalip=<ip>", "Specify your own public address", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-forcednsseed", strprintf( "Always query for peer addresses via DNS lookup (default: %d)", DEFAULT_FORCEDNSSEED), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-overridednsseed", "If set, only use the specified DNS seed when " "querying for peer addresses via DNS lookup.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-listen", "Accept connections from outside (default: 1 if no -proxy or -connect)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-listenonion", strprintf("Automatically create Tor onion service (default: %d)", DEFAULT_LISTEN_ONION), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-maxconnections=<n>", strprintf("Maintain at most <n> connections to peers. The effective " "limit depends on system limitations and might be lower than " "the specified value (default: %u)", DEFAULT_MAX_PEER_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 " "bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-maxsendbuffer=<n>", strprintf( "Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local " "perspective of time may be influenced by peers forward or " "backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onion=<ip:port>", strprintf("Use separate SOCKS5 proxy to reach peers via Tor " "onion services (default: %s)", "-proxy"), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-onlynet=<net>", "Make outgoing connections only through network <net> (ipv4, ipv6 or " "onion). 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("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with " "bloom filters (default: %d)", 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: %d)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, " "testnet: %u, regtest: %u)", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-proxyrandomize", strprintf("Randomize credentials for every proxy connection. " "This enables Tor stream isolation (default: %d)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); gArgs.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed " "by the setnetworkactive RPC command", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION); argsman.AddArg("-timeout=<n>", strprintf("Specify connection timeout in milliseconds " "(minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option " "determines the amount of time a peer may be inactive before " "the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), true, OptionsCategory::CONNECTION); argsman.AddArg( "-torcontrol=<ip>:<port>", strprintf( "Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::CONNECTION); #ifdef USE_UPNP #if USE_UPNP argsman.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when " "listening and no -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #else argsman.AddArg( "-upnp", strprintf("Use UPnP to map the listening port (default: %u)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); #endif #else hidden_args.emplace_back("-upnp"); #endif argsman.AddArg( "-whitebind=<[permissions@]addr>", "Bind to the given address and add permission flags to the peers " "connecting to it." "Use [host]:port notation for IPv6. Allowed permissions: " + Join(NET_PERMISSIONS_DOC, ", ") + ". " "Specify multiple permissions separated by commas (default: " "download,noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-whitelist=<[permissions@]IP address or network>", "Add permission flags to the peers connecting from the " "given IP address (e.g. 1.2.3.4) or CIDR-notated network " "(e.g. 1.2.3.0/24). " "Uses the same permissions as -whitebind. Can be specified " "multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg( "-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in " "MiB per 24h). Limit does not apply to peers with 'download' " "permission. 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); g_wallet_init_interface.AddWalletOptions(argsman); #if ENABLE_ZMQ argsman.AddArg("-zmqpubhashblock=<address>", "Enable publish hash block in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubhashtx=<address>", "Enable publish hash transaction in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubrawblock=<address>", "Enable publish raw block in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubrawtx=<address>", "Enable publish raw transaction in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubsequence=<address>", "Enable publish hash block and tx sequence in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg( "-zmqpubhashblockhwm=<n>", strprintf("Set publish hash block outbound message high water " "mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg( "-zmqpubhashtxhwm=<n>", strprintf("Set publish hash transaction outbound message high " "water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); argsman.AddArg( "-zmqpubrawblockhwm=<n>", strprintf("Set publish raw block outbound message high water " "mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg( "-zmqpubrawtxhwm=<n>", strprintf("Set publish raw transaction outbound message high " "water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubsequencehwm=<n>", strprintf("Set publish hash sequence message high water mark" " (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); #else hidden_args.emplace_back("-zmqpubhashblock=<address>"); hidden_args.emplace_back("-zmqpubhashtx=<address>"); hidden_args.emplace_back("-zmqpubrawblock=<address>"); hidden_args.emplace_back("-zmqpubrawtx=<address>"); hidden_args.emplace_back("-zmqpubsequence=<n>"); hidden_args.emplace_back("-zmqpubhashblockhwm=<n>"); hidden_args.emplace_back("-zmqpubhashtxhwm=<n>"); hidden_args.emplace_back("-zmqpubrawblockhwm=<n>"); hidden_args.emplace_back("-zmqpubrawtxhwm=<n>"); hidden_args.emplace_back("-zmqpubsequencehwm=<n>"); #endif argsman.AddArg( "-checkblocks=<n>", strprintf("How many blocks to check at startup (default: %u, 0 = all)", DEFAULT_CHECKBLOCKS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-checklevel=<n>", strprintf("How thorough the block verification of " "-checkblocks is: %s (0-4, default: %u)", Join(CHECKLEVEL_DOC, ", "), DEFAULT_CHECKLEVEL), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-checkblockindex", strprintf("Do a consistency check for the block tree, " "chainstate, and other validation data structures " "occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-checkmempool=<n>", strprintf( "Run checks every <n> transactions (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-checkpoints", strprintf("Only accept block chain matching built-in " "checkpoints (default: %d)", DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %d)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-stopatheight", strprintf("Stop running after reaching the given height in " "the main chain (default: %u)", DEFAULT_STOPATHEIGHT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-limitancestorcount=<n>", strprintf("Do not accept transactions if number of in-mempool " "ancestors is <n> or more (default: %u)", DEFAULT_ANCESTOR_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool " "ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> " "or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more " "than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-addrmantest", "Allows to test address relay on localhost", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-debug=<category>", strprintf("Output debugging information (default: %u, " "supplying <category> is optional)", 0) + ". " + "If <category> is not supplied or if <category> = 1, " "output all debugging information. <category> can be: " + LogInstance().LogCategoriesString() + ".", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-debugexclude=<category>", strprintf("Exclude debugging information for a category. Can be used " "in conjunction with -debug=1 to output debug logs for all " "categories except one or more specified categories."), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-logips", strprintf("Include IP addresses in debug output (default: %d)", DEFAULT_LOGIPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-logtimestamps", strprintf("Prepend debug output with timestamp (default: %d)", DEFAULT_LOGTIMESTAMPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); #ifdef HAVE_THREAD_LOCAL argsman.AddArg( "-logthreadnames", strprintf( "Prepend debug output with name of the originating thread (only " "available on platforms supporting thread_local) (default: %u)", DEFAULT_LOGTHREADNAMES), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); #else hidden_args.emplace_back("-logthreadnames"); #endif argsman.AddArg( "-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %d)", DEFAULT_LOGTIMEMICROS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-maxsigcachesize=<n>", strprintf("Limit size of signature cache to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-maxscriptcachesize=<n>", strprintf("Limit size of script cache to <n> MiB (default: %u)", DEFAULT_MAX_SCRIPT_CACHE_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in " "initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-printtoconsole", "Send trace/debug info to console instead of debug.log file (default: " "1 when no -daemon. To disable logging to file, set debuglogfile=0)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-printpriority", strprintf("Log transaction priority and fee per kB when " "mining blocks (default: %d)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg( "-shrinkdebugfile", "Shrink debug.log file on client startup (default: 1 when no -debug)", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-uaclientname=<clientname>", "Set user agent client name", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-uaclientversion=<clientversion>", "Set user agent client version", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); SetupChainParamsBaseOptions(argsman); argsman.AddArg( "-acceptnonstdtxn", strprintf( "Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", defaultChainParams->RequireStandard()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-excessiveblocksize=<n>", strprintf("Do not accept blocks larger than this limit, in " "bytes (default: %d)", DEFAULT_MAX_BLOCK_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); const auto &ticker = Currency::get().ticker; argsman.AddArg( "-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define dust, the value of an " "output such that it will cost about 1/3 of its value in " "fees at this fee rate to spend it. (default: %s)", ticker, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for " "relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg( "-datacarrier", strprintf("Relay and mine data carrier transactions (default: %d)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg( "-datacarriersize", strprintf("Maximum size of data in data carrier transactions " "we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg( "-minrelaytxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are rejected for " "relaying, mining and transaction creation (default: %s)", ticker, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE_PER_KB)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg( "-whitelistrelay", strprintf("Add 'relay' permission to whitelisted inbound peers " "with default permissions. This will accept relayed " "transactions even when not relaying transactions " "(default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg( "-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers" " with default permissions. This will relay transactions " "even if the transactions were already in the mempool " "(default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); // Not sure this really belongs here, but it will do for now. // FIXME: This doesn't work anyways. argsman.AddArg("-excessutxocharge=<amt>", strprintf("Fees (in %s/kB) to charge per utxo created for " "relaying, and mining (default: %s)", ticker, FormatMoney(DEFAULT_UTXO_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-blockmaxsize=<n>", strprintf("Set maximum block size in bytes (default: %d)", DEFAULT_MAX_GENERATED_BLOCK_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg( "-blockmintxfee=<amt>", strprintf("Set lowest fee rate (in %s/kB) for transactions to " "be included in block creation. (default: %s)", ticker, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION); argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rest", strprintf("Accept public REST requests (default: %d)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg( "-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not " "expose the RPC server to untrusted networks such as the public " "internet! This option is ignored unless -rpcallowip is also passed. " "Port is optional and overrides -rpcport. Use [host]:port notation " "for IPv6. This option can be specified multiple times (default: " "127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg( "-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed " "by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg( "-rpcwhitelist=<whitelist>", "Set a whitelist to filter incoming RPC calls for a specific user. The " "field <whitelist> comes in the format: <USERNAME>:<rpc 1>,<rpc " "2>,...,<rpc n>. If multiple whitelists are set for a given user, they " "are set-intersected. See -rpcwhitelistdefault documentation for " "information on default whitelist behavior.", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg( "-rpcwhitelistdefault", "Sets default behavior for rpc whitelisting. Unless " "rpcwhitelistdefault is set to 0, if any -rpcwhitelist is set, the rpc " "server acts as if all rpc users are subject to " "empty-unless-otherwise-specified whitelists. If rpcwhitelistdefault " "is set to 1 and no -rpcwhitelist is set, rpc server acts as if all " "rpc users are subject to empty whitelists.", ArgsManager::ALLOW_BOOL, OptionsCategory::RPC); argsman.AddArg( "-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. " "The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A " "canonical python script is included in share/rpcauth. The client then " "connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> " "pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> " "(default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); argsman.AddArg( "-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid for " "<ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. " "1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). " "This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg( "-rpcthreads=<n>", strprintf( "Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg( "-rpccorsdomain=value", "Domain from which to accept cross origin requests (browser enforced)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC " "calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); #if HAVE_DECL_DAEMON argsman.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #else hidden_args.emplace_back("-daemon"); #endif // Avalanche options. argsman.AddArg( "-enableavalanche", strprintf("Enable avalanche (default: %u)", AVALANCHE_DEFAULT_ENABLED), ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg("-enableavalanchepeerdiscovery", strprintf("Enable avalanche peer discovery (default: %u)", AVALANCHE_DEFAULT_PEER_DISCOVERY_ENABLED), ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg( "-avacooldown", strprintf("Mandatory cooldown between two avapoll (default: %u)", AVALANCHE_DEFAULT_COOLDOWN), ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg( "-avadelegation", "Avalanche proof delegation to the master key used by this node " "(default: none). Should be used in conjunction with -avaproof and " "-avamasterkey", ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg("-avaproof", "Avalanche proof to be used by this node (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg( "-legacyavaproof", strprintf("Use the legacy avalanche proof format (default: %u)", AVALANCHE_DEFAULT_LEGACY_PROOF), ArgsManager::ALLOW_BOOL, OptionsCategory::AVALANCHE); argsman.AddArg("-avamasterkey", "Master key associated with the proof. If a proof is " "required, this is mandatory.", ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); argsman.AddArg("-avasessionkey", "Avalanche session key (default: random)", ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE); // Add the hidden options argsman.AddHiddenArgs(hidden_args); } std::string LicenseInfo() { const std::string URL_SOURCE_CODE = "<https://github.com/Bitcoin-ABC/bitcoin-abc>"; const std::string URL_WEBSITE = "<https://www.bitcoinabc.org>"; return CopyrightHolders(strprintf(_("Copyright (C) %i-%i").translated, 2009, COPYRIGHT_YEAR) + " ") + "\n" + "\n" + strprintf(_("Please contribute if you find %s useful. " "Visit %s for further information about the software.") .translated, PACKAGE_NAME, URL_WEBSITE) + "\n" + strprintf(_("The source code is available from %s.").translated, URL_SOURCE_CODE) + "\n" + "\n" + _("This is experimental software.").translated + "\n" + strprintf(_("Distributed under the MIT software license, see the " "accompanying file %s or %s") .translated, "COPYING", "<https://opensource.org/licenses/MIT>") + "\n" + "\n" + strprintf(_("This product includes software developed by the " "OpenSSL Project for use in the OpenSSL Toolkit %s and " "cryptographic software written by Eric Young and UPnP " "software written by Thomas Bernard.") .translated, "<https://www.openssl.org>") + "\n"; } static bool fHaveGenesis = false; static Mutex g_genesis_wait_mutex; static std::condition_variable g_genesis_wait_cv; static void BlockNotifyGenesisWait(const CBlockIndex *pBlockIndex) { if (pBlockIndex != nullptr) { { LOCK(g_genesis_wait_mutex); fHaveGenesis = true; } g_genesis_wait_cv.notify_all(); } } struct CImportingNow { CImportingNow() { assert(fImporting == false); fImporting = true; } ~CImportingNow() { assert(fImporting == true); fImporting = false; } }; // If we're using -prune with -reindex, then delete block files that will be // ignored by the reindex. Since reindexing works by starting at block file 0 // and looping until a blockfile is missing, do the same here to delete any // later block files after a gap. Also delete all rev files since they'll be // rewritten by the reindex anyway. This ensures that vinfoBlockFile is in sync // with what's actually on disk by the time we start downloading, so that // pruning works correctly. static void CleanupBlockRevFiles() { std::map<std::string, fs::path> mapBlockFiles; // Glob all blk?????.dat and rev?????.dat files from the blocks directory. // Remove the rev files immediately and insert the blk file paths into an // ordered map keyed by block file index. LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for " "-reindex with -prune\n"); const auto directoryIterator = fs::directory_iterator{GetBlocksDir()}; for (const auto &file : directoryIterator) { const auto fileName = file.path().filename().string(); if (fs::is_regular_file(file) && fileName.length() == 12 && fileName.substr(8, 4) == ".dat") { if (fileName.substr(0, 3) == "blk") { mapBlockFiles[fileName.substr(3, 5)] = file.path(); } else if (fileName.substr(0, 3) == "rev") { remove(file.path()); } } } // Remove all block files that aren't part of a contiguous set starting at // zero by walking the ordered map (keys are block file indices) by keeping // a separate counter. Once we hit a gap (or if 0 doesn't exist) start // removing block files. int contiguousCounter = 0; for (const auto &item : mapBlockFiles) { if (atoi(item.first) == contiguousCounter) { contiguousCounter++; continue; } remove(item.second); } } #if HAVE_SYSTEM static void StartupNotify(const ArgsManager &args) { std::string cmd = args.GetArg("-startupnotify", ""); if (!cmd.empty()) { std::thread t(runCommand, cmd); // thread runs free t.detach(); } } #endif static void ThreadImport(const Config &config, ChainstateManager &chainman, std::vector<fs::path> vImportFiles, const ArgsManager &args) { ScheduleBatchPriority(); { const CChainParams &chainParams = config.GetChainParams(); CImportingNow imp; // -reindex if (fReindex) { int nFile = 0; while (true) { FlatFilePos pos(nFile, 0); if (!fs::exists(GetBlockPosFilename(pos))) { // No block files left to reindex break; } FILE *file = OpenBlockFile(pos, true); if (!file) { // This error is logged in OpenBlockFile break; } LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); LoadExternalBlockFile(config, file, &pos); if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exit %s\n", __func__); return; } nFile++; } pblocktree->WriteReindexing(false); fReindex = false; LogPrintf("Reindexing finished\n"); // To avoid ending up in a situation without genesis block, re-try // initializing (no-op if reindexing worked): LoadGenesisBlock(chainParams); } // -loadblock= for (const fs::path &path : vImportFiles) { FILE *file = fsbridge::fopen(path, "rb"); if (file) { LogPrintf("Importing blocks file %s...\n", path.string()); LoadExternalBlockFile(config, file); if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exit %s\n", __func__); return; } } else { LogPrintf("Warning: Could not open blocks file %s\n", path.string()); } } // Reconsider blocks we know are valid. They may have been marked // invalid by, for instance, running an outdated version of the node // software. const MapCheckpoints &checkpoints = chainParams.Checkpoints().mapCheckpoints; for (const MapCheckpoints::value_type &i : checkpoints) { const BlockHash &hash = i.second; LOCK(cs_main); CBlockIndex *pblockindex = LookupBlockIndex(hash); if (pblockindex && !pblockindex->nStatus.isValid()) { LogPrintf("Reconsidering checkpointed block %s ...\n", hash.GetHex()); ResetBlockFailureFlags(pblockindex); } } // scan for better chains in the block chain database, that are not yet // connected in the active best chain // We can't hold cs_main during ActivateBestChain even though we're // accessing the chainman unique_ptrs since ABC requires us not to be // holding cs_main, so retrieve the relevant pointers before the ABC // call. for (CChainState *chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { BlockValidationState state; if (!chainstate->ActivateBestChain(config, state, nullptr)) { LogPrintf("Failed to connect best block (%s)\n", state.ToString()); StartShutdown(); return; } } if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) { LogPrintf("Stopping after block import\n"); StartShutdown(); return; } } // End scope of CImportingNow chainman.ActiveChainstate().LoadMempool(config, args); } /** Sanity checks * Ensure that Bitcoin is running in a usable environment with all * necessary library support. */ static bool InitSanityCheck() { if (!ECC_InitSanityCheck()) { return InitError(Untranslated( "Elliptic curve cryptography sanity check failure. Aborting.")); } if (!glibcxx_sanity_test()) { return false; } if (!Random_SanityCheck()) { return InitError(Untranslated( "OS cryptographic RNG sanity check failure. Aborting.")); } return true; } static bool AppInitServers(Config &config, HTTPRPCRequestProcessor &httpRPCRequestProcessor, NodeContext &node) { const ArgsManager &args = *Assert(node.args); RPCServerSignals::OnStarted(&OnRPCStarted); RPCServerSignals::OnStopped(&OnRPCStopped); if (!InitHTTPServer(config)) { return false; } StartRPC(); node.rpc_interruption_point = RpcInterruptionPoint; if (!StartHTTPRPC(httpRPCRequestProcessor)) { return false; } if (args.GetBoolArg("-rest", DEFAULT_REST_ENABLE)) { StartREST(httpRPCRequestProcessor.context); } StartHTTPServer(); return true; } // Parameter interaction based on rules void InitParameterInteraction(ArgsManager &args) { // when specifying an explicit binding address, you want to listen on it // even when -connect or -proxy is specified. if (args.IsArgSet("-bind")) { if (args.SoftSetBoolArg("-listen", true)) { LogPrintf( "%s: parameter interaction: -bind set -> setting -listen=1\n", __func__); } } if (args.IsArgSet("-whitebind")) { if (args.SoftSetBoolArg("-listen", true)) { LogPrintf("%s: parameter interaction: -whitebind set -> setting " "-listen=1\n", __func__); } } if (args.IsArgSet("-connect")) { // when only connecting to trusted nodes, do not seed via DNS, or listen // by default. if (args.SoftSetBoolArg("-dnsseed", false)) { LogPrintf("%s: parameter interaction: -connect set -> setting " "-dnsseed=0\n", __func__); } if (args.SoftSetBoolArg("-listen", false)) { LogPrintf("%s: parameter interaction: -connect set -> setting " "-listen=0\n", __func__); } } if (args.IsArgSet("-proxy")) { // to protect privacy, do not listen by default if a default proxy // server is specified. if (args.SoftSetBoolArg("-listen", false)) { LogPrintf( "%s: parameter interaction: -proxy set -> setting -listen=0\n", __func__); } // to protect privacy, do not use UPNP when a proxy is set. The user may // still specify -listen=1 to listen locally, so don't rely on this // happening through -listen below. if (args.SoftSetBoolArg("-upnp", false)) { LogPrintf( "%s: parameter interaction: -proxy set -> setting -upnp=0\n", __func__); } // to protect privacy, do not discover addresses by default if (args.SoftSetBoolArg("-discover", false)) { LogPrintf("%s: parameter interaction: -proxy set -> setting " "-discover=0\n", __func__); } } if (!args.GetBoolArg("-listen", DEFAULT_LISTEN)) { // do not map ports or try to retrieve public IP when not listening // (pointless) if (args.SoftSetBoolArg("-upnp", false)) { LogPrintf( "%s: parameter interaction: -listen=0 -> setting -upnp=0\n", __func__); } if (args.SoftSetBoolArg("-discover", false)) { LogPrintf( "%s: parameter interaction: -listen=0 -> setting -discover=0\n", __func__); } if (args.SoftSetBoolArg("-listenonion", false)) { LogPrintf("%s: parameter interaction: -listen=0 -> setting " "-listenonion=0\n", __func__); } } if (args.IsArgSet("-externalip")) { // if an explicit public IP is specified, do not try to find others if (args.SoftSetBoolArg("-discover", false)) { LogPrintf("%s: parameter interaction: -externalip set -> setting " "-discover=0\n", __func__); } } // disable whitelistrelay in blocksonly mode if (args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)) { if (args.SoftSetBoolArg("-whitelistrelay", false)) { LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting " "-whitelistrelay=0\n", __func__); } } // Forcing relay from whitelisted hosts implies we will accept relays from // them in the first place. if (args.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) { if (args.SoftSetBoolArg("-whitelistrelay", true)) { LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> " "setting -whitelistrelay=1\n", __func__); } } } /** * Initialize global loggers. * * Note that this is called very early in the process lifetime, so you should be * careful about what global state you rely on here. */ void InitLogging(const ArgsManager &args) { LogInstance().m_print_to_file = !args.IsArgNegated("-debuglogfile"); LogInstance().m_file_path = AbsPathForConfigVal(args.GetArg("-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); #ifdef HAVE_THREAD_LOCAL LogInstance().m_log_threadnames = args.GetBoolArg("-logthreadnames", DEFAULT_LOGTHREADNAMES); #endif fLogIPs = args.GetBoolArg("-logips", DEFAULT_LOGIPS); std::string version_string = FormatFullVersion(); #ifdef DEBUG version_string += " (debug build)"; #else version_string += " (release build)"; #endif LogPrintf("%s version %s\n", CLIENT_NAME, version_string); } namespace { // Variables internal to initialization process only int nMaxConnections; int nUserMaxConnections; int nFD; ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED); int64_t peer_connect_timeout; std::set<BlockFilterType> g_enabled_filter_types; } // namespace [[noreturn]] static void new_handler_terminate() { // Rather than throwing std::bad-alloc if allocation fails, terminate // immediately to (try to) avoid chain corruption. Since LogPrintf may // itself allocate memory, set the handler directly to terminate first. std::set_new_handler(std::terminate); LogPrintf("Error: Out of memory. Terminating.\n"); // The log was successful, terminate now. std::terminate(); }; bool AppInitBasicSetup(ArgsManager &args) { // Step 1: setup #ifdef _MSC_VER // Turn off Microsoft heap dump noise _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, 0)); // Disable confusing "helpful" text message on abort, Ctrl-C _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); #endif #ifdef WIN32 // Enable Data Execution Prevention (DEP) SetProcessDEPPolicy(PROCESS_DEP_ENABLE); #endif if (!SetupNetworking()) { return InitError(Untranslated("Initializing networking failed")); } #ifndef WIN32 if (!args.GetBoolArg("-sysperms", false)) { umask(077); } // Clean shutdown on SIGTERM registerSignalHandler(SIGTERM, HandleSIGTERM); registerSignalHandler(SIGINT, HandleSIGTERM); // Reopen debug.log on SIGHUP registerSignalHandler(SIGHUP, HandleSIGHUP); // Ignore SIGPIPE, otherwise it will bring the daemon down if the client // closes unexpectedly signal(SIGPIPE, SIG_IGN); #else SetConsoleCtrlHandler(consoleCtrlHandler, true); #endif std::set_new_handler(new_handler_terminate); return true; } bool AppInitParameterInteraction(Config &config, const ArgsManager &args) { const CChainParams &chainparams = config.GetChainParams(); // Step 2: parameter interactions // also see: InitParameterInteraction() // Error if network-specific options (-addnode, -connect, etc) are // specified in default section of config file, but not overridden // on the command line or in this network's section of the config file. std::string network = args.GetChainName(); bilingual_str errors; for (const auto &arg : args.GetUnsuitableSectionOnlyArgs()) { errors += strprintf(_("Config setting for %s only applied on %s " "network when in [%s] section.") + Untranslated("\n"), arg, network, network); } if (!errors.empty()) { return InitError(errors); } // Warn if unrecognized section name are present in the config file. bilingual_str warnings; for (const auto §ion : args.GetUnrecognizedSections()) { warnings += strprintf(Untranslated("%s:%i ") + _("Section [%s] is not recognized.") + Untranslated("\n"), section.m_file, section.m_line, section.m_name); } if (!warnings.empty()) { InitWarning(warnings); } if (!fs::is_directory(GetBlocksDir())) { return InitError( strprintf(_("Specified blocks directory \"%s\" does not exist."), args.GetArg("-blocksdir", ""))); } // parse and validate enabled filter types std::string blockfilterindex_value = args.GetArg("-blockfilterindex", DEFAULT_BLOCKFILTERINDEX); if (blockfilterindex_value == "" || blockfilterindex_value == "1") { g_enabled_filter_types = AllBlockFilterTypes(); } else if (blockfilterindex_value != "0") { const std::vector<std::string> names = args.GetArgs("-blockfilterindex"); for (const auto &name : names) { BlockFilterType filter_type; if (!BlockFilterTypeByName(name, filter_type)) { return InitError( strprintf(_("Unknown -blockfilterindex value %s."), name)); } g_enabled_filter_types.insert(filter_type); } } // Signal NODE_COMPACT_FILTERS if peerblockfilters and basic filters index // are both enabled. if (gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)) { if (g_enabled_filter_types.count(BlockFilterType::BASIC) != 1) { return InitError( _("Cannot set -peerblockfilters without -blockfilterindex.")); } nLocalServices = ServiceFlags(nLocalServices | NODE_COMPACT_FILTERS); } // if using block pruning, then disallow txindex if (args.GetArg("-prune", 0)) { if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { return InitError(_("Prune mode is incompatible with -txindex.")); } if (!g_enabled_filter_types.empty()) { return InitError( _("Prune mode is incompatible with -blockfilterindex.")); } } // -bind and -whitebind can't be set when not listening size_t nUserBind = args.GetArgs("-bind").size() + args.GetArgs("-whitebind").size(); if (nUserBind != 0 && !args.GetBoolArg("-listen", DEFAULT_LISTEN)) { return InitError(Untranslated( "Cannot set -bind or -whitebind together with -listen=0")); } // Make sure enough file descriptors are available int nBind = std::max(nUserBind, size_t(1)); nUserMaxConnections = args.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS); nMaxConnections = std::max(nUserMaxConnections, 0); // Trim requested connection counts, to fit into system limitations // <int> in std::min<int>(...) to work around FreeBSD compilation issue // described in #2695 nFD = RaiseFileDescriptorLimit(nMaxConnections + nBind + MIN_CORE_FILEDESCRIPTORS + MAX_ADDNODE_CONNECTIONS); #ifdef USE_POLL int fd_max = nFD; #else int fd_max = FD_SETSIZE; #endif nMaxConnections = std::max(std::min<int>(nMaxConnections, fd_max - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS), 0); if (nFD < MIN_CORE_FILEDESCRIPTORS) { return InitError(_("Not enough file descriptors available.")); } nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS, nMaxConnections); if (nMaxConnections < nUserMaxConnections) { // Not categorizing as "Warning" because this is the normal behavior for // platforms using the select() interface for which FD_SETSIZE is // usually 1024. LogPrintf("Reducing -maxconnections from %d to %d, because of system " "limitations.\n", nUserMaxConnections, nMaxConnections); } // Step 3: parameter-to-internal-flags if (args.IsArgSet("-debug")) { // Special-case: if -debug=0/-nodebug is set, turn off debugging // messages const std::vector<std::string> &categories = args.GetArgs("-debug"); if (std::none_of( categories.begin(), categories.end(), [](std::string cat) { return cat == "0" || cat == "none"; })) { for (const auto &cat : categories) { if (!LogInstance().EnableCategory(cat)) { InitWarning( strprintf(_("Unsupported logging category %s=%s."), "-debug", cat)); } } } } // Now remove the logging categories which were explicitly excluded for (const std::string &cat : args.GetArgs("-debugexclude")) { if (!LogInstance().DisableCategory(cat)) { InitWarning(strprintf(_("Unsupported logging category %s=%s."), "-debugexclude", cat)); } } fCheckBlockIndex = args.GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks()); fCheckpointsEnabled = args.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED); if (fCheckpointsEnabled) { LogPrintf("Checkpoints will be verified.\n"); } else { LogPrintf("Skipping checkpoint verification.\n"); } hashAssumeValid = BlockHash::fromHex( args.GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex())); if (!hashAssumeValid.IsNull()) { LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex()); } else { LogPrintf("Validating signatures for all blocks.\n"); } if (args.IsArgSet("-minimumchainwork")) { const std::string minChainWorkStr = args.GetArg("-minimumchainwork", ""); if (!IsHexNumber(minChainWorkStr)) { return InitError(strprintf( Untranslated( "Invalid non-hex (%s) minimum chain work value specified"), minChainWorkStr)); } nMinimumChainWork = UintToArith256(uint256S(minChainWorkStr)); } else { nMinimumChainWork = UintToArith256(chainparams.GetConsensus().nMinimumChainWork); } LogPrintf("Setting nMinimumChainWork=%s\n", nMinimumChainWork.GetHex()); if (nMinimumChainWork < UintToArith256(chainparams.GetConsensus().nMinimumChainWork)) { LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainparams.GetConsensus().nMinimumChainWork.GetHex()); } // mempool limits int64_t nMempoolSizeMax = args.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; int64_t nMempoolSizeMin = args.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000 * 40; if (nMempoolSizeMax < 0 || nMempoolSizeMax < nMempoolSizeMin) { return InitError(strprintf(_("-maxmempool must be at least %d MB"), std::ceil(nMempoolSizeMin / 1000000.0))); } // Configure excessive block size. const int64_t nProposedExcessiveBlockSize = args.GetArg("-excessiveblocksize", DEFAULT_MAX_BLOCK_SIZE); if (nProposedExcessiveBlockSize <= 0 || !config.SetMaxBlockSize(nProposedExcessiveBlockSize)) { return InitError( _("Excessive block size must be > 1,000,000 bytes (1MB)")); } // Check blockmaxsize does not exceed maximum accepted block size. const int64_t nProposedMaxGeneratedBlockSize = args.GetArg("-blockmaxsize", DEFAULT_MAX_GENERATED_BLOCK_SIZE); if (nProposedMaxGeneratedBlockSize <= 0) { return InitError(_("Max generated block size must be greater than 0")); } if (uint64_t(nProposedMaxGeneratedBlockSize) > config.GetMaxBlockSize()) { return InitError(_("Max generated block size (blockmaxsize) cannot " "exceed the excessive block size " "(excessiveblocksize)")); } // block pruning; get the amount of disk space (in MiB) to allot for block & // undo files int64_t nPruneArg = args.GetArg("-prune", 0); if (nPruneArg < 0) { return InitError( _("Prune cannot be configured with a negative value.")); } nPruneTarget = (uint64_t)nPruneArg * 1024 * 1024; if (nPruneArg == 1) { // manual pruning: -prune=1 LogPrintf("Block pruning enabled. Use RPC call " "pruneblockchain(height) to manually prune block and undo " "files.\n"); nPruneTarget = std::numeric_limits<uint64_t>::max(); fPruneMode = true; } else if (nPruneTarget) { if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) { return InitError( strprintf(_("Prune configured below the minimum of %d MiB. " "Please use a higher number."), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)); } LogPrintf("Prune configured to target %u MiB on disk for block and " "undo files.\n", nPruneTarget / 1024 / 1024); fPruneMode = true; } nConnectTimeout = args.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT); if (nConnectTimeout <= 0) { nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; } peer_connect_timeout = args.GetArg("-peertimeout", DEFAULT_PEER_CONNECT_TIMEOUT); if (peer_connect_timeout <= 0) { return InitError(Untranslated( "peertimeout cannot be configured with a negative value.")); } // Obtain the amount to charge excess UTXO if (args.IsArgSet("-excessutxocharge")) { Amount n = Amount::zero(); auto parsed = ParseMoney(args.GetArg("-excessutxocharge", ""), n); if (!parsed || Amount::zero() > n) { return InitError(AmountErrMsg( "excessutxocharge", args.GetArg("-excessutxocharge", ""))); } config.SetExcessUTXOCharge(n); } else { config.SetExcessUTXOCharge(DEFAULT_UTXO_FEE); } if (args.IsArgSet("-minrelaytxfee")) { Amount n = Amount::zero(); auto parsed = ParseMoney(args.GetArg("-minrelaytxfee", ""), n); if (!parsed || n == Amount::zero()) { return InitError(AmountErrMsg("minrelaytxfee", args.GetArg("-minrelaytxfee", ""))); } // High fee check is done afterward in CWallet::CreateWalletFromFile() ::minRelayTxFee = CFeeRate(n); } // Sanity check argument for min fee for including tx in block // TODO: Harmonize which arguments need sanity checking and where that // happens. if (args.IsArgSet("-blockmintxfee")) { Amount n = Amount::zero(); if (!ParseMoney(args.GetArg("-blockmintxfee", ""), n)) { return InitError(AmountErrMsg("blockmintxfee", args.GetArg("-blockmintxfee", ""))); } } // Feerate used to define dust. Shouldn't be changed lightly as old // implementations may inadvertently create non-standard transactions. if (args.IsArgSet("-dustrelayfee")) { Amount n = Amount::zero(); auto parsed = ParseMoney(args.GetArg("-dustrelayfee", ""), n); if (!parsed || Amount::zero() == n) { return InitError( AmountErrMsg("dustrelayfee", args.GetArg("-dustrelayfee", ""))); } dustRelayFee = CFeeRate(n); } fRequireStandard = !args.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard()); if (!chainparams.IsTestChain() && !fRequireStandard) { return InitError(strprintf( Untranslated( "acceptnonstdtxn is not currently supported for %s chain"), chainparams.NetworkIDString())); } nBytesPerSigOp = args.GetArg("-bytespersigop", nBytesPerSigOp); if (!g_wallet_init_interface.ParameterInteraction()) { return false; } fIsBareMultisigStd = args.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG); fAcceptDatacarrier = args.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER); // Option to startup with mocktime set (used for regression testing): SetMockTime(args.GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op if (args.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS)) { nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM); } nMaxTipAge = args.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE); if (args.IsArgSet("-proxy") && args.GetArg("-proxy", "").empty()) { return InitError(_( "No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.")); } return true; } static bool LockDataDirectory(bool probeOnly) { // Make sure only a single Bitcoin process is using the data directory. fs::path datadir = GetDataDir(); if (!DirIsWritable(datadir)) { return InitError(strprintf( _("Cannot write to data directory '%s'; check permissions."), datadir.string())); } if (!LockDirectory(datadir, ".lock", probeOnly)) { return InitError(strprintf(_("Cannot obtain a lock on data directory " "%s. %s is probably already running."), datadir.string(), PACKAGE_NAME)); } return true; } bool AppInitSanityChecks() { // Step 4: sanity checks // Initialize elliptic curve code std::string sha256_algo = SHA256AutoDetect(); LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); RandomInit(); ECC_Start(); globalVerifyHandle.reset(new ECCVerifyHandle()); // Sanity check if (!InitSanityCheck()) { return InitError(strprintf( _("Initialization sanity check failed. %s is shutting down."), PACKAGE_NAME)); } // Probe the data directory lock to give an early error message, if possible // We cannot hold the data directory lock here, as the forking for daemon() // hasn't yet happened, and a fork will cause weird behavior to it. return LockDataDirectory(true); } bool AppInitLockDataDirectory() { // After daemonization get the data directory lock again and hold on to it // until exit. This creates a slight window for a race condition to happen, // however this condition is harmless: it will at most make us exit without // printing a message to console. if (!LockDataDirectory(false)) { // Detailed error printed inside LockDataDirectory return false; } return true; } bool AppInitInterfaces(NodeContext &node) { node.chain = interfaces::MakeChain(node, Params()); // Create client interfaces for wallets that are supposed to be loaded // according to -wallet and -disablewallet options. This only constructs // the interfaces, it doesn't load wallet data. Wallets actually get loaded // when load() and start() interface methods are called below. g_wallet_init_interface.Construct(node); return true; } bool AppInitMain(Config &config, RPCServer &rpcServer, HTTPRPCRequestProcessor &httpRPCRequestProcessor, NodeContext &node, interfaces::BlockAndHeaderTipInfo *tip_info) { // Step 4a: application initialization const ArgsManager &args = *Assert(node.args); const CChainParams &chainparams = config.GetChainParams(); if (!CreatePidFile(args)) { // Detailed error printed inside CreatePidFile(). return false; } BCLog::Logger &logger = LogInstance(); if (logger.m_print_to_file) { if (args.GetBoolArg("-shrinkdebugfile", logger.DefaultShrinkDebugFile())) { // Do this first since it both loads a bunch of debug.log into // memory, and because this needs to happen before any other // debug.log printing. logger.ShrinkDebugFile(); } } if (!logger.StartLogging()) { return InitError( strprintf(Untranslated("Could not open debug log file %s"), logger.m_file_path.string())); } if (!logger.m_log_timestamps) { LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime())); } LogPrintf("Default data directory %s\n", GetDefaultDataDir().string()); LogPrintf("Using data directory %s\n", GetDataDir().string()); // Only log conf file usage message if conf file actually exists. fs::path config_file_path = GetConfigFile(args.GetArg("-conf", BITCOIN_CONF_FILENAME)); if (fs::exists(config_file_path)) { LogPrintf("Config file: %s\n", config_file_path.string()); } else if (args.IsArgSet("-conf")) { // Warn if no conf file exists at path provided by user InitWarning( strprintf(_("The specified config file %s does not exist\n"), config_file_path.string())); } else { // Not categorizing as "Warning" because it's the default behavior LogPrintf("Config file: %s (not found, skipping)\n", config_file_path.string()); } // Log the config arguments to debug.log args.LogArgs(); LogPrintf("Using at most %i automatic connections (%i file descriptors " "available)\n", nMaxConnections, nFD); // Warn about relative -datadir path. if (args.IsArgSet("-datadir") && !fs::path(args.GetArg("-datadir", "")).is_absolute()) { LogPrintf("Warning: relative datadir option '%s' specified, which will " "be interpreted relative to the current working directory " "'%s'. This is fragile, because if bitcoin is started in the " "future from a different location, it will be unable to " "locate the current data files. There could also be data " "loss if bitcoin is started while in a temporary " "directory.\n", args.GetArg("-datadir", ""), fs::current_path().string()); } InitSignatureCache(); InitScriptExecutionCache(); int script_threads = args.GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS); if (script_threads <= 0) { // -par=0 means autodetect (number of cores - 1 script threads) // -par=-n means "leave n cores free" (number of cores - n - 1 script // threads) script_threads += GetNumCores(); } // Subtract 1 because the main thread counts towards the par threads script_threads = std::max(script_threads - 1, 0); // Number of script-checking threads <= MAX_SCRIPTCHECK_THREADS script_threads = std::min(script_threads, MAX_SCRIPTCHECK_THREADS); LogPrintf("Script verification uses %d additional threads\n", script_threads); if (script_threads >= 1) { for (int i = 0; i < script_threads; ++i) { threadGroup.create_thread([i]() { return ThreadScriptCheck(i); }); } } assert(!node.scheduler); node.scheduler = std::make_unique<CScheduler>(); // Start the lightweight task scheduler thread CScheduler::Function serviceLoop = [&node] { node.scheduler->serviceQueue(); }; threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop)); // Gather some entropy once per minute. node.scheduler->scheduleEvery( [] { RandAddPeriodic(); return true; }, std::chrono::minutes{1}); GetMainSignals().RegisterBackgroundSignalScheduler(*node.scheduler); /** * Register RPC commands regardless of -server setting so they will be * available in the GUI RPC console even if external calls are disabled. */ RegisterAllRPCCommands(config, rpcServer, tableRPC); for (const auto &client : node.chain_clients) { client->registerRpcs(); } #if ENABLE_ZMQ RegisterZMQRPCCommands(tableRPC); #endif /** * Start the RPC server. It will be started in "warmup" mode and not * process calls yet (but it will verify that the server is there and will * be ready later). Warmup mode will be completed when initialisation is * finished. */ if (args.GetBoolArg("-server", false)) { uiInterface.InitMessage_connect(SetRPCWarmupStatus); if (!AppInitServers(config, httpRPCRequestProcessor, node)) { return InitError( _("Unable to start HTTP server. See debug log for details.")); } } // Step 5: verify wallet database integrity for (const auto &client : node.chain_clients) { if (!client->verify()) { return false; } } // Step 6: network initialization // Note that we absolutely cannot open any actual connections // until the very end ("start node") as the UTXO/block state // is not yet setup and may end up being set up twice if we // need to reindex later. assert(!node.banman); node.banman = std::make_unique<BanMan>( GetDataDir() / "banlist.dat", config.GetChainParams(), &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!node.connman); node.connman = std::make_unique<CConnman>( config, GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()), gArgs.GetBoolArg("-networkactive", true)); // Make mempool generally available in the node context. For example the // connection manager, wallet, or RPC threads, which are all started after // this, may use it from the node context. assert(!node.mempool); node.mempool = std::make_unique<CTxMemPool>(); if (node.mempool) { int ratio = std::min<int>( std::max<int>( args.GetArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0), 1000000); if (ratio != 0) { node.mempool->setSanityCheck(1.0 / ratio); } } assert(!node.chainman); node.chainman = &g_chainman; ChainstateManager &chainman = *Assert(node.chainman); node.peerman.reset(new PeerManager(chainparams, *node.connman, node.banman.get(), *node.scheduler, chainman, *node.mempool)); RegisterValidationInterface(node.peerman.get()); // sanitize comments per BIP-0014, format user agent and check total size std::vector<std::string> uacomments; for (const std::string &cmt : args.GetArgs("-uacomment")) { if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT)) { return InitError(strprintf( _("User Agent comment (%s) contains unsafe characters."), cmt)); } uacomments.push_back(cmt); } const std::string client_name = args.GetArg("-uaclientname", CLIENT_NAME); const std::string client_version = args.GetArg("-uaclientversion", FormatVersion(CLIENT_VERSION)); if (client_name != SanitizeString(client_name, SAFE_CHARS_UA_COMMENT)) { return InitError(strprintf( _("-uaclientname (%s) contains invalid characters."), client_name)); } if (client_version != SanitizeString(client_version, SAFE_CHARS_UA_COMMENT)) { return InitError( strprintf(_("-uaclientversion (%s) contains invalid characters."), client_version)); } const std::string strSubVersion = FormatUserAgent(client_name, client_version, uacomments); if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) { return InitError(strprintf( _("Total length of network version string (%i) exceeds maximum " "length (%i). Reduce the number or size of uacomments."), strSubVersion.size(), MAX_SUBVERSION_LENGTH)); } if (args.IsArgSet("-onlynet")) { std::set<enum Network> nets; for (const std::string &snet : args.GetArgs("-onlynet")) { enum Network net = ParseNetwork(snet); if (net == NET_UNROUTABLE) { return InitError(strprintf( _("Unknown network specified in -onlynet: '%s'"), snet)); } nets.insert(net); } for (int n = 0; n < NET_MAX; n++) { enum Network net = (enum Network)n; if (!nets.count(net)) { SetReachable(net, false); } } } // Check for host lookup allowed before parsing any network related // parameters fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP); bool proxyRandomize = args.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE); // -proxy sets a proxy for all outgoing network traffic // -noproxy (or -proxy=0) as well as the empty string can be used to not set // a proxy, this is the default std::string proxyArg = args.GetArg("-proxy", ""); SetReachable(NET_ONION, false); if (proxyArg != "" && proxyArg != "0") { CService proxyAddr; if (!Lookup(proxyArg, proxyAddr, 9050, fNameLookup)) { return InitError(strprintf( _("Invalid -proxy address or hostname: '%s'"), proxyArg)); } proxyType addrProxy = proxyType(proxyAddr, proxyRandomize); if (!addrProxy.IsValid()) { return InitError(strprintf( _("Invalid -proxy address or hostname: '%s'"), proxyArg)); } SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); SetProxy(NET_ONION, addrProxy); SetNameProxy(addrProxy); // by default, -proxy sets onion as reachable, unless -noonion later SetReachable(NET_ONION, true); } // -onion can be used to set only a proxy for .onion, or override normal // proxy for .onion addresses. // -noonion (or -onion=0) disables connecting to .onion entirely. An empty // string is used to not override the onion proxy (in which case it defaults // to -proxy set above, or none) std::string onionArg = args.GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 SetReachable(NET_ONION, false); } else { CService onionProxy; if (!Lookup(onionArg, onionProxy, 9050, fNameLookup)) { return InitError(strprintf( _("Invalid -onion address or hostname: '%s'"), onionArg)); } proxyType addrOnion = proxyType(onionProxy, proxyRandomize); if (!addrOnion.IsValid()) { return InitError(strprintf( _("Invalid -onion address or hostname: '%s'"), onionArg)); } SetProxy(NET_ONION, addrOnion); SetReachable(NET_ONION, true); } } // see Step 2: parameter interactions for more information about these fListen = args.GetBoolArg("-listen", DEFAULT_LISTEN); fDiscover = args.GetBoolArg("-discover", true); g_relay_txes = !args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY); for (const std::string &strAddr : args.GetArgs("-externalip")) { CService addrLocal; if (Lookup(strAddr, addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid()) { AddLocal(addrLocal, LOCAL_MANUAL); } else { return InitError(ResolveErrMsg("externalip", strAddr)); } } // Read asmap file if configured if (args.IsArgSet("-asmap")) { fs::path asmap_path = fs::path(args.GetArg("-asmap", "")); if (asmap_path.empty()) { asmap_path = DEFAULT_ASMAP_FILENAME; } if (!asmap_path.is_absolute()) { asmap_path = GetDataDir() / asmap_path; } if (!fs::exists(asmap_path)) { InitError(strprintf(_("Could not find asmap file %s"), asmap_path)); return false; } std::vector<bool> asmap = CAddrMan::DecodeAsmap(asmap_path); if (asmap.size() == 0) { InitError( strprintf(_("Could not parse asmap file %s"), asmap_path)); return false; } const uint256 asmap_version = SerializeHash(asmap); node.connman->SetAsmap(std::move(asmap)); LogPrintf("Using asmap version %s for IP bucketing\n", asmap_version.ToString()); } else { LogPrintf("Using /16 prefix for IP bucketing\n"); } #if ENABLE_ZMQ g_zmq_notification_interface = CZMQNotificationInterface::Create(); if (g_zmq_notification_interface) { RegisterValidationInterface(g_zmq_notification_interface); } #endif // unlimited unless -maxuploadtarget is set uint64_t nMaxOutboundLimit = 0; uint64_t nMaxOutboundTimeframe = MAX_UPLOAD_TIMEFRAME; if (args.IsArgSet("-maxuploadtarget")) { nMaxOutboundLimit = args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET) * 1024 * 1024; } // Step 6.5 (I guess ?): Initialize Avalanche. bilingual_str avalancheError; g_avalanche = avalanche::Processor::MakeProcessor( args, *node.chain, node.connman.get(), avalancheError); if (!g_avalanche) { InitError(avalancheError); return false; } if (isAvalancheEnabled(args) && g_avalanche->isAvalancheServiceAvailable()) { nLocalServices = ServiceFlags(nLocalServices | NODE_AVALANCHE); } // Step 7: load block chain fReindex = args.GetBoolArg("-reindex", false); bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false); // cache size calculations int64_t nTotalCache = (args.GetArg("-dbcache", DEFAULT_DB_CACHE_MB) << 20); // total cache cannot be less than MIN_DB_CACHE_MB nTotalCache = std::max(nTotalCache, MIN_DB_CACHE_MB << 20); // total cache cannot be greater than MAX_DB_CACHE_MB nTotalCache = std::min(nTotalCache, MAX_DB_CACHE_MB << 20); int64_t nBlockTreeDBCache = std::min(nTotalCache / 8, MAX_BLOCK_DB_CACHE_MB << 20); nTotalCache -= nBlockTreeDBCache; int64_t nTxIndexCache = std::min(nTotalCache / 8, args.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? MAX_TX_INDEX_CACHE_MB << 20 : 0); nTotalCache -= nTxIndexCache; int64_t filter_index_cache = 0; if (!g_enabled_filter_types.empty()) { size_t n_indexes = g_enabled_filter_types.size(); int64_t max_cache = std::min(nTotalCache / 8, MAX_FILTER_INDEX_CACHE_MB << 20); filter_index_cache = max_cache / n_indexes; nTotalCache -= filter_index_cache * n_indexes; } // use 25%-50% of the remainder for disk cache int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // cap total coins db cache nCoinDBCache = std::min(nCoinDBCache, MAX_COINS_DB_CACHE_MB << 20); nTotalCache -= nCoinDBCache; // the rest goes to in-memory cache int64_t nCoinCacheUsage = nTotalCache; int64_t nMempoolSizeMax = args.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; LogPrintf("Cache configuration:\n"); LogPrintf("* Using %.1f MiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { LogPrintf("* Using %.1f MiB for transaction index database\n", nTxIndexCache * (1.0 / 1024 / 1024)); } for (BlockFilterType filter_type : g_enabled_filter_types) { LogPrintf("* Using %.1f MiB for %s block filter index database\n", filter_index_cache * (1.0 / 1024 / 1024), BlockFilterTypeName(filter_type)); } LogPrintf("* Using %.1f MiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of " "unused mempool space)\n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); bool fLoaded = false; while (!fLoaded && !ShutdownRequested()) { const bool fReset = fReindex; auto is_coinsview_empty = [&](CChainState *chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); }; bilingual_str strLoadError; uiInterface.InitMessage(_("Loading block index...").translated); do { bool failed_verification = false; const int64_t load_block_index_start_time = GetTimeMillis(); try { LOCK(cs_main); chainman.InitializeChainstate(*Assert(node.mempool)); chainman.m_total_coinstip_cache = nCoinCacheUsage; chainman.m_total_coinsdb_cache = nCoinDBCache; UnloadBlockIndex(node.mempool.get(), chainman); // new CBlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it // first: pblocktree.reset(); pblocktree.reset( new CBlockTreeDB(nBlockTreeDBCache, false, fReset)); if (fReset) { pblocktree->WriteReindexing(true); // If we're reindexing in prune mode, wipe away unusable // block files and all undo data files if (fPruneMode) { CleanupBlockRevFiles(); } } const Consensus::Params ¶ms = chainparams.GetConsensus(); // If necessary, upgrade from older database format. // This is a no-op if we cleared the block tree db with -reindex // or -reindex-chainstate if (!pblocktree->Upgrade(params)) { strLoadError = _("Error upgrading block index database"); break; } if (ShutdownRequested()) { break; } // LoadBlockIndex will load fHavePruned if we've ever removed a // block file from disk. // Note that it also sets fReindex based on the disk flag! // From here on out fReindex and fReset mean something // different! if (!chainman.LoadBlockIndex(params)) { if (ShutdownRequested()) { break; } strLoadError = _("Error loading block database"); break; } // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way // around). if (!chainman.BlockIndex().empty() && !LookupBlockIndex(params.hashGenesisBlock)) { return InitError(_("Incorrect or no genesis block found. " "Wrong datadir for network?")); } // Check for changed -prune state. What we are concerned about // is a user who has pruned blocks in the past, but is now // trying to run unpruned. if (fHavePruned && !fPruneMode) { strLoadError = _("You need to rebuild the database using -reindex to " "go back to unpruned mode. This will redownload the " "entire blockchain"); break; } // At this point blocktree args are consistent with what's on // disk. If we're not mid-reindex (based on disk + args), add a // genesis block on disk (otherwise we use the one already on // disk). // This is called again in ThreadImport after the reindex // completes. if (!fReindex && !LoadGenesisBlock(chainparams)) { strLoadError = _("Error initializing block database"); break; } // At this point we're either in reindex or we've loaded a // useful block tree into BlockIndex()! bool failed_chainstate_init = false; for (CChainState *chainstate : chainman.GetAll()) { chainstate->InitCoinsDB( /* cache_size_bytes */ nCoinDBCache, /* in_memory */ false, /* should_wipe */ fReset || fReindexChainState); chainstate->CoinsErrorCatcher().AddReadErrCallback([]() { uiInterface.ThreadSafeMessageBox( _("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR); }); // If necessary, upgrade from older database format. // This is a no-op if we cleared the coinsviewdb with // -reindex or -reindex-chainstate if (!chainstate->CoinsDB().Upgrade()) { strLoadError = _("Error upgrading chainstate database"); failed_chainstate_init = true; break; } // ReplayBlocks is a no-op if we cleared the coinsviewdb // with -reindex or -reindex-chainstate if (!chainstate->ReplayBlocks(params)) { strLoadError = _( "Unable to replay blocks. You will need to rebuild " "the database using -reindex-chainstate."); failed_chainstate_init = true; break; } // The on-disk coinsdb is now in a good state, create the // cache chainstate->InitCoinsCache(nCoinCacheUsage); assert(chainstate->CanFlushToDisk()); if (!is_coinsview_empty(chainstate)) { // LoadChainTip initializes the chain based on // CoinsTip()'s best block if (!chainstate->LoadChainTip(chainparams)) { strLoadError = _("Error initializing block database"); failed_chainstate_init = true; // out of the per-chainstate loop break; } assert(chainstate->m_chain.Tip() != nullptr); } } if (failed_chainstate_init) { // out of the chainstate activation do-while break; } for (CChainState *chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { uiInterface.InitMessage( _("Verifying blocks...").translated); if (fHavePruned && args.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) { LogPrintf( "Prune: pruned datadir may not have more than " "%d blocks; only checking available blocks\n", MIN_BLOCKS_TO_KEEP); } const CBlockIndex *tip = chainstate->m_chain.Tip(); RPCNotifyBlockChange(tip); if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) { strLoadError = _("The block database contains a block which " "appears to be from the future. " "This may be due to your computer's date and " "time being set incorrectly. " "Only rebuild the block database if you are " "sure that your computer's date and time are " "correct"); failed_verification = true; break; } // Only verify the DB of the active chainstate. This is // fixed in later work when we allow VerifyDB to be // parameterized by chainstate. if (&::ChainstateActive() == chainstate && !CVerifyDB().VerifyDB( config, &chainstate->CoinsDB(), args.GetArg("-checklevel", DEFAULT_CHECKLEVEL), args.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { strLoadError = _("Corrupted block database detected"); failed_verification = true; break; } } } } catch (const std::exception &e) { LogPrintf("%s\n", e.what()); strLoadError = _("Error opening block database"); failed_verification = true; break; } if (!failed_verification) { fLoaded = true; LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); } } while (false); if (!fLoaded && !ShutdownRequested()) { // first suggest a reindex if (!fReset) { bool fRet = uiInterface.ThreadSafeQuestion( strLoadError + Untranslated(".\n\n") + _("Do you want to rebuild the block database now?"), strLoadError.original + ".\nPlease restart with -reindex or " "-reindex-chainstate to recover.", "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); if (fRet) { fReindex = true; AbortShutdown(); } else { LogPrintf("Aborted block database rebuild. Exiting.\n"); return false; } } else { return InitError(strLoadError); } } } // As LoadBlockIndex can take several minutes, it's possible the user // requested to kill the GUI during the last operation. If so, exit. // As the program has not fully started yet, Shutdown() is possibly // overkill. if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exiting.\n"); return false; } // Encoded addresses using cashaddr instead of base58. // We do this by default to avoid confusion with BTC addresses. config.SetCashAddrEncoding(args.GetBoolArg("-usecashaddr", true)); // Step 8: load indexers if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { g_txindex = std::make_unique<TxIndex>(nTxIndexCache, false, fReindex); g_txindex->Start(); } for (const auto &filter_type : g_enabled_filter_types) { InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex); GetBlockFilterIndex(filter_type)->Start(); } // Step 9: load wallet for (const auto &client : node.chain_clients) { if (!client->load()) { return false; } } // Step 10: data directory maintenance // if pruning, unset the service bit and perform the initial blockstore // prune after any wallet rescanning has taken place. if (fPruneMode) { LogPrintf("Unsetting NODE_NETWORK on prune mode\n"); nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK); if (!fReindex) { LOCK(cs_main); for (CChainState *chainstate : chainman.GetAll()) { uiInterface.InitMessage(_("Pruning blockstore...").translated); chainstate->PruneAndFlush(); } } } // Step 11: import blocks if (!CheckDiskSpace(GetDataDir())) { InitError( strprintf(_("Error: Disk space is low for %s"), GetDataDir())); return false; } if (!CheckDiskSpace(GetBlocksDir())) { InitError( strprintf(_("Error: Disk space is low for %s"), GetBlocksDir())); return false; } // Either install a handler to notify us when genesis activates, or set // fHaveGenesis directly. // No locking, as this happens before any background thread is started. boost::signals2::connection block_notify_genesis_wait_connection; if (::ChainActive().Tip() == nullptr) { block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect( std::bind(BlockNotifyGenesisWait, std::placeholders::_2)); } else { fHaveGenesis = true; } #if defined(HAVE_SYSTEM) const std::string block_notify = args.GetArg("-blocknotify", ""); if (!block_notify.empty()) { uiInterface.NotifyBlockTip_connect([block_notify]( SynchronizationState sync_state, const CBlockIndex *pBlockIndex) { if (sync_state != SynchronizationState::POST_INIT || !pBlockIndex) { return; } std::string command = block_notify; boost::replace_all(command, "%s", pBlockIndex->GetBlockHash().GetHex()); std::thread t(runCommand, command); // thread runs free t.detach(); }); } #endif std::vector<fs::path> vImportFiles; for (const std::string &strFile : args.GetArgs("-loadblock")) { vImportFiles.push_back(strFile); } g_load_block = std::thread(&TraceThread<std::function<void()>>, "loadblk", [=, &config, &chainman, &args] { ThreadImport(config, chainman, vImportFiles, args); }); // Wait for genesis block to be processed { WAIT_LOCK(g_genesis_wait_mutex, lock); // We previously could hang here if StartShutdown() is called prior to // ThreadImport getting started, so instead we just wait on a timer to // check ShutdownRequested() regularly. while (!fHaveGenesis && !ShutdownRequested()) { g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500)); } block_notify_genesis_wait_connection.disconnect(); } if (ShutdownRequested()) { return false; } // Step 12: start node int chain_active_height; //// debug print { LOCK(cs_main); LogPrintf("block tree size = %u\n", chainman.BlockIndex().size()); chain_active_height = chainman.ActiveChain().Height(); if (tip_info) { tip_info->block_height = chain_active_height; tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : Params().GenesisBlock().GetBlockTime(); tip_info->verification_progress = GuessVerificationProgress( Params().TxData(), chainman.ActiveChain().Tip()); } if (tip_info && ::pindexBestHeader) { tip_info->header_height = ::pindexBestHeader->nHeight; tip_info->header_time = ::pindexBestHeader->GetBlockTime(); } } LogPrintf("nBestHeight = %d\n", chain_active_height); - if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) { - StartTorControl(DefaultOnionServiceTarget()); - } - Discover(); // Map ports with UPnP if (args.GetBoolArg("-upnp", DEFAULT_UPNP)) { StartMapPort(); } CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; connOptions.nMaxConnections = nMaxConnections; connOptions.m_max_outbound_full_relay = std::min( MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections); connOptions.m_max_outbound_block_relay = std::min( MAX_BLOCK_RELAY_ONLY_CONNECTIONS, connOptions.nMaxConnections - connOptions.m_max_outbound_full_relay); connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxFeeler = MAX_FEELER_CONNECTIONS; connOptions.nBestHeight = chain_active_height; connOptions.uiInterface = &uiInterface; connOptions.m_banman = node.banman.get(); connOptions.m_msgproc = node.peerman.get(); connOptions.nSendBufferMaxSize = 1000 * args.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000 * args.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); connOptions.m_added_nodes = args.GetArgs("-addnode"); connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe; connOptions.nMaxOutboundLimit = nMaxOutboundLimit; connOptions.m_peer_connect_timeout = peer_connect_timeout; - for (const std::string &strBind : args.GetArgs("-bind")) { - CService addrBind; - if (!Lookup(strBind, addrBind, GetListenPort(), false)) { - return InitError(ResolveErrMsg("bind", strBind)); + for (const std::string &bind_arg : args.GetArgs("-bind")) { + CService bind_addr; + const size_t index = bind_arg.rfind('='); + if (index == std::string::npos) { + if (Lookup(bind_arg, bind_addr, GetListenPort(), false)) { + connOptions.vBinds.push_back(bind_addr); + continue; + } + } else { + const std::string network_type = bind_arg.substr(index + 1); + if (network_type == "onion") { + const std::string truncated_bind_arg = + bind_arg.substr(0, index); + if (Lookup(truncated_bind_arg, bind_addr, + BaseParams().OnionServiceTargetPort(), false)) { + connOptions.onion_binds.push_back(bind_addr); + continue; + } + } + } + return InitError(ResolveErrMsg("bind", bind_arg)); + } + + if (connOptions.onion_binds.empty()) { + connOptions.onion_binds.push_back(DefaultOnionServiceTarget()); + } + + if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) { + const auto bind_addr = connOptions.onion_binds.front(); + if (connOptions.onion_binds.size() > 1) { + InitWarning(strprintf( + _("More than one onion bind address is provided. Using %s for " + "the automatically created Tor onion service."), + bind_addr.ToStringIPPort())); } - connOptions.vBinds.push_back(addrBind); + StartTorControl(bind_addr); } for (const std::string &strBind : args.GetArgs("-whitebind")) { NetWhitebindPermissions whitebind; bilingual_str error; if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) { return InitError(error); } connOptions.vWhiteBinds.push_back(whitebind); } for (const auto &net : args.GetArgs("-whitelist")) { NetWhitelistPermissions subnet; bilingual_str error; if (!NetWhitelistPermissions::TryParse(net, subnet, error)) { return InitError(error); } connOptions.vWhitelistedRange.push_back(subnet); } connOptions.vSeedNodes = args.GetArgs("-seednode"); // Initiate outbound connections unless connect=0 connOptions.m_use_addrman_outgoing = !args.IsArgSet("-connect"); if (!connOptions.m_use_addrman_outgoing) { const auto connect = args.GetArgs("-connect"); if (connect.size() != 1 || connect[0] != "0") { connOptions.m_specified_outgoing = connect; } } if (!node.connman->Start(*node.scheduler, connOptions)) { return false; } // Step 13: finished SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading").translated); for (const auto &client : node.chain_clients) { client->start(*node.scheduler); } BanMan *banman = node.banman.get(); node.scheduler->scheduleEvery( [banman] { banman->DumpBanlist(); return true; }, DUMP_BANS_INTERVAL); // Start Avalanche's event loop. g_avalanche->startEventLoop(*node.scheduler); #if HAVE_SYSTEM StartupNotify(args); #endif return true; } diff --git a/src/net.cpp b/src/net.cpp index 7d825a500..c6a8c9be6 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1,3372 +1,3385 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif #include <net.h> #include <avalanche/avalanche.h> #include <banman.h> #include <clientversion.h> #include <config.h> #include <consensus/consensus.h> #include <crypto/sha256.h> #include <dnsseeds.h> #include <netbase.h> #include <node/ui_interface.h> #include <protocol.h> #include <random.h> #include <scheduler.h> #include <util/strencodings.h> #include <util/translation.h> #ifdef WIN32 #include <cstring> #else #include <fcntl.h> #endif #ifdef USE_POLL #include <poll.h> #endif #ifdef USE_UPNP #include <miniupnpc/miniupnpc.h> #include <miniupnpc/upnpcommands.h> #include <miniupnpc/upnperrors.h> // The minimum supported miniUPnPc API version is set to 10. This keeps // compatibility with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"); #endif #include <algorithm> #include <cmath> #include <cstdint> #include <limits> #include <optional> #include <unordered_map> // How often to dump addresses to peers.dat static constexpr std::chrono::minutes DUMP_PEERS_INTERVAL{15}; /** * Number of DNS seeds to query when the number of connections is low. */ static constexpr int DNSSEEDS_TO_QUERY_AT_ONCE = 3; /** * How long to delay before querying DNS seeds * * If we have more than THRESHOLD entries in addrman, then it's likely * that we got those addresses from having previously connected to the P2P * network, and that we'll be able to successfully reconnect to the P2P * network via contacting one of them. So if that's the case, spend a * little longer trying to connect to known peers before querying the * DNS seeds. */ static constexpr std::chrono::seconds DNSSEEDS_DELAY_FEW_PEERS{11}; static constexpr std::chrono::minutes DNSSEEDS_DELAY_MANY_PEERS{5}; // "many" vs "few" peers static constexpr int DNSSEEDS_DELAY_PEER_THRESHOLD = 1000; // We add a random period time (0 to 1 seconds) to feeler connections to prevent // synchronization. #define FEELER_SLEEP_WINDOW 1 // MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define // it as 0 #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL 0 #endif // MSG_DONTWAIT is not available on some platforms, if it doesn't exist define // it as 0 #if !defined(MSG_DONTWAIT) #define MSG_DONTWAIT 0 #endif /** Used to pass flags to the Bind() function */ enum BindFlags { BF_NONE = 0, BF_EXPLICIT = (1U << 0), BF_REPORT_ERROR = (1U << 1), + /** + * Do not call AddLocal() for our special addresses, e.g., for incoming + * Tor connections, to prevent gossiping them over the network. + */ + BF_DONT_ADVERTISE = (1U << 2), }; // The set of sockets cannot be modified while waiting // The sleep time needs to be small to avoid new sockets stalling static const uint64_t SELECT_TIMEOUT_MILLISECONDS = 50; const std::string NET_MESSAGE_COMMAND_OTHER = "*other*"; // SHA256("netgroup")[0:8] static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("localhostnonce")[0:8] static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[8:16] static const uint64_t RANDOMIZER_ID_EXTRAENTROPY = 0x94b05d41679a4ff7ULL; // SHA256("addrcache")[0:8] static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // // Global state variables // bool fDiscover = true; bool fListen = true; bool g_relay_txes = !DEFAULT_BLOCKSONLY; RecursiveMutex cs_mapLocalHost; std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(cs_mapLocalHost); static bool vfLimited[NET_MAX] GUARDED_BY(cs_mapLocalHost) = {}; void CConnman::AddAddrFetch(const std::string &strDest) { LOCK(m_addr_fetches_mutex); m_addr_fetches.push_back(strDest); } uint16_t GetListenPort() { return uint16_t(gArgs.GetArg("-port", Params().GetDefaultPort())); } // find 'best' local address for a particular peer bool GetLocal(CService &addr, const CNetAddr *paddrPeer) { if (!fListen) { return false; } int nBestScore = -1; int nBestReachability = -1; { LOCK(cs_mapLocalHost); for (const auto &entry : mapLocalHost) { int nScore = entry.second.nScore; int nReachability = entry.first.GetReachabilityFrom(paddrPeer); if (nReachability > nBestReachability || (nReachability == nBestReachability && nScore > nBestScore)) { addr = CService(entry.first, entry.second.nPort); nBestReachability = nReachability; nBestScore = nScore; } } } return nBestScore >= 0; } //! Convert the pnSeed6 array into usable address objects. static std::vector<CAddress> convertSeed6(const std::vector<SeedSpec6> &vSeedsIn) { // It'll only connect to one or two seed nodes because once it connects, // it'll get a pile of addresses with newer timestamps. Seed nodes are given // a random 'last seen time' of between one and two weeks ago. const int64_t nOneWeek = 7 * 24 * 60 * 60; std::vector<CAddress> vSeedsOut; vSeedsOut.reserve(vSeedsIn.size()); FastRandomContext rng; for (const auto &seed_in : vSeedsIn) { struct in6_addr ip; memcpy(&ip, seed_in.addr, sizeof(ip)); CAddress addr(CService(ip, seed_in.port), GetDesirableServiceFlags(NODE_NONE)); addr.nTime = GetTime() - rng.randrange(nOneWeek) - nOneWeek; vSeedsOut.push_back(addr); } return vSeedsOut; } // Get best local address for a particular peer as a CAddress. Otherwise, return // the unroutable 0.0.0.0 but filled in with the normal parameters, since the IP // may be changed to a useful one by discovery. CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices) { CAddress ret(CService(CNetAddr(), GetListenPort()), nLocalServices); CService addr; if (GetLocal(addr, paddrPeer)) { ret = CAddress(addr, nLocalServices); } ret.nTime = GetAdjustedTime(); return ret; } static int GetnScore(const CService &addr) { LOCK(cs_mapLocalHost); if (mapLocalHost.count(addr) == 0) { return 0; } return mapLocalHost[addr].nScore; } // Is our peer's addrLocal potentially useful as an external IP source? bool IsPeerAddrLocalGood(CNode *pnode) { CService addrLocal = pnode->GetAddrLocal(); return fDiscover && pnode->addr.IsRoutable() && addrLocal.IsRoutable() && IsReachable(addrLocal.GetNetwork()); } // Pushes our own address to a peer. void AdvertiseLocal(CNode *pnode) { if (fListen && pnode->fSuccessfullyConnected) { CAddress addrLocal = GetLocalAddress(&pnode->addr, pnode->GetLocalServices()); if (gArgs.GetBoolArg("-addrmantest", false)) { // use IPv4 loopback during addrmantest addrLocal = CAddress(CService(LookupNumeric("127.0.0.1", GetListenPort())), pnode->GetLocalServices()); } // If discovery is enabled, sometimes give our peer the address it // tells us that it sees us as in case it has a better idea of our // address than we do. FastRandomContext rng; if (IsPeerAddrLocalGood(pnode) && (!addrLocal.IsRoutable() || rng.randbits((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3 : 1) == 0)) { addrLocal.SetIP(pnode->GetAddrLocal()); } if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false)) { LogPrint(BCLog::NET, "AdvertiseLocal: advertising address %s\n", addrLocal.ToString()); pnode->PushAddress(addrLocal, rng); } } } // Learn a new local address. bool AddLocal(const CService &addr, int nScore) { if (!addr.IsRoutable()) { return false; } if (!fDiscover && nScore < LOCAL_MANUAL) { return false; } if (!IsReachable(addr)) { return false; } LogPrintf("AddLocal(%s,%i)\n", addr.ToString(), nScore); { LOCK(cs_mapLocalHost); bool fAlready = mapLocalHost.count(addr) > 0; LocalServiceInfo &info = mapLocalHost[addr]; if (!fAlready || nScore >= info.nScore) { info.nScore = nScore + (fAlready ? 1 : 0); info.nPort = addr.GetPort(); } } return true; } bool AddLocal(const CNetAddr &addr, int nScore) { return AddLocal(CService(addr, GetListenPort()), nScore); } void RemoveLocal(const CService &addr) { LOCK(cs_mapLocalHost); LogPrintf("RemoveLocal(%s)\n", addr.ToString()); mapLocalHost.erase(addr); } void SetReachable(enum Network net, bool reachable) { if (net == NET_UNROUTABLE || net == NET_INTERNAL) { return; } LOCK(cs_mapLocalHost); vfLimited[net] = !reachable; } bool IsReachable(enum Network net) { LOCK(cs_mapLocalHost); return !vfLimited[net]; } bool IsReachable(const CNetAddr &addr) { return IsReachable(addr.GetNetwork()); } /** vote for a local address */ bool SeenLocal(const CService &addr) { LOCK(cs_mapLocalHost); if (mapLocalHost.count(addr) == 0) { return false; } mapLocalHost[addr].nScore++; return true; } /** check whether a given address is potentially local */ bool IsLocal(const CService &addr) { LOCK(cs_mapLocalHost); return mapLocalHost.count(addr) > 0; } CNode *CConnman::FindNode(const CNetAddr &ip) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (static_cast<CNetAddr>(pnode->addr) == ip) { return pnode; } } return nullptr; } CNode *CConnman::FindNode(const CSubNet &subNet) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (subNet.Match(static_cast<CNetAddr>(pnode->addr))) { return pnode; } } return nullptr; } CNode *CConnman::FindNode(const std::string &addrName) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (pnode->GetAddrName() == addrName) { return pnode; } } return nullptr; } CNode *CConnman::FindNode(const CService &addr) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (static_cast<CService>(pnode->addr) == addr) { return pnode; } } return nullptr; } bool CConnman::CheckIncomingNonce(uint64_t nonce) { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (!pnode->fSuccessfullyConnected && !pnode->IsInboundConn() && pnode->GetLocalNonce() == nonce) { return false; } } return true; } /** Get the bind address for a socket as CAddress */ static CAddress GetBindAddress(SOCKET sock) { CAddress addr_bind; struct sockaddr_storage sockaddr_bind; socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); if (sock != INVALID_SOCKET) { if (!getsockname(sock, (struct sockaddr *)&sockaddr_bind, &sockaddr_bind_len)) { addr_bind.SetSockAddr((const struct sockaddr *)&sockaddr_bind); } else { LogPrint(BCLog::NET, "Warning: getsockname failed\n"); } } return addr_bind; } CNode *CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type) { assert(conn_type != ConnectionType::INBOUND); if (pszDest == nullptr) { if (IsLocal(addrConnect)) { return nullptr; } // Look for an existing connection CNode *pnode = FindNode(static_cast<CService>(addrConnect)); if (pnode) { LogPrintf("Failed to open new connection, already connected\n"); return nullptr; } } /// debug print LogPrint(BCLog::NET, "trying connection %s lastseen=%.1fhrs\n", pszDest ? pszDest : addrConnect.ToString(), pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime) / 3600.0); // Resolve const int default_port = Params().GetDefaultPort(); if (pszDest) { std::vector<CService> resolved; if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) { addrConnect = CAddress(resolved[GetRand(resolved.size())], NODE_NONE); if (!addrConnect.IsValid()) { LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest); return nullptr; } // It is possible that we already have a connection to the IP/port // pszDest resolved to. In that case, drop the connection that was // just created, and return the existing CNode instead. Also store // the name we used to connect in that CNode, so that future // FindNode() calls to that name catch this early. LOCK(cs_vNodes); CNode *pnode = FindNode(static_cast<CService>(addrConnect)); if (pnode) { pnode->MaybeSetAddrName(std::string(pszDest)); LogPrintf("Failed to open new connection, already connected\n"); return nullptr; } } } // Connect bool connected = false; SOCKET hSocket = INVALID_SOCKET; proxyType proxy; if (addrConnect.IsValid()) { bool proxyConnectionFailed = false; if (GetProxy(addrConnect.GetNetwork(), proxy)) { hSocket = CreateSocket(proxy.proxy); if (hSocket == INVALID_SOCKET) { return nullptr; } connected = ConnectThroughProxy( proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), hSocket, nConnectTimeout, proxyConnectionFailed); } else { // no proxy needed (none set for target network) hSocket = CreateSocket(addrConnect); if (hSocket == INVALID_SOCKET) { return nullptr; } connected = ConnectSocketDirectly(addrConnect, hSocket, nConnectTimeout, conn_type == ConnectionType::MANUAL); } if (!proxyConnectionFailed) { // If a connection to the node was attempted, and failure (if any) // is not caused by a problem connecting to the proxy, mark this as // an attempt. addrman.Attempt(addrConnect, fCountFailure); } } else if (pszDest && GetNameProxy(proxy)) { hSocket = CreateSocket(proxy.proxy); if (hSocket == INVALID_SOCKET) { return nullptr; } std::string host; int port = default_port; SplitHostPort(std::string(pszDest), port, host); bool proxyConnectionFailed; connected = ConnectThroughProxy(proxy, host, port, hSocket, nConnectTimeout, proxyConnectionFailed); } if (!connected) { CloseSocket(hSocket); return nullptr; } // Add node NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE) .Write(id) .Finalize(); uint64_t extra_entropy = GetDeterministicRandomizer(RANDOMIZER_ID_EXTRAENTROPY) .Write(id) .Finalize(); CAddress addr_bind = GetBindAddress(hSocket); CNode *pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, extra_entropy, addr_bind, pszDest ? pszDest : "", conn_type); pnode->AddRef(); // We're making a new connection, harvest entropy from the time (and our // peer count) RandAddEvent(uint32_t(id)); return pnode; } void CNode::CloseSocketDisconnect() { fDisconnect = true; LOCK(cs_hSocket); if (hSocket != INVALID_SOCKET) { LogPrint(BCLog::NET, "disconnecting peer=%d\n", id); CloseSocket(hSocket); } } void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags &flags, const CNetAddr &addr) const { for (const auto &subnet : vWhitelistedRange) { if (subnet.m_subnet.Match(addr)) { NetPermissions::AddFlag(flags, subnet.m_flags); } } } std::string CNode::ConnectionTypeAsString() const { switch (m_conn_type) { case ConnectionType::INBOUND: return "inbound"; case ConnectionType::MANUAL: return "manual"; case ConnectionType::FEELER: return "feeler"; case ConnectionType::OUTBOUND_FULL_RELAY: return "outbound-full-relay"; case ConnectionType::BLOCK_RELAY: return "block-relay-only"; case ConnectionType::ADDR_FETCH: return "addr-fetch"; } // no default case, so the compiler can warn about missing cases assert(false); } std::string CNode::GetAddrName() const { LOCK(cs_addrName); return addrName; } void CNode::MaybeSetAddrName(const std::string &addrNameIn) { LOCK(cs_addrName); if (addrName.empty()) { addrName = addrNameIn; } } CService CNode::GetAddrLocal() const { LOCK(cs_addrLocal); return addrLocal; } void CNode::SetAddrLocal(const CService &addrLocalIn) { LOCK(cs_addrLocal); if (addrLocal.IsValid()) { error("Addr local already set for node: %i. Refusing to change from %s " "to %s", id, addrLocal.ToString(), addrLocalIn.ToString()); } else { addrLocal = addrLocalIn; } } void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) { stats.nodeid = this->GetId(); stats.nServices = nServices; stats.addr = addr; stats.addrBind = addrBind; stats.m_mapped_as = addr.GetMappedAS(m_asmap); if (m_tx_relay != nullptr) { LOCK(m_tx_relay->cs_filter); stats.fRelayTxes = m_tx_relay->fRelayTxes; } else { stats.fRelayTxes = false; } stats.nLastSend = nLastSend; stats.nLastRecv = nLastRecv; stats.nLastTXTime = nLastTXTime; stats.nLastProofTime = nLastProofTime; stats.nLastBlockTime = nLastBlockTime; stats.nTimeConnected = nTimeConnected; stats.nTimeOffset = nTimeOffset; stats.addrName = GetAddrName(); stats.nVersion = nVersion; { LOCK(cs_SubVer); stats.cleanSubVer = cleanSubVer; } stats.fInbound = IsInboundConn(); stats.m_manual_connection = IsManualConn(); stats.nStartingHeight = nStartingHeight; { LOCK(cs_vSend); stats.mapSendBytesPerMsgCmd = mapSendBytesPerMsgCmd; stats.nSendBytes = nSendBytes; } { LOCK(cs_vRecv); stats.mapRecvBytesPerMsgCmd = mapRecvBytesPerMsgCmd; stats.nRecvBytes = nRecvBytes; } stats.m_legacyWhitelisted = m_legacyWhitelisted; stats.m_permissionFlags = m_permissionFlags; if (m_tx_relay != nullptr) { LOCK(m_tx_relay->cs_feeFilter); stats.minFeeFilter = m_tx_relay->minFeeFilter; } else { stats.minFeeFilter = Amount::zero(); } // It is common for nodes with good ping times to suddenly become lagged, // due to a new block arriving or other large transfer. Merely reporting // pingtime might fool the caller into thinking the node was still // responsive, since pingtime does not update until the ping is complete, // which might take a while. So, if a ping is taking an unusually long time // in flight, the caller can immediately detect that this is happening. std::chrono::microseconds ping_wait{0}; if ((0 != nPingNonceSent) && (0 != m_ping_start.load().count())) { ping_wait = GetTime<std::chrono::microseconds>() - m_ping_start.load(); } // Raw ping time is in microseconds, but show it to user as whole seconds // (Bitcoin users should be well used to small numbers with many decimal // places by now :) stats.m_ping_usec = nPingUsecTime; stats.m_min_ping_usec = nMinPingUsecTime; stats.m_ping_wait_usec = count_microseconds(ping_wait); // Leave string empty if addrLocal invalid (not filled in yet) CService addrLocalUnlocked = GetAddrLocal(); stats.addrLocal = addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : ""; stats.m_conn_type_string = ConnectionTypeAsString(); } bool CNode::ReceiveMsgBytes(const Config &config, const char *pch, uint32_t nBytes, bool &complete) { complete = false; const auto time = GetTime<std::chrono::microseconds>(); LOCK(cs_vRecv); nLastRecv = std::chrono::duration_cast<std::chrono::seconds>(time).count(); nRecvBytes += nBytes; while (nBytes > 0) { // Absorb network data. int handled = m_deserializer->Read(config, pch, nBytes); if (handled < 0) { return false; } pch += handled; nBytes -= handled; if (m_deserializer->Complete()) { // decompose a transport agnostic CNetMessage from the deserializer CNetMessage msg = m_deserializer->GetMessage(config, time); // Store received bytes per message command to prevent a memory DOS, // only allow valid commands. mapMsgCmdSize::iterator i = mapRecvBytesPerMsgCmd.find(msg.m_command); if (i == mapRecvBytesPerMsgCmd.end()) { i = mapRecvBytesPerMsgCmd.find(NET_MESSAGE_COMMAND_OTHER); } assert(i != mapRecvBytesPerMsgCmd.end()); i->second += msg.m_raw_message_size; // push the message to the process queue, vRecvMsg.push_back(std::move(msg)); complete = true; } } return true; } int V1TransportDeserializer::readHeader(const Config &config, const char *pch, uint32_t nBytes) { // copy data to temporary parsing buffer uint32_t nRemaining = CMessageHeader::HEADER_SIZE - nHdrPos; uint32_t nCopy = std::min(nRemaining, nBytes); memcpy(&hdrbuf[nHdrPos], pch, nCopy); nHdrPos += nCopy; // if header incomplete, exit if (nHdrPos < CMessageHeader::HEADER_SIZE) { return nCopy; } // deserialize to CMessageHeader try { hdrbuf >> hdr; } catch (const std::exception &) { return -1; } // Reject oversized messages if (hdr.IsOversized(config)) { LogPrint(BCLog::NET, "Oversized header detected\n"); return -1; } // switch state to reading message data in_data = true; return nCopy; } int V1TransportDeserializer::readData(const char *pch, uint32_t nBytes) { unsigned int nRemaining = hdr.nMessageSize - nDataPos; unsigned int nCopy = std::min(nRemaining, nBytes); if (vRecv.size() < nDataPos + nCopy) { // Allocate up to 256 KiB ahead, but never more than the total message // size. vRecv.resize(std::min(hdr.nMessageSize, nDataPos + nCopy + 256 * 1024)); } hasher.Write({(const uint8_t *)pch, nCopy}); memcpy(&vRecv[nDataPos], pch, nCopy); nDataPos += nCopy; return nCopy; } const uint256 &V1TransportDeserializer::GetMessageHash() const { assert(Complete()); if (data_hash.IsNull()) { hasher.Finalize(data_hash); } return data_hash; } CNetMessage V1TransportDeserializer::GetMessage(const Config &config, const std::chrono::microseconds time) { // decompose a single CNetMessage from the TransportDeserializer CNetMessage msg(std::move(vRecv)); // store state about valid header, netmagic and checksum msg.m_valid_header = hdr.IsValid(config); // FIXME Split CheckHeaderMagicAndCommand() into CheckHeaderMagic() and // CheckCommand() to prevent the net magic check code duplication. msg.m_valid_netmagic = (memcmp(std::begin(hdr.pchMessageStart), std::begin(config.GetChainParams().NetMagic()), CMessageHeader::MESSAGE_START_SIZE) == 0); uint256 hash = GetMessageHash(); // store command string, payload size msg.m_command = hdr.GetCommand(); msg.m_message_size = hdr.nMessageSize; msg.m_raw_message_size = hdr.nMessageSize + CMessageHeader::HEADER_SIZE; // We just received a message off the wire, harvest entropy from the time // (and the message checksum) RandAddEvent(ReadLE32(hash.begin())); msg.m_valid_checksum = (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) == 0); if (!msg.m_valid_checksum) { LogPrint( BCLog::NET, "CHECKSUM ERROR (%s, %u bytes), expected %s was %s\n", SanitizeString(msg.m_command), msg.m_message_size, HexStr(Span<uint8_t>(hash.begin(), hash.begin() + CMessageHeader::CHECKSUM_SIZE)), HexStr(hdr.pchChecksum)); } // store receive time msg.m_time = time; // reset the network deserializer (prepare for the next message) Reset(); return msg; } void V1TransportSerializer::prepareForTransport(const Config &config, CSerializedNetMsg &msg, std::vector<uint8_t> &header) { // create dbl-sha256 checksum uint256 hash = Hash(msg.data); // create header CMessageHeader hdr(config.GetChainParams().NetMagic(), msg.m_type.c_str(), msg.data.size()); memcpy(hdr.pchChecksum, hash.begin(), CMessageHeader::CHECKSUM_SIZE); // serialize header header.reserve(CMessageHeader::HEADER_SIZE); CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, header, 0, hdr}; } size_t CConnman::SocketSendData(CNode *pnode) const EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_vSend) { size_t nSentSize = 0; size_t nMsgCount = 0; for (const auto &data : pnode->vSendMsg) { assert(data.size() > pnode->nSendOffset); int nBytes = 0; { LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) { break; } nBytes = send(pnode->hSocket, reinterpret_cast<const char *>(data.data()) + pnode->nSendOffset, data.size() - pnode->nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT); } if (nBytes == 0) { // couldn't send anything at all break; } if (nBytes < 0) { // error int nErr = WSAGetLastError(); if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) { LogPrintf("socket send error %s\n", NetworkErrorString(nErr)); pnode->CloseSocketDisconnect(); } break; } assert(nBytes > 0); pnode->nLastSend = GetSystemTimeInSeconds(); pnode->nSendBytes += nBytes; pnode->nSendOffset += nBytes; nSentSize += nBytes; if (pnode->nSendOffset != data.size()) { // could not send full message; stop sending more break; } pnode->nSendOffset = 0; pnode->nSendSize -= data.size(); pnode->fPauseSend = pnode->nSendSize > nSendBufferMaxSize; nMsgCount++; } pnode->vSendMsg.erase(pnode->vSendMsg.begin(), pnode->vSendMsg.begin() + nMsgCount); if (pnode->vSendMsg.empty()) { assert(pnode->nSendOffset == 0); assert(pnode->nSendSize == 0); } return nSentSize; } static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nMinPingUsecTime > b.nMinPingUsecTime; } static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nTimeConnected > b.nTimeConnected; } static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { if (a.m_is_local != b.m_is_local) { return b.m_is_local; } return a.nTimeConnected > b.nTimeConnected; } static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nKeyedNetGroup < b.nKeyedNetGroup; } static bool CompareNodeBlockTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { // There is a fall-through here because it is common for a node to have many // peers which have not yet relayed a block. if (a.nLastBlockTime != b.nLastBlockTime) { return a.nLastBlockTime < b.nLastBlockTime; } if (a.fRelevantServices != b.fRelevantServices) { return b.fRelevantServices; } return a.nTimeConnected > b.nTimeConnected; } static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { // There is a fall-through here because it is common for a node to have more // than a few peers that have not yet relayed txn. if (a.nLastTXTime != b.nLastTXTime) { return a.nLastTXTime < b.nLastTXTime; } if (a.fRelayTxes != b.fRelayTxes) { return b.fRelayTxes; } if (a.fBloomFilter != b.fBloomFilter) { return a.fBloomFilter; } return a.nTimeConnected > b.nTimeConnected; } static bool CompareNodeProofTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { // There is a fall-through here because it is common for a node to have more // than a few peers that have not yet relayed proofs. This fallback is also // used in the case avalanche is not enabled. if (a.nLastProofTime != b.nLastProofTime) { return a.nLastProofTime < b.nLastProofTime; } return a.nTimeConnected > b.nTimeConnected; } // Pick out the potential block-relay only peers, and sort them by last block // time. static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { if (a.fRelayTxes != b.fRelayTxes) { return a.fRelayTxes; } if (a.nLastBlockTime != b.nLastBlockTime) { return a.nLastBlockTime < b.nLastBlockTime; } if (a.fRelevantServices != b.fRelevantServices) { return b.fRelevantServices; } return a.nTimeConnected > b.nTimeConnected; } static bool CompareNodeAvailabilityScore(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { // Equality can happen if the nodes have no score or it has not been // computed yet. if (a.availabilityScore != b.availabilityScore) { return a.availabilityScore < b.availabilityScore; } return a.nTimeConnected > b.nTimeConnected; } //! Sort an array by the specified comparator, then erase the last K elements. template <typename T, typename Comparator> static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, size_t k) { std::sort(elements.begin(), elements.end(), comparator); size_t eraseSize = std::min(k, elements.size()); elements.erase(elements.end() - eraseSize, elements.end()); } //! Sort an array by the specified comparator, then erase up to K last elements //! which verify the condition. template <typename T, typename Comparator, typename Condition> static void EraseLastKElementsIf(std::vector<T> &elements, Comparator comparator, size_t k, Condition cond) { std::sort(elements.begin(), elements.end(), comparator); size_t eraseSize = std::min(k, elements.size()); elements.erase( std::remove_if(elements.end() - eraseSize, elements.end(), cond), elements.end()); } [[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate> &&vEvictionCandidates) { // Protect connections with certain characteristics // Deterministically select 4 peers to protect by netgroup. // An attacker cannot predict which netgroups will be protected EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4); // Protect the 8 nodes with the lowest minimum ping time. // An attacker cannot manipulate this metric without physically moving nodes // closer to the target. EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8); // Protect 4 nodes that most recently sent us novel transactions accepted // into our mempool. An attacker cannot manipulate this metric without // performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4); // Protect 4 nodes that most recently sent us novel proofs accepted // into our proof pool. An attacker cannot manipulate this metric without // performing useful work. // TODO this filter must happen before the last tx time once avalanche is // enabled for pre-consensus. EraseLastKElements(vEvictionCandidates, CompareNodeProofTime, 4); // Protect up to 8 non-tx-relay peers that have sent us novel blocks. EraseLastKElementsIf(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8, [](NodeEvictionCandidate const &n) { return !n.fRelayTxes && n.fRelevantServices; }); // Protect 4 nodes that most recently sent us novel blocks. // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4); // Protect up to 128 nodes that have the highest avalanche availability // score. EraseLastKElementsIf(vEvictionCandidates, CompareNodeAvailabilityScore, 128, [](NodeEvictionCandidate const &n) { return n.availabilityScore > 0.; }); // Protect the half of the remaining nodes which have been connected the // longest. This replicates the non-eviction implicit behavior, and // precludes attacks that start later. // Reserve half of these protected spots for localhost peers, even if // they're not longest-uptime overall. This helps protect tor peers, which // tend to be otherwise disadvantaged under our eviction criteria. size_t initial_size = vEvictionCandidates.size(); size_t total_protect_size = initial_size / 2; // Pick out up to 1/4 peers that are localhost, sorted by longest uptime. EraseLastKElementsIf( vEvictionCandidates, CompareLocalHostTimeConnected, total_protect_size / 2, [](NodeEvictionCandidate const &n) { return n.m_is_local; }); // Calculate how many we removed, and update our total number of peers that // we want to protect based on uptime accordingly. total_protect_size -= initial_size - vEvictionCandidates.size(); EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size); if (vEvictionCandidates.empty()) { return std::nullopt; } // If any remaining peers are preferred for eviction consider only them. // This happens after the other preferences since if a peer is really the // best by other criteria (esp relaying blocks) // then we probably don't want to evict it no matter what. if (std::any_of( vEvictionCandidates.begin(), vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.prefer_evict; })) { vEvictionCandidates.erase( std::remove_if( vEvictionCandidates.begin(), vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return !n.prefer_evict; }), vEvictionCandidates.end()); } // Identify the network group with the most connections and youngest member. // (vEvictionCandidates is already sorted by reverse connect time) uint64_t naMostConnections; unsigned int nMostConnections = 0; int64_t nMostConnectionsTime = 0; std::map<uint64_t, std::vector<NodeEvictionCandidate>> mapNetGroupNodes; for (const NodeEvictionCandidate &node : vEvictionCandidates) { std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup]; group.push_back(node); int64_t grouptime = group[0].nTimeConnected; size_t group_size = group.size(); if (group_size > nMostConnections || (group_size == nMostConnections && grouptime > nMostConnectionsTime)) { nMostConnections = group_size; nMostConnectionsTime = grouptime; naMostConnections = node.nKeyedNetGroup; } } // Reduce to the network group with the most connections vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]); // Disconnect from the network group with the most connections return vEvictionCandidates.front().id; } /** Try to find a connection to evict when the node is full. * Extreme care must be taken to avoid opening the node to attacker * triggered network partitioning. * The strategy used here is to protect a small number of peers * for each of several distinct characteristics which are difficult * to forge. In order to partition a node the attacker must be * simultaneously better at all of them than honest peers. */ bool CConnman::AttemptToEvictConnection() { std::vector<NodeEvictionCandidate> vEvictionCandidates; { LOCK(cs_vNodes); for (const CNode *node : vNodes) { if (node->HasPermission(PF_NOBAN)) { continue; } if (!node->IsInboundConn()) { continue; } if (node->fDisconnect) { continue; } bool peer_relay_txes = false; bool peer_filter_not_null = false; if (node->m_tx_relay != nullptr) { LOCK(node->m_tx_relay->cs_filter); peer_relay_txes = node->m_tx_relay->fRelayTxes; peer_filter_not_null = node->m_tx_relay->pfilter != nullptr; } NodeEvictionCandidate candidate = { node->GetId(), node->nTimeConnected, node->nMinPingUsecTime, node->nLastBlockTime, node->nLastProofTime, node->nLastTXTime, HasAllDesirableServiceFlags(node->nServices), peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup, node->m_prefer_evict, node->addr.IsLocal(), node->m_avalanche_state ? node->m_avalanche_state->getAvailabilityScore() : -std::numeric_limits<double>::infinity()}; vEvictionCandidates.push_back(candidate); } } const std::optional<NodeId> node_id_to_evict = SelectNodeToEvict(std::move(vEvictionCandidates)); if (!node_id_to_evict) { return false; } LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (pnode->GetId() == *node_id_to_evict) { pnode->fDisconnect = true; return true; } } return false; } void CConnman::AcceptConnection(const ListenSocket &hListenSocket) { struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr *)&sockaddr, &len); CAddress addr; int nInbound = 0; int nMaxInbound = nMaxConnections - m_max_outbound; if (hSocket != INVALID_SOCKET) { if (!addr.SetSockAddr((const struct sockaddr *)&sockaddr)) { LogPrintf("Warning: Unknown socket family\n"); } } NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE; hListenSocket.AddSocketPermissionFlags(permissionFlags); AddWhitelistPermissionFlags(permissionFlags, addr); bool legacyWhitelisted = false; if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_ISIMPLICIT)) { NetPermissions::ClearFlag(permissionFlags, PF_ISIMPLICIT); if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) { NetPermissions::AddFlag(permissionFlags, PF_FORCERELAY); } if (gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) { NetPermissions::AddFlag(permissionFlags, PF_RELAY); } NetPermissions::AddFlag(permissionFlags, PF_MEMPOOL); NetPermissions::AddFlag(permissionFlags, PF_NOBAN); legacyWhitelisted = true; } { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (pnode->IsInboundConn()) { nInbound++; } } } if (hSocket == INVALID_SOCKET) { int nErr = WSAGetLastError(); if (nErr != WSAEWOULDBLOCK) { LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); } return; } if (!fNetworkActive) { LogPrintf("connection from %s dropped: not accepting new connections\n", addr.ToString()); CloseSocket(hSocket); return; } if (!IsSelectableSocket(hSocket)) { LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString()); CloseSocket(hSocket); return; } // According to the internet TCP_NODELAY is not carried into accepted // sockets on all platforms. Set it again here just to be sure. SetSocketNoDelay(hSocket); // Don't accept connections from banned peers. bool banned = m_banman && m_banman->IsBanned(addr); if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN) && banned) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); CloseSocket(hSocket); return; } // Only accept connections from discouraged peers if our inbound slots // aren't (almost) full. bool discouraged = m_banman && m_banman->IsDiscouraged(addr); if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_NOBAN) && nInbound + 1 >= nMaxInbound && discouraged) { LogPrint(BCLog::NET, "connection from %s dropped (discouraged)\n", addr.ToString()); CloseSocket(hSocket); return; } if (nInbound >= nMaxInbound) { if (!AttemptToEvictConnection()) { // No connection to evict, disconnect the new connection LogPrint(BCLog::NET, "failed to find an eviction candidate - " "connection dropped (full)\n"); CloseSocket(hSocket); return; } } NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE) .Write(id) .Finalize(); uint64_t extra_entropy = GetDeterministicRandomizer(RANDOMIZER_ID_EXTRAENTROPY) .Write(id) .Finalize(); CAddress addr_bind = GetBindAddress(hSocket); ServiceFlags nodeServices = nLocalServices; if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) { nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM); } CNode *pnode = new CNode(id, nodeServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, extra_entropy, addr_bind, "", ConnectionType::INBOUND); pnode->AddRef(); pnode->m_permissionFlags = permissionFlags; // If this flag is present, the user probably expect that RPC and QT report // it as whitelisted (backward compatibility) pnode->m_legacyWhitelisted = legacyWhitelisted; pnode->m_prefer_evict = discouraged; m_msgproc->InitializeNode(*config, pnode); LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString()); { LOCK(cs_vNodes); vNodes.push_back(pnode); } // We received a new connection, harvest entropy from the time (and our peer // count) RandAddEvent(uint32_t(id)); } void CConnman::DisconnectNodes() { { LOCK(cs_vNodes); if (!fNetworkActive) { // Disconnect any connected nodes for (CNode *pnode : vNodes) { if (!pnode->fDisconnect) { LogPrint(BCLog::NET, "Network not active, dropping peer=%d\n", pnode->GetId()); pnode->fDisconnect = true; } } } // Disconnect unused nodes std::vector<CNode *> vNodesCopy = vNodes; for (CNode *pnode : vNodesCopy) { if (pnode->fDisconnect) { // remove from vNodes vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); // release outbound grant (if any) pnode->grantOutbound.Release(); // close socket and cleanup pnode->CloseSocketDisconnect(); // hold in disconnected pool until all refs are released pnode->Release(); vNodesDisconnected.push_back(pnode); } } } { // Delete disconnected nodes std::list<CNode *> vNodesDisconnectedCopy = vNodesDisconnected; for (CNode *pnode : vNodesDisconnectedCopy) { // wait until threads are done using it if (pnode->GetRefCount() <= 0) { bool fDelete = false; { TRY_LOCK(pnode->cs_vSend, lockSend); if (lockSend) { fDelete = true; } } if (fDelete) { vNodesDisconnected.remove(pnode); DeleteNode(pnode); } } } } } void CConnman::NotifyNumConnectionsChanged() { size_t vNodesSize; { LOCK(cs_vNodes); vNodesSize = vNodes.size(); } if (vNodesSize != nPrevNodeCount) { nPrevNodeCount = vNodesSize; if (clientInterface) { clientInterface->NotifyNumConnectionsChanged(vNodesSize); } } } void CConnman::InactivityCheck(CNode *pnode) { int64_t nTime = GetSystemTimeInSeconds(); if (nTime - pnode->nTimeConnected > m_peer_connect_timeout) { if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) { LogPrint(BCLog::NET, "socket no message in first %i seconds, %d %d from %d\n", m_peer_connect_timeout, pnode->nLastRecv != 0, pnode->nLastSend != 0, pnode->GetId()); pnode->fDisconnect = true; } else if (nTime - pnode->nLastSend > TIMEOUT_INTERVAL) { LogPrintf("socket sending timeout: %is\n", nTime - pnode->nLastSend); pnode->fDisconnect = true; } else if (nTime - pnode->nLastRecv > (pnode->GetCommonVersion() > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90 * 60)) { LogPrintf("socket receive timeout: %is\n", nTime - pnode->nLastRecv); pnode->fDisconnect = true; } else if (pnode->nPingNonceSent && pnode->m_ping_start.load() + std::chrono::seconds{TIMEOUT_INTERVAL} < GetTime<std::chrono::microseconds>()) { LogPrintf("ping timeout: %fs\n", 0.000001 * count_microseconds( GetTime<std::chrono::microseconds>() - pnode->m_ping_start.load())); pnode->fDisconnect = true; } else if (!pnode->fSuccessfullyConnected) { LogPrint(BCLog::NET, "version handshake timeout from %d\n", pnode->GetId()); pnode->fDisconnect = true; } } } bool CConnman::GenerateSelectSet(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) { for (const ListenSocket &hListenSocket : vhListenSocket) { recv_set.insert(hListenSocket.socket); } { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { // Implement the following logic: // * If there is data to send, select() for sending data. As this // only happens when optimistic write failed, we choose to first // drain the write buffer in this case before receiving more. This // avoids needlessly queueing received data, if the remote peer is // not themselves receiving data. This means properly utilizing // TCP flow control signalling. // * Otherwise, if there is space left in the receive buffer, // select() for receiving data. // * Hand off all complete messages to the processor, to be handled // without blocking here. bool select_recv = !pnode->fPauseRecv; bool select_send; { LOCK(pnode->cs_vSend); select_send = !pnode->vSendMsg.empty(); } LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) { continue; } error_set.insert(pnode->hSocket); if (select_send) { send_set.insert(pnode->hSocket); continue; } if (select_recv) { recv_set.insert(pnode->hSocket); } } } return !recv_set.empty() || !send_set.empty() || !error_set.empty(); } #ifdef USE_POLL void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) { std::set<SOCKET> recv_select_set, send_select_set, error_select_set; if (!GenerateSelectSet(recv_select_set, send_select_set, error_select_set)) { interruptNet.sleep_for( std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS)); return; } std::unordered_map<SOCKET, struct pollfd> pollfds; for (SOCKET socket_id : recv_select_set) { pollfds[socket_id].fd = socket_id; pollfds[socket_id].events |= POLLIN; } for (SOCKET socket_id : send_select_set) { pollfds[socket_id].fd = socket_id; pollfds[socket_id].events |= POLLOUT; } for (SOCKET socket_id : error_select_set) { pollfds[socket_id].fd = socket_id; // These flags are ignored, but we set them for clarity pollfds[socket_id].events |= POLLERR | POLLHUP; } std::vector<struct pollfd> vpollfds; vpollfds.reserve(pollfds.size()); for (auto it : pollfds) { vpollfds.push_back(std::move(it.second)); } if (poll(vpollfds.data(), vpollfds.size(), SELECT_TIMEOUT_MILLISECONDS) < 0) { return; } if (interruptNet) { return; } for (struct pollfd pollfd_entry : vpollfds) { if (pollfd_entry.revents & POLLIN) { recv_set.insert(pollfd_entry.fd); } if (pollfd_entry.revents & POLLOUT) { send_set.insert(pollfd_entry.fd); } if (pollfd_entry.revents & (POLLERR | POLLHUP)) { error_set.insert(pollfd_entry.fd); } } } #else void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) { std::set<SOCKET> recv_select_set, send_select_set, error_select_set; if (!GenerateSelectSet(recv_select_set, send_select_set, error_select_set)) { interruptNet.sleep_for( std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS)); return; } // // Find which sockets have data to receive // struct timeval timeout; timeout.tv_sec = 0; // frequency to poll pnode->vSend timeout.tv_usec = SELECT_TIMEOUT_MILLISECONDS * 1000; fd_set fdsetRecv; fd_set fdsetSend; fd_set fdsetError; FD_ZERO(&fdsetRecv); FD_ZERO(&fdsetSend); FD_ZERO(&fdsetError); SOCKET hSocketMax = 0; for (SOCKET hSocket : recv_select_set) { FD_SET(hSocket, &fdsetRecv); hSocketMax = std::max(hSocketMax, hSocket); } for (SOCKET hSocket : send_select_set) { FD_SET(hSocket, &fdsetSend); hSocketMax = std::max(hSocketMax, hSocket); } for (SOCKET hSocket : error_select_set) { FD_SET(hSocket, &fdsetError); hSocketMax = std::max(hSocketMax, hSocket); } int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, &fdsetError, &timeout); if (interruptNet) { return; } if (nSelect == SOCKET_ERROR) { int nErr = WSAGetLastError(); LogPrintf("socket select error %s\n", NetworkErrorString(nErr)); for (unsigned int i = 0; i <= hSocketMax; i++) { FD_SET(i, &fdsetRecv); } FD_ZERO(&fdsetSend); FD_ZERO(&fdsetError); if (!interruptNet.sleep_for( std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS))) { return; } } for (SOCKET hSocket : recv_select_set) { if (FD_ISSET(hSocket, &fdsetRecv)) { recv_set.insert(hSocket); } } for (SOCKET hSocket : send_select_set) { if (FD_ISSET(hSocket, &fdsetSend)) { send_set.insert(hSocket); } } for (SOCKET hSocket : error_select_set) { if (FD_ISSET(hSocket, &fdsetError)) { error_set.insert(hSocket); } } } #endif void CConnman::SocketHandler() { std::set<SOCKET> recv_set, send_set, error_set; SocketEvents(recv_set, send_set, error_set); if (interruptNet) { return; } // // Accept new connections // for (const ListenSocket &hListenSocket : vhListenSocket) { if (hListenSocket.socket != INVALID_SOCKET && recv_set.count(hListenSocket.socket) > 0) { AcceptConnection(hListenSocket); } } // // Service each socket // std::vector<CNode *> vNodesCopy; { LOCK(cs_vNodes); vNodesCopy = vNodes; for (CNode *pnode : vNodesCopy) { pnode->AddRef(); } } for (CNode *pnode : vNodesCopy) { if (interruptNet) { return; } // // Receive // bool recvSet = false; bool sendSet = false; bool errorSet = false; { LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) { continue; } recvSet = recv_set.count(pnode->hSocket) > 0; sendSet = send_set.count(pnode->hSocket) > 0; errorSet = error_set.count(pnode->hSocket) > 0; } if (recvSet || errorSet) { // typical socket buffer is 8K-64K char pchBuf[0x10000]; int32_t nBytes = 0; { LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) { continue; } nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT); } if (nBytes > 0) { bool notify = false; if (!pnode->ReceiveMsgBytes(*config, pchBuf, nBytes, notify)) { pnode->CloseSocketDisconnect(); } RecordBytesRecv(nBytes); if (notify) { size_t nSizeAdded = 0; auto it(pnode->vRecvMsg.begin()); for (; it != pnode->vRecvMsg.end(); ++it) { // vRecvMsg contains only completed CNetMessage // the single possible partially deserialized message // are held by TransportDeserializer nSizeAdded += it->m_raw_message_size; } { LOCK(pnode->cs_vProcessMsg); pnode->vProcessMsg.splice(pnode->vProcessMsg.end(), pnode->vRecvMsg, pnode->vRecvMsg.begin(), it); pnode->nProcessQueueSize += nSizeAdded; pnode->fPauseRecv = pnode->nProcessQueueSize > nReceiveFloodSize; } WakeMessageHandler(); } } else if (nBytes == 0) { // socket closed gracefully if (!pnode->fDisconnect) { LogPrint(BCLog::NET, "socket closed for peer=%d\n", pnode->GetId()); } pnode->CloseSocketDisconnect(); } else if (nBytes < 0) { // error int nErr = WSAGetLastError(); if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) { if (!pnode->fDisconnect) { LogPrint(BCLog::NET, "socket recv error for peer=%d: %s\n", pnode->GetId(), NetworkErrorString(nErr)); } pnode->CloseSocketDisconnect(); } } } // // Send // if (sendSet) { LOCK(pnode->cs_vSend); size_t nBytes = SocketSendData(pnode); if (nBytes) { RecordBytesSent(nBytes); } } InactivityCheck(pnode); } { LOCK(cs_vNodes); for (CNode *pnode : vNodesCopy) { pnode->Release(); } } } void CConnman::ThreadSocketHandler() { while (!interruptNet) { DisconnectNodes(); NotifyNumConnectionsChanged(); SocketHandler(); } } void CConnman::WakeMessageHandler() { { LOCK(mutexMsgProc); fMsgProcWake = true; } condMsgProc.notify_one(); } #ifdef USE_UPNP static CThreadInterrupt g_upnp_interrupt; static std::thread g_upnp_thread; static void ThreadMapPort() { std::string port = strprintf("%u", GetListenPort()); const char *multicastif = nullptr; const char *minissdpdpath = nullptr; struct UPNPDev *devlist = nullptr; char lanaddr[64]; int error = 0; #if MINIUPNPC_API_VERSION < 14 devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); #else devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); #endif struct UPNPUrls urls; struct IGDdatas data; int r; r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); if (r == 1) { if (fDiscover) { char externalIPAddress[40]; r = UPNP_GetExternalIPAddress( urls.controlURL, data.first.servicetype, externalIPAddress); if (r != UPNPCOMMAND_SUCCESS) { LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r); } else { if (externalIPAddress[0]) { CNetAddr resolved; if (LookupHost(externalIPAddress, resolved, false)) { LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString()); AddLocal(resolved, LOCAL_UPNP); } } else { LogPrintf("UPnP: GetExternalIPAddress failed.\n"); } } } std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); do { r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); if (r != UPNPCOMMAND_SUCCESS) { LogPrintf( "AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r)); } else { LogPrintf("UPnP Port Mapping successful.\n"); } } while (g_upnp_interrupt.sleep_for(std::chrono::minutes(20))); r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); freeUPNPDevlist(devlist); devlist = nullptr; FreeUPNPUrls(&urls); } else { LogPrintf("No valid UPnP IGDs found\n"); freeUPNPDevlist(devlist); devlist = nullptr; if (r != 0) { FreeUPNPUrls(&urls); } } } void StartMapPort() { if (!g_upnp_thread.joinable()) { assert(!g_upnp_interrupt); g_upnp_thread = std::thread( (std::bind(&TraceThread<void (*)()>, "upnp", &ThreadMapPort))); } } void InterruptMapPort() { if (g_upnp_thread.joinable()) { g_upnp_interrupt(); } } void StopMapPort() { if (g_upnp_thread.joinable()) { g_upnp_thread.join(); g_upnp_interrupt.reset(); } } #else void StartMapPort() { // Intentionally left blank. } void InterruptMapPort() { // Intentionally left blank. } void StopMapPort() { // Intentionally left blank. } #endif void CConnman::ThreadDNSAddressSeed() { FastRandomContext rng; std::vector<std::string> seeds = GetRandomizedDNSSeeds(config->GetChainParams()); // Number of seeds left before testing if we have enough connections int seeds_right_now = 0; int found = 0; if (gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED)) { // When -forcednsseed is provided, query all. seeds_right_now = seeds.size(); } else if (addrman.size() == 0) { // If we have no known peers, query all. // This will occur on the first run, or if peers.dat has been // deleted. seeds_right_now = seeds.size(); } // goal: only query DNS seed if address need is acute // * If we have a reasonable number of peers in addrman, spend // some time trying them first. This improves user privacy by // creating fewer identifying DNS requests, reduces trust by // giving seeds less influence on the network topology, and // reduces traffic to the seeds. // * When querying DNS seeds query a few at once, this ensures // that we don't give DNS seeds the ability to eclipse nodes // that query them. // * If we continue having problems, eventually query all the // DNS seeds, and if that fails too, also try the fixed seeds. // (done in ThreadOpenConnections) const std::chrono::seconds seeds_wait_time = (addrman.size() >= DNSSEEDS_DELAY_PEER_THRESHOLD ? DNSSEEDS_DELAY_MANY_PEERS : DNSSEEDS_DELAY_FEW_PEERS); for (const std::string &seed : seeds) { if (seeds_right_now == 0) { seeds_right_now += DNSSEEDS_TO_QUERY_AT_ONCE; if (addrman.size() > 0) { LogPrintf("Waiting %d seconds before querying DNS seeds.\n", seeds_wait_time.count()); std::chrono::seconds to_wait = seeds_wait_time; while (to_wait.count() > 0) { // if sleeping for the MANY_PEERS interval, wake up // early to see if we have enough peers and can stop // this thread entirely freeing up its resources std::chrono::seconds w = std::min(DNSSEEDS_DELAY_FEW_PEERS, to_wait); if (!interruptNet.sleep_for(w)) { return; } to_wait -= w; int nRelevant = 0; { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (pnode->fSuccessfullyConnected && pnode->IsOutboundOrBlockRelayConn()) { ++nRelevant; } } } if (nRelevant >= 2) { if (found > 0) { LogPrintf("%d addresses found from DNS seeds\n", found); LogPrintf( "P2P peers available. Finished DNS seeding.\n"); } else { LogPrintf( "P2P peers available. Skipped DNS seeding.\n"); } return; } } } } if (interruptNet) { return; } // hold off on querying seeds if P2P network deactivated if (!fNetworkActive) { LogPrintf("Waiting for network to be reactivated before querying " "DNS seeds.\n"); do { if (!interruptNet.sleep_for(std::chrono::seconds{1})) { return; } } while (!fNetworkActive); } LogPrintf("Loading addresses from DNS seed %s\n", seed); if (HaveNameProxy()) { AddAddrFetch(seed); } else { std::vector<CNetAddr> vIPs; std::vector<CAddress> vAdd; ServiceFlags requiredServiceBits = GetDesirableServiceFlags(NODE_NONE); std::string host = strprintf("x%x.%s", requiredServiceBits, seed); CNetAddr resolveSource; if (!resolveSource.SetInternal(host)) { continue; } // Limits number of IPs learned from a DNS seed unsigned int nMaxIPs = 256; if (LookupHost(host, vIPs, nMaxIPs, true)) { for (const CNetAddr &ip : vIPs) { int nOneDay = 24 * 3600; CAddress addr = CAddress( CService(ip, config->GetChainParams().GetDefaultPort()), requiredServiceBits); // Use a random age between 3 and 7 days old. addr.nTime = GetTime() - 3 * nOneDay - rng.randrange(4 * nOneDay); vAdd.push_back(addr); found++; } addrman.Add(vAdd, resolveSource); } else { // We now avoid directly using results from DNS Seeds which do // not support service bit filtering, instead using them as a // addrfetch to get nodes with our desired service bits. AddAddrFetch(seed); } } --seeds_right_now; } LogPrintf("%d addresses found from DNS seeds\n", found); } void CConnman::DumpAddresses() { int64_t nStart = GetTimeMillis(); CAddrDB adb(config->GetChainParams()); adb.Write(addrman); LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n", addrman.size(), GetTimeMillis() - nStart); } void CConnman::ProcessAddrFetch() { std::string strDest; { LOCK(m_addr_fetches_mutex); if (m_addr_fetches.empty()) { return; } strDest = m_addr_fetches.front(); m_addr_fetches.pop_front(); } CAddress addr; CSemaphoreGrant grant(*semOutbound, true); if (grant) { OpenNetworkConnection(addr, false, &grant, strDest.c_str(), ConnectionType::ADDR_FETCH); } } bool CConnman::GetTryNewOutboundPeer() { return m_try_another_outbound_peer; } void CConnman::SetTryNewOutboundPeer(bool flag) { m_try_another_outbound_peer = flag; LogPrint(BCLog::NET, "net: setting try another outbound peer=%s\n", flag ? "true" : "false"); } // Return the number of peers we have over our outbound connection limit. // Exclude peers that are marked for disconnect, or are going to be disconnected // soon (eg ADDR_FETCH and FEELER). // Also exclude peers that haven't finished initial connection handshake yet (so // that we don't decide we're over our desired connection limit, and then evict // some peer that has finished the handshake). int CConnman::GetExtraOutboundCount() { int nOutbound = 0; { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsOutboundOrBlockRelayConn()) { ++nOutbound; } } } return std::max( nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0); } void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) { // Connect to specific addresses if (!connect.empty()) { for (int64_t nLoop = 0;; nLoop++) { ProcessAddrFetch(); for (const std::string &strAddr : connect) { CAddress addr(CService(), NODE_NONE); OpenNetworkConnection(addr, false, nullptr, strAddr.c_str(), ConnectionType::MANUAL); for (int i = 0; i < 10 && i < nLoop; i++) { if (!interruptNet.sleep_for( std::chrono::milliseconds(500))) { return; } } } if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) { return; } } } // Initiate network connections int64_t nStart = GetTime(); // Minimum time before next feeler connection (in microseconds). int64_t nNextFeeler = PoissonNextSend(nStart * 1000 * 1000, FEELER_INTERVAL); while (!interruptNet) { ProcessAddrFetch(); if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) { return; } CSemaphoreGrant grant(*semOutbound); if (interruptNet) { return; } // Add seed nodes if DNS seeds are all down (an infrastructure attack?). // Note that we only do this if we started with an empty peers.dat, // (in which case we will query DNS seeds immediately) *and* the DNS // seeds have not returned any results. if (addrman.size() == 0 && (GetTime() - nStart > 60)) { static bool done = false; if (!done) { LogPrintf("Adding fixed seed nodes as DNS doesn't seem to be " "available.\n"); CNetAddr local; local.SetInternal("fixedseeds"); addrman.Add(convertSeed6(config->GetChainParams().FixedSeeds()), local); done = true; } } // // Choose an address to connect to based on most recently seen // CAddress addrConnect; // Only connect out to one peer per network group (/16 for IPv4). int nOutboundFullRelay = 0; int nOutboundBlockRelay = 0; std::set<std::vector<uint8_t>> setConnected; { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (pnode->IsFullOutboundConn()) { nOutboundFullRelay++; } if (pnode->IsBlockOnlyConn()) { nOutboundBlockRelay++; } // Netgroups for inbound and manual peers are not excluded // because our goal here is to not use multiple of our // limited outbound slots on a single netgroup but inbound // and manual peers do not use our outbound slots. Inbound // peers also have the added issue that they could be attacker // controlled and could be used to prevent us from connecting // to particular hosts if we used them here. switch (pnode->m_conn_type) { case ConnectionType::INBOUND: case ConnectionType::MANUAL: break; case ConnectionType::OUTBOUND_FULL_RELAY: case ConnectionType::BLOCK_RELAY: case ConnectionType::ADDR_FETCH: case ConnectionType::FEELER: setConnected.insert( pnode->addr.GetGroup(addrman.m_asmap)); } // no default case, so the compiler can warn about missing // cases } } ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY; int64_t nTime = GetTimeMicros(); bool fFeeler = false; // Determine what type of connection to open. Opening // OUTBOUND_FULL_RELAY connections gets the highest priority until we // meet our full-relay capacity. Then we open BLOCK_RELAY connection // until we hit our block-relay-only peer limit. // GetTryNewOutboundPeer() gets set when a stale tip is detected, so we // try opening an additional OUTBOUND_FULL_RELAY connection. If none of // these conditions are met, check the nNextFeeler timer to decide if // we should open a FEELER. if (nOutboundFullRelay < m_max_outbound_full_relay) { // OUTBOUND_FULL_RELAY } else if (nOutboundBlockRelay < m_max_outbound_block_relay) { conn_type = ConnectionType::BLOCK_RELAY; } else if (GetTryNewOutboundPeer()) { // OUTBOUND_FULL_RELAY } else if (nTime > nNextFeeler) { nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); conn_type = ConnectionType::FEELER; fFeeler = true; } else { // skip to next iteration of while loop continue; } addrman.ResolveCollisions(); int64_t nANow = GetAdjustedTime(); int nTries = 0; while (!interruptNet) { CAddrInfo addr = addrman.SelectTriedCollision(); // SelectTriedCollision returns an invalid address if it is empty. if (!fFeeler || !addr.IsValid()) { addr = addrman.Select(fFeeler); } // Require outbound connections, other than feelers, to be to // distinct network groups if (!fFeeler && setConnected.count(addr.GetGroup(addrman.m_asmap))) { break; } // if we selected an invalid or local address, restart if (!addr.IsValid() || IsLocal(addr)) { break; } // If we didn't find an appropriate destination after trying 100 // addresses fetched from addrman, stop this loop, and let the outer // loop run again (which sleeps, adds seed nodes, recalculates // already-connected network ranges, ...) before trying new addrman // addresses. nTries++; if (nTries > 100) { break; } if (!IsReachable(addr)) { continue; } // only consider very recently tried nodes after 30 failed attempts if (nANow - addr.nLastTry < 600 && nTries < 30) { continue; } // for non-feelers, require all the services we'll want, // for feelers, only require they be a full node (only because most // SPV clients don't have a good address DB available) if (!fFeeler && !HasAllDesirableServiceFlags(addr.nServices)) { continue; } if (fFeeler && !MayHaveUsefulAddressDB(addr.nServices)) { continue; } // do not allow non-default ports, unless after 50 invalid addresses // selected already. if (addr.GetPort() != config->GetChainParams().GetDefaultPort() && nTries < 50) { continue; } addrConnect = addr; break; } if (addrConnect.IsValid()) { if (fFeeler) { // Add small amount of random noise before connection to avoid // synchronization. int randsleep = GetRandInt(FEELER_SLEEP_WINDOW * 1000); if (!interruptNet.sleep_for( std::chrono::milliseconds(randsleep))) { return; } LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString()); } OpenNetworkConnection(addrConnect, int(setConnected.size()) >= std::min(nMaxConnections - 1, 2), &grant, nullptr, conn_type); } } } std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() { std::vector<AddedNodeInfo> ret; std::list<std::string> lAddresses(0); { LOCK(cs_vAddedNodes); ret.reserve(vAddedNodes.size()); std::copy(vAddedNodes.cbegin(), vAddedNodes.cend(), std::back_inserter(lAddresses)); } // Build a map of all already connected addresses (by IP:port and by name) // to inbound/outbound and resolved CService std::map<CService, bool> mapConnected; std::map<std::string, std::pair<bool, CService>> mapConnectedByName; { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (pnode->addr.IsValid()) { mapConnected[pnode->addr] = pnode->IsInboundConn(); } std::string addrName = pnode->GetAddrName(); if (!addrName.empty()) { mapConnectedByName[std::move(addrName)] = std::make_pair(pnode->IsInboundConn(), static_cast<const CService &>(pnode->addr)); } } } for (const std::string &strAddNode : lAddresses) { CService service(LookupNumeric(strAddNode, Params().GetDefaultPort())); AddedNodeInfo addedNode{strAddNode, CService(), false, false}; if (service.IsValid()) { // strAddNode is an IP:port auto it = mapConnected.find(service); if (it != mapConnected.end()) { addedNode.resolvedAddress = service; addedNode.fConnected = true; addedNode.fInbound = it->second; } } else { // strAddNode is a name auto it = mapConnectedByName.find(strAddNode); if (it != mapConnectedByName.end()) { addedNode.resolvedAddress = it->second.second; addedNode.fConnected = true; addedNode.fInbound = it->second.first; } } ret.emplace_back(std::move(addedNode)); } return ret; } void CConnman::ThreadOpenAddedConnections() { while (true) { CSemaphoreGrant grant(*semAddnode); std::vector<AddedNodeInfo> vInfo = GetAddedNodeInfo(); bool tried = false; for (const AddedNodeInfo &info : vInfo) { if (!info.fConnected) { if (!grant.TryAcquire()) { // If we've used up our semaphore and need a new one, let's // not wait here since while we are waiting the // addednodeinfo state might change. break; } tried = true; CAddress addr(CService(), NODE_NONE); OpenNetworkConnection(addr, false, &grant, info.strAddedNode.c_str(), ConnectionType::MANUAL); if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) { return; } } } // Retry every 60 seconds if a connection was attempted, otherwise two // seconds. if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2))) { return; } } } // If successful, this moves the passed grant to the constructed node. void CConnman::OpenNetworkConnection(const CAddress &addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, ConnectionType conn_type) { assert(conn_type != ConnectionType::INBOUND); // // Initiate outbound network connection // if (interruptNet) { return; } if (!fNetworkActive) { return; } if (!pszDest) { bool banned_or_discouraged = m_banman && (m_banman->IsDiscouraged(addrConnect) || m_banman->IsBanned(addrConnect)); if (IsLocal(addrConnect) || FindNode(static_cast<CNetAddr>(addrConnect)) || banned_or_discouraged || FindNode(addrConnect.ToStringIPPort())) { return; } } else if (FindNode(std::string(pszDest))) { return; } CNode *pnode = ConnectNode(addrConnect, pszDest, fCountFailure, conn_type); if (!pnode) { return; } if (grantOutbound) { grantOutbound->MoveTo(pnode->grantOutbound); } m_msgproc->InitializeNode(*config, pnode); { LOCK(cs_vNodes); vNodes.push_back(pnode); } } void CConnman::ThreadMessageHandler() { while (!flagInterruptMsgProc) { std::vector<CNode *> vNodesCopy; { LOCK(cs_vNodes); vNodesCopy = vNodes; for (CNode *pnode : vNodesCopy) { pnode->AddRef(); } } bool fMoreWork = false; for (CNode *pnode : vNodesCopy) { if (pnode->fDisconnect) { continue; } // Receive messages bool fMoreNodeWork = m_msgproc->ProcessMessages( *config, pnode, flagInterruptMsgProc); fMoreWork |= (fMoreNodeWork && !pnode->fPauseSend); if (flagInterruptMsgProc) { return; } // Send messages { LOCK(pnode->cs_sendProcessing); m_msgproc->SendMessages(*config, pnode, flagInterruptMsgProc); } if (flagInterruptMsgProc) { return; } } { LOCK(cs_vNodes); for (CNode *pnode : vNodesCopy) { pnode->Release(); } } WAIT_LOCK(mutexMsgProc, lock); if (!fMoreWork) { condMsgProc.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::milliseconds(100), [this]() EXCLUSIVE_LOCKS_REQUIRED( mutexMsgProc) { return fMsgProcWake; }); } fMsgProcWake = false; } } bool CConnman::BindListenPort(const CService &addrBind, bilingual_str &strError, NetPermissionFlags permissions) { int nOne = 1; // Create socket for listening for incoming connections struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (!addrBind.GetSockAddr((struct sockaddr *)&sockaddr, &len)) { strError = strprintf( Untranslated("Error: Bind address family for %s not supported"), addrBind.ToString()); LogPrintf("%s\n", strError.original); return false; } SOCKET hListenSocket = CreateSocket(addrBind); if (hListenSocket == INVALID_SOCKET) { strError = strprintf(Untranslated("Error: Couldn't open socket for incoming " "connections (socket returned error %s)"), NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError.original); return false; } // Allow binding if the port is still in TIME_WAIT state after // the program was closed and restarted. setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int)); // Some systems don't have IPV6_V6ONLY but are always v6only; others do have // the option and enable it by default or not. Try to enable it, if // possible. if (addrBind.IsIPv6()) { #ifdef IPV6_V6ONLY setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int)); #endif #ifdef WIN32 int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED; setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (sockopt_arg_type)&nProtLevel, sizeof(int)); #endif } if (::bind(hListenSocket, (struct sockaddr *)&sockaddr, len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) { strError = strprintf(_("Unable to bind to %s on this computer. %s " "is probably already running."), addrBind.ToString(), PACKAGE_NAME); } else { strError = strprintf(_("Unable to bind to %s on this computer " "(bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); } LogPrintf("%s\n", strError.original); CloseSocket(hListenSocket); return false; } LogPrintf("Bound to %s\n", addrBind.ToString()); // Listen for incoming connections if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) { strError = strprintf(_("Error: Listening for incoming connections " "failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError.original); CloseSocket(hListenSocket); return false; } vhListenSocket.push_back(ListenSocket(hListenSocket, permissions)); return true; } void Discover() { if (!fDiscover) { return; } #ifdef WIN32 // Get local host IP char pszHostName[256] = ""; if (gethostname(pszHostName, sizeof(pszHostName)) != SOCKET_ERROR) { std::vector<CNetAddr> vaddr; if (LookupHost(pszHostName, vaddr, 0, true)) { for (const CNetAddr &addr : vaddr) { if (AddLocal(addr, LOCAL_IF)) { LogPrintf("%s: %s - %s\n", __func__, pszHostName, addr.ToString()); } } } } #elif (HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS) // Get local host ip struct ifaddrs *myaddrs; if (getifaddrs(&myaddrs) == 0) { for (struct ifaddrs *ifa = myaddrs; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr || (ifa->ifa_flags & IFF_UP) == 0 || strcmp(ifa->ifa_name, "lo") == 0 || strcmp(ifa->ifa_name, "lo0") == 0) { continue; } if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *s4 = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr); CNetAddr addr(s4->sin_addr); if (AddLocal(addr, LOCAL_IF)) { LogPrintf("%s: IPv4 %s: %s\n", __func__, ifa->ifa_name, addr.ToString()); } } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *s6 = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr); CNetAddr addr(s6->sin6_addr); if (AddLocal(addr, LOCAL_IF)) { LogPrintf("%s: IPv6 %s: %s\n", __func__, ifa->ifa_name, addr.ToString()); } } } freeifaddrs(myaddrs); } #endif } void CConnman::SetNetworkActive(bool active) { LogPrintf("%s: %s\n", __func__, active); if (fNetworkActive == active) { return; } fNetworkActive = active; uiInterface.NotifyNetworkActiveChanged(fNetworkActive); } CConnman::CConnman(const Config &configIn, uint64_t nSeed0In, uint64_t nSeed1In, bool network_active) : config(&configIn), nSeed0(nSeed0In), nSeed1(nSeed1In) { SetTryNewOutboundPeer(false); Options connOptions; Init(connOptions); SetNetworkActive(network_active); } NodeId CConnman::GetNewNodeId() { return nLastNodeId.fetch_add(1); } bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions) { if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) { return false; } bilingual_str strError; if (!BindListenPort(addr, strError, permissions)) { if ((flags & BF_REPORT_ERROR) && clientInterface) { clientInterface->ThreadSafeMessageBox( strError, "", CClientUIInterface::MSG_ERROR); } return false; } - if (addr.IsRoutable() && fDiscover && (permissions & PF_NOBAN) == 0) { + if (addr.IsRoutable() && fDiscover && !(flags & BF_DONT_ADVERTISE) && + !(permissions & PF_NOBAN)) { AddLocal(addr, LOCAL_BIND); } return true; } -bool CConnman::InitBinds( - const std::vector<CService> &binds, - const std::vector<NetWhitebindPermissions> &whiteBinds) { +bool CConnman::InitBinds(const std::vector<CService> &binds, + const std::vector<NetWhitebindPermissions> &whiteBinds, + const std::vector<CService> &onion_binds) { bool fBound = false; for (const auto &addrBind : binds) { fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::PF_NONE); } for (const auto &addrBind : whiteBinds) { fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags); } if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = htonl(INADDR_ANY); struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::PF_NONE); fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::PF_NONE); } + + for (const auto &addr_bind : onion_binds) { + fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, + NetPermissionFlags::PF_NONE); + } + return fBound; } bool CConnman::Start(CScheduler &scheduler, const Options &connOptions) { Init(connOptions); { LOCK(cs_totalBytesRecv); nTotalBytesRecv = 0; } { LOCK(cs_totalBytesSent); nTotalBytesSent = 0; nMaxOutboundTotalBytesSentInCycle = 0; nMaxOutboundCycleStartTime = 0; } - if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) { + if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds, + connOptions.onion_binds)) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( _("Failed to listen on any port. Use -listen=0 if you want " "this."), "", CClientUIInterface::MSG_ERROR); } return false; } for (const auto &strDest : connOptions.vSeedNodes) { AddAddrFetch(strDest); } if (clientInterface) { clientInterface->InitMessage(_("Loading P2P addresses...").translated); } // Load addresses from peers.dat int64_t nStart = GetTimeMillis(); { CAddrDB adb(config->GetChainParams()); if (adb.Read(addrman)) { LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman.size(), GetTimeMillis() - nStart); } else { // Addrman can be in an inconsistent state after failure, reset it addrman.Clear(); LogPrintf("Invalid or missing peers.dat; recreating\n"); DumpAddresses(); } } uiInterface.InitMessage(_("Starting network threads...").translated); fAddressesInitialized = true; if (semOutbound == nullptr) { // initialize semaphore semOutbound = std::make_unique<CSemaphore>( std::min(m_max_outbound, nMaxConnections)); } if (semAddnode == nullptr) { // initialize semaphore semAddnode = std::make_unique<CSemaphore>(nMaxAddnode); } // // Start threads // assert(m_msgproc); InterruptSocks5(false); interruptNet.reset(); flagInterruptMsgProc = false; { LOCK(mutexMsgProc); fMsgProcWake = false; } // Send and receive from sockets, accept connections threadSocketHandler = std::thread( &TraceThread<std::function<void()>>, "net", std::function<void()>(std::bind(&CConnman::ThreadSocketHandler, this))); if (!gArgs.GetBoolArg("-dnsseed", true)) { LogPrintf("DNS seeding disabled\n"); } else { threadDNSAddressSeed = std::thread(&TraceThread<std::function<void()>>, "dnsseed", std::function<void()>( std::bind(&CConnman::ThreadDNSAddressSeed, this))); } // Initiate manual connections threadOpenAddedConnections = std::thread(&TraceThread<std::function<void()>>, "addcon", std::function<void()>(std::bind( &CConnman::ThreadOpenAddedConnections, this))); if (connOptions.m_use_addrman_outgoing && !connOptions.m_specified_outgoing.empty()) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( _("Cannot provide specific connections and have addrman find " "outgoing connections at the same."), "", CClientUIInterface::MSG_ERROR); } return false; } if (connOptions.m_use_addrman_outgoing || !connOptions.m_specified_outgoing.empty()) { threadOpenConnections = std::thread(&TraceThread<std::function<void()>>, "opencon", std::function<void()>( std::bind(&CConnman::ThreadOpenConnections, this, connOptions.m_specified_outgoing))); } // Process messages threadMessageHandler = std::thread(&TraceThread<std::function<void()>>, "msghand", std::function<void()>( std::bind(&CConnman::ThreadMessageHandler, this))); // Dump network addresses scheduler.scheduleEvery( [this]() { this->DumpAddresses(); return true; }, DUMP_PEERS_INTERVAL); return true; } class CNetCleanup { public: CNetCleanup() {} ~CNetCleanup() { #ifdef WIN32 // Shutdown Windows Sockets WSACleanup(); #endif } }; static CNetCleanup instance_of_cnetcleanup; void CConnman::Interrupt() { { LOCK(mutexMsgProc); flagInterruptMsgProc = true; } condMsgProc.notify_all(); interruptNet(); InterruptSocks5(true); if (semOutbound) { for (int i = 0; i < m_max_outbound; i++) { semOutbound->post(); } } if (semAddnode) { for (int i = 0; i < nMaxAddnode; i++) { semAddnode->post(); } } } void CConnman::StopThreads() { if (threadMessageHandler.joinable()) { threadMessageHandler.join(); } if (threadOpenConnections.joinable()) { threadOpenConnections.join(); } if (threadOpenAddedConnections.joinable()) { threadOpenAddedConnections.join(); } if (threadDNSAddressSeed.joinable()) { threadDNSAddressSeed.join(); } if (threadSocketHandler.joinable()) { threadSocketHandler.join(); } } void CConnman::StopNodes() { if (fAddressesInitialized) { DumpAddresses(); fAddressesInitialized = false; } // Close sockets LOCK(cs_vNodes); for (CNode *pnode : vNodes) { pnode->CloseSocketDisconnect(); } for (ListenSocket &hListenSocket : vhListenSocket) { if (hListenSocket.socket != INVALID_SOCKET) { if (!CloseSocket(hListenSocket.socket)) { LogPrintf("CloseSocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); } } } // clean up some globals (to help leak detection) for (CNode *pnode : vNodes) { DeleteNode(pnode); } for (CNode *pnode : vNodesDisconnected) { DeleteNode(pnode); } vNodes.clear(); vNodesDisconnected.clear(); vhListenSocket.clear(); semOutbound.reset(); semAddnode.reset(); } void CConnman::DeleteNode(CNode *pnode) { assert(pnode); bool fUpdateConnectionTime = false; m_msgproc->FinalizeNode(*config, pnode->GetId(), fUpdateConnectionTime); if (fUpdateConnectionTime) { addrman.Connected(pnode->addr); } delete pnode; } CConnman::~CConnman() { Interrupt(); Stop(); } void CConnman::SetServices(const CService &addr, ServiceFlags nServices) { addrman.SetServices(addr, nServices); } void CConnman::MarkAddressGood(const CAddress &addr) { addrman.Good(addr); } bool CConnman::AddNewAddresses(const std::vector<CAddress> &vAddr, const CAddress &addrFrom, int64_t nTimePenalty) { return addrman.Add(vAddr, addrFrom, nTimePenalty); } std::vector<CAddress> CConnman::GetAddresses(size_t max_addresses, size_t max_pct) { std::vector<CAddress> addresses = addrman.GetAddr(max_addresses, max_pct); if (m_banman) { addresses.erase(std::remove_if(addresses.begin(), addresses.end(), [this](const CAddress &addr) { return m_banman->IsDiscouraged( addr) || m_banman->IsBanned(addr); }), addresses.end()); } return addresses; } std::vector<CAddress> CConnman::GetAddresses(CNode &requestor, size_t max_addresses, size_t max_pct) { SOCKET socket; WITH_LOCK(requestor.cs_hSocket, socket = requestor.hSocket); auto local_socket_bytes = GetBindAddress(socket).GetAddrBytes(); uint64_t cache_id = GetDeterministicRandomizer(RANDOMIZER_ID_ADDRCACHE) .Write(requestor.addr.GetNetwork()) .Write(local_socket_bytes.data(), local_socket_bytes.size()) .Finalize(); const auto current_time = GetTime<std::chrono::microseconds>(); auto r = m_addr_response_caches.emplace(cache_id, CachedAddrResponse{}); CachedAddrResponse &cache_entry = r.first->second; // New CachedAddrResponse have expiration 0. if (cache_entry.m_cache_entry_expiration < current_time) { cache_entry.m_addrs_response_cache = GetAddresses(max_addresses, max_pct); // Choosing a proper cache lifetime is a trade-off between the privacy // leak minimization and the usefulness of ADDR responses to honest // users. // // Longer cache lifetime makes it more difficult for an attacker to // scrape enough AddrMan data to maliciously infer something useful. By // the time an attacker scraped enough AddrMan records, most of the // records should be old enough to not leak topology info by e.g. // analyzing real-time changes in timestamps. // // It takes only several hundred requests to scrape everything from an // AddrMan containing 100,000 nodes, so ~24 hours of cache lifetime // indeed makes the data less inferable by the time most of it could be // scraped (considering that timestamps are updated via ADDR // self-announcements and when nodes communicate). We also should be // robust to those attacks which may not require scraping *full* // victim's AddrMan (because even several timestamps of the same handful // of nodes may leak privacy). // // On the other hand, longer cache lifetime makes ADDR responses // outdated and less useful for an honest requestor, e.g. if most nodes // in the ADDR response are no longer active. // // However, the churn in the network is known to be rather low. Since we // consider nodes to be "terrible" (see IsTerrible()) if the timestamps // are older than 30 days, max. 24 hours of "penalty" due to cache // shouldn't make any meaningful difference in terms of the freshness of // the response. cache_entry.m_cache_entry_expiration = current_time + std::chrono::hours(21) + GetRandMillis(std::chrono::hours(6)); } return cache_entry.m_addrs_response_cache; } bool CConnman::AddNode(const std::string &strNode) { LOCK(cs_vAddedNodes); for (const std::string &it : vAddedNodes) { if (strNode == it) { return false; } } vAddedNodes.push_back(strNode); return true; } bool CConnman::RemoveAddedNode(const std::string &strNode) { LOCK(cs_vAddedNodes); for (std::vector<std::string>::iterator it = vAddedNodes.begin(); it != vAddedNodes.end(); ++it) { if (strNode == *it) { vAddedNodes.erase(it); return true; } } return false; } size_t CConnman::GetNodeCount(NumConnections flags) { LOCK(cs_vNodes); // Shortcut if we want total if (flags == CConnman::CONNECTIONS_ALL) { return vNodes.size(); } int nNum = 0; for (const auto &pnode : vNodes) { if (flags & (pnode->IsInboundConn() ? CONNECTIONS_IN : CONNECTIONS_OUT)) { nNum++; } } return nNum; } void CConnman::GetNodeStats(std::vector<CNodeStats> &vstats) { vstats.clear(); LOCK(cs_vNodes); vstats.reserve(vNodes.size()); for (CNode *pnode : vNodes) { vstats.emplace_back(); pnode->copyStats(vstats.back(), addrman.m_asmap); } } bool CConnman::DisconnectNode(const std::string &strNode) { LOCK(cs_vNodes); if (CNode *pnode = FindNode(strNode)) { pnode->fDisconnect = true; return true; } return false; } bool CConnman::DisconnectNode(const CSubNet &subnet) { bool disconnected = false; LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (subnet.Match(pnode->addr)) { pnode->fDisconnect = true; disconnected = true; } } return disconnected; } bool CConnman::DisconnectNode(const CNetAddr &addr) { return DisconnectNode(CSubNet(addr)); } bool CConnman::DisconnectNode(NodeId id) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (id == pnode->GetId()) { pnode->fDisconnect = true; return true; } } return false; } void CConnman::RecordBytesRecv(uint64_t bytes) { LOCK(cs_totalBytesRecv); nTotalBytesRecv += bytes; } void CConnman::RecordBytesSent(uint64_t bytes) { LOCK(cs_totalBytesSent); nTotalBytesSent += bytes; uint64_t now = GetTime(); if (nMaxOutboundCycleStartTime + nMaxOutboundTimeframe < now) { // timeframe expired, reset cycle nMaxOutboundCycleStartTime = now; nMaxOutboundTotalBytesSentInCycle = 0; } // TODO, exclude peers with download permission nMaxOutboundTotalBytesSentInCycle += bytes; } void CConnman::SetMaxOutboundTarget(uint64_t limit) { LOCK(cs_totalBytesSent); nMaxOutboundLimit = limit; } uint64_t CConnman::GetMaxOutboundTarget() { LOCK(cs_totalBytesSent); return nMaxOutboundLimit; } uint64_t CConnman::GetMaxOutboundTimeframe() { LOCK(cs_totalBytesSent); return nMaxOutboundTimeframe; } uint64_t CConnman::GetMaxOutboundTimeLeftInCycle() { LOCK(cs_totalBytesSent); if (nMaxOutboundLimit == 0) { return 0; } if (nMaxOutboundCycleStartTime == 0) { return nMaxOutboundTimeframe; } uint64_t cycleEndTime = nMaxOutboundCycleStartTime + nMaxOutboundTimeframe; uint64_t now = GetTime(); return (cycleEndTime < now) ? 0 : cycleEndTime - GetTime(); } void CConnman::SetMaxOutboundTimeframe(uint64_t timeframe) { LOCK(cs_totalBytesSent); if (nMaxOutboundTimeframe != timeframe) { // reset measure-cycle in case of changing the timeframe. nMaxOutboundCycleStartTime = GetTime(); } nMaxOutboundTimeframe = timeframe; } bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) { LOCK(cs_totalBytesSent); if (nMaxOutboundLimit == 0) { return false; } if (historicalBlockServingLimit) { // keep a large enough buffer to at least relay each block once. uint64_t timeLeftInCycle = GetMaxOutboundTimeLeftInCycle(); uint64_t buffer = timeLeftInCycle / 600 * ONE_MEGABYTE; if (buffer >= nMaxOutboundLimit || nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit - buffer) { return true; } } else if (nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit) { return true; } return false; } uint64_t CConnman::GetOutboundTargetBytesLeft() { LOCK(cs_totalBytesSent); if (nMaxOutboundLimit == 0) { return 0; } return (nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit) ? 0 : nMaxOutboundLimit - nMaxOutboundTotalBytesSentInCycle; } uint64_t CConnman::GetTotalBytesRecv() { LOCK(cs_totalBytesRecv); return nTotalBytesRecv; } uint64_t CConnman::GetTotalBytesSent() { LOCK(cs_totalBytesSent); return nTotalBytesSent; } ServiceFlags CConnman::GetLocalServices() const { return nLocalServices; } void CConnman::SetBestHeight(int height) { nBestHeight.store(height, std::memory_order_release); } int CConnman::GetBestHeight() const { return nBestHeight.load(std::memory_order_acquire); } unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } void CNode::AvalancheState::invsPolled(uint32_t count) { invCounters += count; } void CNode::AvalancheState::invsVoted(uint32_t count) { invCounters += uint64_t(count) << 32; } void CNode::AvalancheState::updateAvailabilityScore() { LOCK(cs_statistics); uint64_t windowInvCounters = invCounters.exchange(0); double previousScore = availabilityScore; uint32_t polls = windowInvCounters & std::numeric_limits<uint32_t>::max(); uint32_t votes = windowInvCounters >> 32; availabilityScore = AVALANCHE_STATISTICS_DECAY_FACTOR * (2 * votes - polls) + (1. - AVALANCHE_STATISTICS_DECAY_FACTOR) * previousScore; } double CNode::AvalancheState::getAvailabilityScore() const { // The score is set atomically so there is no need to lock the statistics // when reading. return availabilityScore; } CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, uint64_t nLocalExtraEntropyIn, const CAddress &addrBindIn, const std::string &addrNameIn, ConnectionType conn_type_in) : nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn), addrBind(addrBindIn), nKeyedNetGroup(nKeyedNetGroupIn), // Don't relay addr messages to peers that we connect to as // block-relay-only peers (to prevent adversaries from inferring these // links from addr traffic). id(idIn), nLocalHostNonce(nLocalHostNonceIn), nLocalExtraEntropy(nLocalExtraEntropyIn), m_conn_type(conn_type_in), nLocalServices(nLocalServicesIn), nMyStartingHeight(nMyStartingHeightIn) { hSocket = hSocketIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; hashContinue = BlockHash(); if (conn_type_in != ConnectionType::BLOCK_RELAY) { m_tx_relay = std::make_unique<TxRelay>(); } if (RelayAddrsWithConn()) { m_addr_known = std::make_unique<CRollingBloomFilter>(5000, 0.001); } // Don't relay proofs if avalanche is disabled if (isAvalancheEnabled(gArgs)) { m_proof_relay = std::make_unique<ProofRelay>(); } for (const std::string &msg : getAllNetMessageTypes()) { mapRecvBytesPerMsgCmd[msg] = 0; } mapRecvBytesPerMsgCmd[NET_MESSAGE_COMMAND_OTHER] = 0; if (fLogIPs) { LogPrint(BCLog::NET, "Added connection to %s peer=%d\n", addrName, id); } else { LogPrint(BCLog::NET, "Added connection peer=%d\n", id); } m_deserializer = std::make_unique<V1TransportDeserializer>( V1TransportDeserializer(GetConfig().GetChainParams().NetMagic(), SER_NETWORK, INIT_PROTO_VERSION)); m_serializer = std::make_unique<V1TransportSerializer>(V1TransportSerializer()); } CNode::~CNode() { CloseSocket(hSocket); } bool CConnman::NodeFullyConnected(const CNode *pnode) { return pnode && pnode->fSuccessfullyConnected && !pnode->fDisconnect; } void CConnman::PushMessage(CNode *pnode, CSerializedNetMsg &&msg) { size_t nMessageSize = msg.data.size(); LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", SanitizeString(msg.m_type), nMessageSize, pnode->GetId()); // make sure we use the appropriate network transport format std::vector<uint8_t> serializedHeader; pnode->m_serializer->prepareForTransport(*config, msg, serializedHeader); size_t nTotalSize = nMessageSize + serializedHeader.size(); size_t nBytesSent = 0; { LOCK(pnode->cs_vSend); bool optimisticSend(pnode->vSendMsg.empty()); // log total amount of bytes per message type pnode->mapSendBytesPerMsgCmd[msg.m_type] += nTotalSize; pnode->nSendSize += nTotalSize; if (pnode->nSendSize > nSendBufferMaxSize) { pnode->fPauseSend = true; } pnode->vSendMsg.push_back(std::move(serializedHeader)); if (nMessageSize) { pnode->vSendMsg.push_back(std::move(msg.data)); } // If write queue empty, attempt "optimistic write" if (optimisticSend == true) { nBytesSent = SocketSendData(pnode); } } if (nBytesSent) { RecordBytesSent(nBytesSent); } } bool CConnman::ForNode(NodeId id, std::function<bool(CNode *pnode)> func) { CNode *found = nullptr; LOCK(cs_vNodes); for (auto &&pnode : vNodes) { if (pnode->GetId() == id) { found = pnode; break; } } return found != nullptr && NodeFullyConnected(found) && func(found); } int64_t CConnman::PoissonNextSendInbound(int64_t now, int average_interval_seconds) { if (m_next_send_inv_to_incoming < now) { // If this function were called from multiple threads simultaneously // it would be possible that both update the next send variable, and // return a different result to their caller. This is not possible in // practice as only the net processing thread invokes this function. m_next_send_inv_to_incoming = PoissonNextSend(now, average_interval_seconds); } return m_next_send_inv_to_incoming; } int64_t PoissonNextSend(int64_t now, int average_interval_seconds) { return now + int64_t(log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */) * average_interval_seconds * -1000000.0 + 0.5); } CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const { return CSipHasher(nSeed0, nSeed1).Write(id); } uint64_t CConnman::CalculateKeyedNetGroup(const CAddress &ad) const { std::vector<uint8_t> vchNetGroup(ad.GetGroup(addrman.m_asmap)); return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP) .Write(vchNetGroup.data(), vchNetGroup.size()) .Finalize(); } /** * This function convert MaxBlockSize from byte to * MB with a decimal precision one digit rounded down * E.g. * 1660000 -> 1.6 * 2010000 -> 2.0 * 1000000 -> 1.0 * 230000 -> 0.2 * 50000 -> 0.0 * * NB behavior for EB<1MB not standardized yet still * the function applies the same algo used for * EB greater or equal to 1MB */ std::string getSubVersionEB(uint64_t MaxBlockSize) { // Prepare EB string we are going to add to SubVer: // 1) translate from byte to MB and convert to string // 2) limit the EB string to the first decimal digit (floored) std::stringstream ebMBs; ebMBs << (MaxBlockSize / (ONE_MEGABYTE / 10)); std::string eb = ebMBs.str(); eb.insert(eb.size() - 1, ".", 1); if (eb.substr(0, 1) == ".") { eb = "0" + eb; } return eb; } std::string userAgent(const Config &config) { // format excessive blocksize value std::string eb = getSubVersionEB(config.GetMaxBlockSize()); std::vector<std::string> uacomments; uacomments.push_back("EB" + eb); // Comments are checked for char compliance at startup, it is safe to add // them to the user agent string for (const std::string &cmt : gArgs.GetArgs("-uacomment")) { uacomments.push_back(cmt); } const std::string client_name = gArgs.GetArg("-uaclientname", CLIENT_NAME); const std::string client_version = gArgs.GetArg("-uaclientversion", FormatVersion(CLIENT_VERSION)); // Size compliance is checked at startup, it is safe to not check it again return FormatUserAgent(client_name, client_version, uacomments); } diff --git a/src/net.h b/src/net.h index 22b07e31f..485b7d2c6 100644 --- a/src/net.h +++ b/src/net.h @@ -1,1388 +1,1391 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_NET_H #define BITCOIN_NET_H #include <addrdb.h> #include <addrman.h> #include <amount.h> #include <avalanche/delegation.h> #include <bloom.h> #include <chainparams.h> #include <compat.h> #include <crypto/siphash.h> #include <hash.h> #include <net_permissions.h> #include <netaddress.h> #include <nodeid.h> #include <protocol.h> #include <random.h> #include <streams.h> #include <sync.h> #include <threadinterrupt.h> #include <uint256.h> #include <util/check.h> #include <validation.h> // For cs_main #include <atomic> #include <condition_variable> #include <cstdint> #include <deque> #include <map> #include <memory> #include <thread> #include <vector> #ifndef WIN32 #include <arpa/inet.h> #endif class BanMan; class Config; class CNode; class CScheduler; struct bilingual_str; /** Default for -whitelistrelay. */ static const bool DEFAULT_WHITELISTRELAY = true; /** Default for -whitelistforcerelay. */ static const bool DEFAULT_WHITELISTFORCERELAY = false; /** * Time after which to disconnect, after waiting for a ping response (or * inactivity). */ static const int TIMEOUT_INTERVAL = 20 * 60; /** Run the feeler connection loop once every 2 minutes or 120 seconds. **/ static const int FEELER_INTERVAL = 120; /** * The maximum number of addresses from our addrman to return in response to * a getaddr message. */ static constexpr size_t MAX_ADDR_TO_SEND = 1000; /** Maximum length of the user agent string in `version` message */ static const unsigned int MAX_SUBVERSION_LENGTH = 256; /** * Maximum number of automatic outgoing nodes over which we'll relay everything * (blocks, tx, addrs, etc) */ static const int MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 8; /** Maximum number of addnode outgoing nodes */ static const int MAX_ADDNODE_CONNECTIONS = 8; /** Maximum number of block-relay-only outgoing connections */ static const int MAX_BLOCK_RELAY_ONLY_CONNECTIONS = 2; /** Maximum number of feeler connections */ static const int MAX_FEELER_CONNECTIONS = 1; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ #ifdef USE_UPNP static const bool DEFAULT_UPNP = USE_UPNP; #else static const bool DEFAULT_UPNP = false; #endif /** * The maximum number of peer connections to maintain. * This quantity might not be reachable on some systems, especially on platforms * that do not provide a working poll() interface. */ static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 4096; /** The default for -maxuploadtarget. 0 = Unlimited */ static const uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0; /** The default timeframe for -maxuploadtarget. 1 day. */ static const uint64_t MAX_UPLOAD_TIMEFRAME = 60 * 60 * 24; /** Default for blocks only*/ static const bool DEFAULT_BLOCKSONLY = false; /** -peertimeout default */ static const int64_t DEFAULT_PEER_CONNECT_TIMEOUT = 60; static const bool DEFAULT_FORCEDNSSEED = false; static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000; static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000; /** Refresh period for the avalanche statistics computation */ static constexpr std::chrono::minutes AVALANCHE_STATISTICS_REFRESH_PERIOD{10}; /** Time constant for the avalanche statistics computation */ static constexpr std::chrono::minutes AVALANCHE_STATISTICS_TIME_CONSTANT{10}; /** * Pre-computed decay factor for the avalanche statistics computation. * There is currently no constexpr variant of std::exp, so use a const. */ static const double AVALANCHE_STATISTICS_DECAY_FACTOR = 1. - std::exp(-1. * AVALANCHE_STATISTICS_REFRESH_PERIOD.count() / AVALANCHE_STATISTICS_TIME_CONSTANT.count()); struct AddedNodeInfo { std::string strAddedNode; CService resolvedAddress; bool fConnected; bool fInbound; }; struct CNodeStats; class CClientUIInterface; struct CSerializedNetMsg { CSerializedNetMsg() = default; CSerializedNetMsg(CSerializedNetMsg &&) = default; CSerializedNetMsg &operator=(CSerializedNetMsg &&) = default; // No copying, only moves. CSerializedNetMsg(const CSerializedNetMsg &msg) = delete; CSerializedNetMsg &operator=(const CSerializedNetMsg &) = delete; std::vector<uint8_t> data; std::string m_type; }; const std::vector<std::string> CONNECTION_TYPE_DOC{ "outbound-full-relay (default automatic connections)", "block-relay-only (does not relay transactions or addresses)", "inbound (initiated by the peer)", "manual (added via addnode RPC or -addnode/-connect configuration options)", "addr-fetch (short-lived automatic connection for soliciting addresses)", "feeler (short-lived automatic connection for testing addresses)"}; /** * Different types of connections to a peer. This enum encapsulates the * information we have available at the time of opening or accepting the * connection. Aside from INBOUND, all types are initiated by us. */ enum class ConnectionType { /** * Inbound connections are those initiated by a peer. This is the only * property we know at the time of connection, until P2P messages are * exchanged. */ INBOUND, /** * These are the default connections that we use to connect with the * network. There is no restriction on what is relayed- by default we relay * blocks, addresses & transactions. We automatically attempt to open * MAX_OUTBOUND_FULL_RELAY_CONNECTIONS using addresses from our AddrMan. */ OUTBOUND_FULL_RELAY, /** * We open manual connections to addresses that users explicitly inputted * via the addnode RPC, or the -connect command line argument. Even if a * manual connection is misbehaving, we do not automatically disconnect or * add it to our discouragement filter. */ MANUAL, /** * Feeler connections are short-lived connections made to check that a node * is alive. They can be useful for: * - test-before-evict: if one of the peers is considered for eviction from * our AddrMan because another peer is mapped to the same slot in the * tried table, evict only if this longer-known peer is offline. * - move node addresses from New to Tried table, so that we have more * connectable addresses in our AddrMan. * Note that in the literature ("Eclipse Attacks on Bitcoin’s Peer-to-Peer * Network") only the latter feature is referred to as "feeler connections", * although in our codebase feeler connections encompass test-before-evict * as well. * We make these connections approximately every FEELER_INTERVAL: * first we resolve previously found collisions if they exist * (test-before-evict), otherwise connect to a node from the new table. */ FEELER, /** * We use block-relay-only connections to help prevent against partition * attacks. By not relaying transactions or addresses, these connections * are harder to detect by a third party, thus helping obfuscate the * network topology. We automatically attempt to open * MAX_BLOCK_RELAY_ONLY_CONNECTIONS using addresses from our AddrMan. */ BLOCK_RELAY, /** * AddrFetch connections are short lived connections used to solicit * addresses from peers. These are initiated to addresses submitted via the * -seednode command line argument, or under certain conditions when the * AddrMan is empty. */ ADDR_FETCH, }; namespace { struct CConnmanTest; } class NetEventsInterface; class CConnman { public: enum NumConnections { CONNECTIONS_NONE = 0, CONNECTIONS_IN = (1U << 0), CONNECTIONS_OUT = (1U << 1), CONNECTIONS_ALL = (CONNECTIONS_IN | CONNECTIONS_OUT), }; struct Options { ServiceFlags nLocalServices = NODE_NONE; int nMaxConnections = 0; int m_max_outbound_full_relay = 0; int m_max_outbound_block_relay = 0; int nMaxAddnode = 0; int nMaxFeeler = 0; int nBestHeight = 0; CClientUIInterface *uiInterface = nullptr; NetEventsInterface *m_msgproc = nullptr; BanMan *m_banman = nullptr; unsigned int nSendBufferMaxSize = 0; unsigned int nReceiveFloodSize = 0; uint64_t nMaxOutboundTimeframe = 0; uint64_t nMaxOutboundLimit = 0; int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT; std::vector<std::string> vSeedNodes; std::vector<NetWhitelistPermissions> vWhitelistedRange; std::vector<NetWhitebindPermissions> vWhiteBinds; std::vector<CService> vBinds; + std::vector<CService> onion_binds; bool m_use_addrman_outgoing = true; std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_added_nodes; std::vector<bool> m_asmap; }; void Init(const Options &connOptions) { nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing; nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; { // Lock cs_main to prevent a potential race with the peer validation // logic thread. LOCK(::cs_main); m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay; m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler; } nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; m_banman = connOptions.m_banman; m_msgproc = connOptions.m_msgproc; nSendBufferMaxSize = connOptions.nSendBufferMaxSize; nReceiveFloodSize = connOptions.nReceiveFloodSize; m_peer_connect_timeout = connOptions.m_peer_connect_timeout; { LOCK(cs_totalBytesSent); nMaxOutboundTimeframe = connOptions.nMaxOutboundTimeframe; nMaxOutboundLimit = connOptions.nMaxOutboundLimit; } vWhitelistedRange = connOptions.vWhitelistedRange; { LOCK(cs_vAddedNodes); vAddedNodes = connOptions.m_added_nodes; } } CConnman(const Config &configIn, uint64_t seed0, uint64_t seed1, bool network_active = true); ~CConnman(); bool Start(CScheduler &scheduler, const Options &options); void StopThreads(); void StopNodes(); void Stop() { StopThreads(); StopNodes(); }; void Interrupt(); bool GetNetworkActive() const { return fNetworkActive; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; void SetNetworkActive(bool active); void OpenNetworkConnection(const CAddress &addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *strDest, ConnectionType conn_type); bool CheckIncomingNonce(uint64_t nonce); bool ForNode(NodeId id, std::function<bool(CNode *pnode)> func); void PushMessage(CNode *pnode, CSerializedNetMsg &&msg); using NodeFn = std::function<void(CNode *)>; void ForEachNode(const NodeFn &func) { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { func(node); } } }; void ForEachNode(const NodeFn &func) const { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { func(node); } } }; template <typename Callable, typename CallableAfter> void ForEachNodeThen(Callable &&pre, CallableAfter &&post) { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { pre(node); } } post(); }; template <typename Callable, typename CallableAfter> void ForEachNodeThen(Callable &&pre, CallableAfter &&post) const { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { pre(node); } } post(); }; // Addrman functions void SetServices(const CService &addr, ServiceFlags nServices); void MarkAddressGood(const CAddress &addr); bool AddNewAddresses(const std::vector<CAddress> &vAddr, const CAddress &addrFrom, int64_t nTimePenalty = 0); std::vector<CAddress> GetAddresses(size_t max_addresses, size_t max_pct); /** * Cache is used to minimize topology leaks, so it should * be used for all non-trusted calls, for example, p2p. * A non-malicious call (from RPC or a peer with addr permission) should * call the function without a parameter to avoid using the cache. */ std::vector<CAddress> GetAddresses(CNode &requestor, size_t max_addresses, size_t max_pct); // This allows temporarily exceeding m_max_outbound_full_relay, with the // goal of finding a peer that is better than all our current peers. void SetTryNewOutboundPeer(bool flag); bool GetTryNewOutboundPeer(); // Return the number of outbound peers we have in excess of our target (eg, // if we previously called SetTryNewOutboundPeer(true), and have since set // to false, we may have extra peers that we wish to disconnect). This may // return a value less than (num_outbound_connections - num_outbound_slots) // in cases where some outbound connections are not yet fully connected, or // not yet fully disconnected. int GetExtraOutboundCount(); bool AddNode(const std::string &node); bool RemoveAddedNode(const std::string &node); std::vector<AddedNodeInfo> GetAddedNodeInfo(); size_t GetNodeCount(NumConnections num); void GetNodeStats(std::vector<CNodeStats> &vstats); bool DisconnectNode(const std::string &node); bool DisconnectNode(const CSubNet &subnet); bool DisconnectNode(const CNetAddr &addr); bool DisconnectNode(NodeId id); //! Used to convey which local services we are offering peers during node //! connection. //! //! The data returned by this is used in CNode construction, //! which is used to advertise which services we are offering //! that peer during `net_processing.cpp:PushNodeVersion()`. ServiceFlags GetLocalServices() const; //! set the max outbound target in bytes. void SetMaxOutboundTarget(uint64_t limit); uint64_t GetMaxOutboundTarget(); //! set the timeframe for the max outbound target. void SetMaxOutboundTimeframe(uint64_t timeframe); uint64_t GetMaxOutboundTimeframe(); //! check if the outbound target is reached. If param //! historicalBlockServingLimit is set true, the function will response true //! if the limit for serving historical blocks has been reached. bool OutboundTargetReached(bool historicalBlockServingLimit); //! response the bytes left in the current max outbound cycle in case of no //! limit, it will always response 0 uint64_t GetOutboundTargetBytesLeft(); //! response the time in second left in the current max outbound cycle in //! case of no limit, it will always response 0 uint64_t GetMaxOutboundTimeLeftInCycle(); uint64_t GetTotalBytesRecv(); uint64_t GetTotalBytesSent(); void SetBestHeight(int height); int GetBestHeight() const; /** Get a unique deterministic randomizer. */ CSipHasher GetDeterministicRandomizer(uint64_t id) const; unsigned int GetReceiveFloodSize() const; void WakeMessageHandler(); /** * Attempts to obfuscate tx time through exponentially distributed emitting. * Works assuming that a single interval is used. * Variable intervals will result in privacy decrease. */ int64_t PoissonNextSendInbound(int64_t now, int average_interval_seconds); void SetAsmap(std::vector<bool> asmap) { addrman.m_asmap = std::move(asmap); } private: struct ListenSocket { public: SOCKET socket; inline void AddSocketPermissionFlags(NetPermissionFlags &flags) const { NetPermissions::AddFlag(flags, m_permissions); } ListenSocket(SOCKET socket_, NetPermissionFlags permissions_) : socket(socket_), m_permissions(permissions_) {} private: NetPermissionFlags m_permissions; }; bool BindListenPort(const CService &bindAddr, bilingual_str &strError, NetPermissionFlags permissions); bool Bind(const CService &addr, unsigned int flags, NetPermissionFlags permissions); bool InitBinds(const std::vector<CService> &binds, - const std::vector<NetWhitebindPermissions> &whiteBinds); + const std::vector<NetWhitebindPermissions> &whiteBinds, + const std::vector<CService> &onion_binds); + void ThreadOpenAddedConnections(); void AddAddrFetch(const std::string &strDest); void ProcessAddrFetch(); void ThreadOpenConnections(std::vector<std::string> connect); void ThreadMessageHandler(); void AcceptConnection(const ListenSocket &hListenSocket); void DisconnectNodes(); void NotifyNumConnectionsChanged(); void InactivityCheck(CNode *pnode); bool GenerateSelectSet(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set); void SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set); void SocketHandler(); void ThreadSocketHandler(); void ThreadDNSAddressSeed(); uint64_t CalculateKeyedNetGroup(const CAddress &ad) const; CNode *FindNode(const CNetAddr &ip); CNode *FindNode(const CSubNet &subNet); CNode *FindNode(const std::string &addrName); CNode *FindNode(const CService &addr); bool AttemptToEvictConnection(); CNode *ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, ConnectionType conn_type); void AddWhitelistPermissionFlags(NetPermissionFlags &flags, const CNetAddr &addr) const; void DeleteNode(CNode *pnode); NodeId GetNewNodeId(); size_t SocketSendData(CNode *pnode) const; void DumpAddresses(); // Network stats void RecordBytesRecv(uint64_t bytes); void RecordBytesSent(uint64_t bytes); // Whether the node should be passed out in ForEach* callbacks static bool NodeFullyConnected(const CNode *pnode); const Config *config; // Network usage totals RecursiveMutex cs_totalBytesRecv; RecursiveMutex cs_totalBytesSent; uint64_t nTotalBytesRecv GUARDED_BY(cs_totalBytesRecv){0}; uint64_t nTotalBytesSent GUARDED_BY(cs_totalBytesSent){0}; // outbound limit & stats uint64_t nMaxOutboundTotalBytesSentInCycle GUARDED_BY(cs_totalBytesSent); uint64_t nMaxOutboundCycleStartTime GUARDED_BY(cs_totalBytesSent); uint64_t nMaxOutboundLimit GUARDED_BY(cs_totalBytesSent); uint64_t nMaxOutboundTimeframe GUARDED_BY(cs_totalBytesSent); // P2P timeout in seconds int64_t m_peer_connect_timeout; // Whitelisted ranges. Any node connecting from these is automatically // whitelisted (as well as those connecting to whitelisted binds). std::vector<NetWhitelistPermissions> vWhitelistedRange; unsigned int nSendBufferMaxSize{0}; unsigned int nReceiveFloodSize{0}; std::vector<ListenSocket> vhListenSocket; std::atomic<bool> fNetworkActive{true}; bool fAddressesInitialized{false}; CAddrMan addrman; std::deque<std::string> m_addr_fetches GUARDED_BY(m_addr_fetches_mutex); RecursiveMutex m_addr_fetches_mutex; std::vector<std::string> vAddedNodes GUARDED_BY(cs_vAddedNodes); RecursiveMutex cs_vAddedNodes; std::vector<CNode *> vNodes GUARDED_BY(cs_vNodes); std::list<CNode *> vNodesDisconnected; mutable RecursiveMutex cs_vNodes; std::atomic<NodeId> nLastNodeId{0}; unsigned int nPrevNodeCount{0}; /** * Cache responses to addr requests to minimize privacy leak. * Attack example: scraping addrs in real-time may allow an attacker * to infer new connections of the victim by detecting new records * with fresh timestamps (per self-announcement). */ struct CachedAddrResponse { std::vector<CAddress> m_addrs_response_cache; std::chrono::microseconds m_cache_entry_expiration{0}; }; /** * Addr responses stored in different caches * per (network, local socket) prevent cross-network node identification. * If a node for example is multi-homed under Tor and IPv6, * a single cache (or no cache at all) would let an attacker * to easily detect that it is the same node by comparing responses. * Indexing by local socket prevents leakage when a node has multiple * listening addresses on the same network. * * The used memory equals to 1000 CAddress records (or around 40 bytes) per * distinct Network (up to 5) we have/had an inbound peer from, * resulting in at most ~196 KB. Every separate local socket may * add up to ~196 KB extra. */ std::map<uint64_t, CachedAddrResponse> m_addr_response_caches; /** * Services this instance offers. * * This data is replicated in each CNode instance we create during peer * connection (in ConnectNode()) under a member also called * nLocalServices. * * This data is not marked const, but after being set it should not * change. See the note in CNode::nLocalServices documentation. * * \sa CNode::nLocalServices */ ServiceFlags nLocalServices; std::unique_ptr<CSemaphore> semOutbound; std::unique_ptr<CSemaphore> semAddnode; int nMaxConnections; // How many full-relay (tx, block, addr) outbound peers we want int m_max_outbound_full_relay; // How many block-relay only outbound peers we want // We do not relay tx or addr messages with these peers int m_max_outbound_block_relay; int nMaxAddnode; int nMaxFeeler; int m_max_outbound; bool m_use_addrman_outgoing; std::atomic<int> nBestHeight; CClientUIInterface *clientInterface; NetEventsInterface *m_msgproc; /** * Pointer to this node's banman. May be nullptr - check existence before * dereferencing. */ BanMan *m_banman; /** SipHasher seeds for deterministic randomness */ const uint64_t nSeed0, nSeed1; /** flag for waking the message processor. */ bool fMsgProcWake GUARDED_BY(mutexMsgProc); std::condition_variable condMsgProc; Mutex mutexMsgProc; std::atomic<bool> flagInterruptMsgProc{false}; CThreadInterrupt interruptNet; std::thread threadDNSAddressSeed; std::thread threadSocketHandler; std::thread threadOpenAddedConnections; std::thread threadOpenConnections; std::thread threadMessageHandler; /** * flag for deciding to connect to an extra outbound peer, in excess of * m_max_outbound_full_relay. This takes the place of a feeler connection. */ std::atomic_bool m_try_another_outbound_peer; std::atomic<int64_t> m_next_send_inv_to_incoming{0}; friend struct ::CConnmanTest; friend struct ConnmanTestMsg; }; void Discover(); void StartMapPort(); void InterruptMapPort(); void StopMapPort(); uint16_t GetListenPort(); /** * Interface for message handling */ class NetEventsInterface { public: virtual bool ProcessMessages(const Config &config, CNode *pnode, std::atomic<bool> &interrupt) = 0; virtual bool SendMessages(const Config &config, CNode *pnode, std::atomic<bool> &interrupt) = 0; virtual void InitializeNode(const Config &config, CNode *pnode) = 0; virtual void FinalizeNode(const Config &config, NodeId id, bool &update_connection_time) = 0; protected: /** * Protected destructor so that instances can only be deleted by derived * classes. If that restriction is no longer desired, this should be made * public and virtual. */ ~NetEventsInterface() = default; }; enum { // unknown LOCAL_NONE, // address a local interface listens on LOCAL_IF, // address explicit bound to LOCAL_BIND, // address reported by UPnP LOCAL_UPNP, // address explicitly specified (-externalip=) LOCAL_MANUAL, LOCAL_MAX }; bool IsPeerAddrLocalGood(CNode *pnode); void AdvertiseLocal(CNode *pnode); /** * Mark a network as reachable or unreachable (no automatic connects to it) * @note Networks are reachable by default */ void SetReachable(enum Network net, bool reachable); /** @returns true if the network is reachable, false otherwise */ bool IsReachable(enum Network net); /** @returns true if the address is in a reachable network, false otherwise */ bool IsReachable(const CNetAddr &addr); bool AddLocal(const CService &addr, int nScore = LOCAL_NONE); bool AddLocal(const CNetAddr &addr, int nScore = LOCAL_NONE); void RemoveLocal(const CService &addr); bool SeenLocal(const CService &addr); bool IsLocal(const CService &addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr); CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices); extern bool fDiscover; extern bool fListen; extern bool g_relay_txes; struct LocalServiceInfo { int nScore; int nPort; }; extern RecursiveMutex cs_mapLocalHost; extern std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(cs_mapLocalHost); extern const std::string NET_MESSAGE_COMMAND_OTHER; // Command, total bytes typedef std::map<std::string, uint64_t> mapMsgCmdSize; /** * POD that contains various stats about a node. * Usually constructed from CConman::GetNodeStats. Stats are filled from the * node using CNode::copyStats. */ struct CNodeStats { NodeId nodeid; ServiceFlags nServices; bool fRelayTxes; int64_t nLastSend; int64_t nLastRecv; int64_t nLastTXTime; int64_t nLastProofTime; int64_t nLastBlockTime; int64_t nTimeConnected; int64_t nTimeOffset; std::string addrName; int nVersion; std::string cleanSubVer; bool fInbound; bool m_manual_connection; int nStartingHeight; uint64_t nSendBytes; mapMsgCmdSize mapSendBytesPerMsgCmd; uint64_t nRecvBytes; mapMsgCmdSize mapRecvBytesPerMsgCmd; NetPermissionFlags m_permissionFlags; bool m_legacyWhitelisted; int64_t m_ping_usec; int64_t m_ping_wait_usec; int64_t m_min_ping_usec; Amount minFeeFilter; // Our address, as reported by the peer std::string addrLocal; // Address of this peer CAddress addr; // Bind address of our side of the connection CAddress addrBind; uint32_t m_mapped_as; std::string m_conn_type_string; }; /** * Transport protocol agnostic message container. * Ideally it should only contain receive time, payload, * command and size. */ class CNetMessage { public: //! received message data CDataStream m_recv; //! time of message receipt std::chrono::microseconds m_time{0}; bool m_valid_netmagic = false; bool m_valid_header = false; bool m_valid_checksum = false; //! size of the payload uint32_t m_message_size{0}; //! used wire size of the message (including header/checksum) uint32_t m_raw_message_size{0}; std::string m_command; CNetMessage(CDataStream &&recv_in) : m_recv(std::move(recv_in)) {} void SetVersion(int nVersionIn) { m_recv.SetVersion(nVersionIn); } }; /** * The TransportDeserializer takes care of holding and deserializing the * network receive buffer. It can deserialize the network buffer into a * transport protocol agnostic CNetMessage (command & payload) */ class TransportDeserializer { public: // returns true if the current deserialization is complete virtual bool Complete() const = 0; // set the serialization context version virtual void SetVersion(int version) = 0; // read and deserialize data virtual int Read(const Config &config, const char *data, uint32_t bytes) = 0; // decomposes a message from the context virtual CNetMessage GetMessage(const Config &config, std::chrono::microseconds time) = 0; virtual ~TransportDeserializer() {} }; class V1TransportDeserializer final : public TransportDeserializer { private: mutable CHash256 hasher; mutable uint256 data_hash; // Parsing header (false) or data (true) bool in_data; // Partially received header. CDataStream hdrbuf; // Complete header. CMessageHeader hdr; // Received message data. CDataStream vRecv; uint32_t nHdrPos; uint32_t nDataPos; const uint256 &GetMessageHash() const; int readHeader(const Config &config, const char *pch, uint32_t nBytes); int readData(const char *pch, uint32_t nBytes); void Reset() { vRecv.clear(); hdrbuf.clear(); hdrbuf.resize(24); in_data = false; nHdrPos = 0; nDataPos = 0; data_hash.SetNull(); hasher.Reset(); } public: V1TransportDeserializer( const CMessageHeader::MessageMagic &pchMessageStartIn, int nTypeIn, int nVersionIn) : hdrbuf(nTypeIn, nVersionIn), hdr(pchMessageStartIn), vRecv(nTypeIn, nVersionIn) { Reset(); } bool Complete() const override { if (!in_data) { return false; } return (hdr.nMessageSize == nDataPos); } void SetVersion(int nVersionIn) override { hdrbuf.SetVersion(nVersionIn); vRecv.SetVersion(nVersionIn); } int Read(const Config &config, const char *pch, uint32_t nBytes) override { int ret = in_data ? readData(pch, nBytes) : readHeader(config, pch, nBytes); if (ret < 0) { Reset(); } return ret; } CNetMessage GetMessage(const Config &config, std::chrono::microseconds time) override; }; /** * The TransportSerializer prepares messages for the network transport */ class TransportSerializer { public: // prepare message for transport (header construction, error-correction // computation, payload encryption, etc.) virtual void prepareForTransport(const Config &config, CSerializedNetMsg &msg, std::vector<uint8_t> &header) = 0; virtual ~TransportSerializer() {} }; class V1TransportSerializer : public TransportSerializer { public: void prepareForTransport(const Config &config, CSerializedNetMsg &msg, std::vector<uint8_t> &header) override; }; /** Information about a peer */ class CNode { friend class CConnman; friend struct ConnmanTestMsg; public: std::unique_ptr<TransportDeserializer> m_deserializer; std::unique_ptr<TransportSerializer> m_serializer; // socket std::atomic<ServiceFlags> nServices{NODE_NONE}; SOCKET hSocket GUARDED_BY(cs_hSocket); // Total size of all vSendMsg entries. size_t nSendSize{0}; // Offset inside the first vSendMsg already sent. size_t nSendOffset{0}; uint64_t nSendBytes GUARDED_BY(cs_vSend){0}; std::deque<std::vector<uint8_t>> vSendMsg GUARDED_BY(cs_vSend); Mutex cs_vSend; Mutex cs_hSocket; Mutex cs_vRecv; RecursiveMutex cs_vProcessMsg; std::list<CNetMessage> vProcessMsg GUARDED_BY(cs_vProcessMsg); size_t nProcessQueueSize{0}; RecursiveMutex cs_sendProcessing; std::deque<CInv> vRecvGetData; uint64_t nRecvBytes GUARDED_BY(cs_vRecv){0}; std::atomic<int64_t> nLastSend{0}; std::atomic<int64_t> nLastRecv{0}; const int64_t nTimeConnected; std::atomic<int64_t> nTimeOffset{0}; // Address of this peer const CAddress addr; // Bind address of our side of the connection const CAddress addrBind; std::atomic<int> nVersion{0}; // The nonce provided by the remote host. uint64_t nRemoteHostNonce{0}; // The extra entropy provided by the remote host. uint64_t nRemoteExtraEntropy{0}; /** * cleanSubVer is a sanitized string of the user agent byte array we read * from the wire. This cleaned string can safely be logged or displayed. */ RecursiveMutex cs_SubVer; std::string cleanSubVer GUARDED_BY(cs_SubVer){}; // This peer is preferred for eviction. bool m_prefer_evict{false}; bool HasPermission(NetPermissionFlags permission) const { return NetPermissions::HasFlag(m_permissionFlags, permission); } // This boolean is unusued in actual processing, only present for backward // compatibility at RPC/QT level bool m_legacyWhitelisted{false}; // set by version message bool fClient{false}; // after BIP159, set by version message bool m_limited_node{false}; /** * Whether the peer has signaled support for receiving ADDRv2 (BIP155) * messages, implying a preference to receive ADDRv2 instead of ADDR ones. */ std::atomic_bool m_wants_addrv2{false}; std::atomic_bool fSuccessfullyConnected{false}; // Setting fDisconnect to true will cause the node to be disconnected the // next time DisconnectNodes() runs std::atomic_bool fDisconnect{false}; bool fSentAddr{false}; CSemaphoreGrant grantOutbound; std::atomic<int> nRefCount{0}; const uint64_t nKeyedNetGroup; std::atomic_bool fPauseRecv{false}; std::atomic_bool fPauseSend{false}; bool IsOutboundOrBlockRelayConn() const { switch (m_conn_type) { case ConnectionType::OUTBOUND_FULL_RELAY: case ConnectionType::BLOCK_RELAY: return true; case ConnectionType::INBOUND: case ConnectionType::MANUAL: case ConnectionType::ADDR_FETCH: case ConnectionType::FEELER: return false; } // no default case, so the compiler can warn about missing cases assert(false); } bool IsFullOutboundConn() const { return m_conn_type == ConnectionType::OUTBOUND_FULL_RELAY; } bool IsManualConn() const { return m_conn_type == ConnectionType::MANUAL; } bool IsBlockOnlyConn() const { return m_conn_type == ConnectionType::BLOCK_RELAY; } bool IsFeelerConn() const { return m_conn_type == ConnectionType::FEELER; } bool IsAddrFetchConn() const { return m_conn_type == ConnectionType::ADDR_FETCH; } bool IsInboundConn() const { return m_conn_type == ConnectionType::INBOUND; } /* Whether we send addr messages over this connection */ bool RelayAddrsWithConn() const { return m_conn_type != ConnectionType::BLOCK_RELAY; } bool ExpectServicesFromConn() const { switch (m_conn_type) { case ConnectionType::INBOUND: case ConnectionType::MANUAL: case ConnectionType::FEELER: return false; case ConnectionType::OUTBOUND_FULL_RELAY: case ConnectionType::BLOCK_RELAY: case ConnectionType::ADDR_FETCH: return true; } // no default case, so the compiler can warn about missing cases assert(false); } protected: mapMsgCmdSize mapSendBytesPerMsgCmd; mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv); public: BlockHash hashContinue; std::atomic<int> nStartingHeight{-1}; // flood relay std::vector<CAddress> vAddrToSend; std::unique_ptr<CRollingBloomFilter> m_addr_known = nullptr; bool fGetAddr{false}; std::chrono::microseconds m_next_addr_send GUARDED_BY(cs_sendProcessing){0}; std::chrono::microseconds m_next_local_addr_send GUARDED_BY(cs_sendProcessing){0}; // List of block ids we still have to announce. // There is no final sorting before sending, as they are always sent // immediately and in the order requested. std::vector<BlockHash> vInventoryBlockToSend GUARDED_BY(cs_inventory); Mutex cs_inventory; struct TxRelay { mutable RecursiveMutex cs_filter; // We use fRelayTxes for two purposes - // a) it allows us to not relay tx invs before receiving the peer's // version message. // b) the peer may tell us in its version message that we should not // relay tx invs unless it loads a bloom filter. bool fRelayTxes GUARDED_BY(cs_filter){false}; std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter) GUARDED_BY(cs_filter){nullptr}; mutable RecursiveMutex cs_tx_inventory; CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_tx_inventory){ 50000, 0.000001}; // Set of transaction ids we still have to announce. // They are sorted by the mempool before relay, so the order is not // important. std::set<TxId> setInventoryTxToSend GUARDED_BY(cs_tx_inventory); // Used for BIP35 mempool sending bool fSendMempool GUARDED_BY(cs_tx_inventory){false}; // Last time a "MEMPOOL" request was serviced. std::atomic<std::chrono::seconds> m_last_mempool_req{ std::chrono::seconds{0}}; std::chrono::microseconds nNextInvSend{0}; RecursiveMutex cs_feeFilter; // Minimum fee rate with which to filter inv's to this node Amount minFeeFilter GUARDED_BY(cs_feeFilter){Amount::zero()}; Amount lastSentFeeFilter{Amount::zero()}; int64_t nextSendTimeFeeFilter{0}; }; // m_tx_relay == nullptr if we're not relaying transactions with this peer std::unique_ptr<TxRelay> m_tx_relay; struct ProofRelay { mutable RecursiveMutex cs_proof_inventory; std::set<avalanche::ProofId> setInventoryProofToSend GUARDED_BY(cs_proof_inventory); // Prevent sending proof invs if the peer already knows about them CRollingBloomFilter filterProofKnown GUARDED_BY(cs_proof_inventory){ 10000, 0.000001}; std::chrono::microseconds nextInvSend{0}; }; // m_proof_relay == nullptr if we're not relaying proofs with this peer std::unique_ptr<ProofRelay> m_proof_relay; class AvalancheState { /** * The inventories polled and voted couters since last score * computation, stored as a pair of uint32_t with the poll counter * being the 32 lowest bits and the vote counter the 32 highest bits. */ std::atomic<uint64_t> invCounters; /** The last computed score */ std::atomic<double> availabilityScore; /** * Protect the sequence of operations required for updating the * statistics. */ Mutex cs_statistics; public: CPubKey pubkey; AvalancheState() : invCounters(0), availabilityScore(0.) {} /** The node was polled for count invs */ void invsPolled(uint32_t count); /** The node voted for count invs */ void invsVoted(uint32_t count); /** * The availability score is calculated using an exponentially weighted * average. * This has several interesting properties: * - The most recent polls/responses have more weight than the previous * ones. A node that recently stopped answering will see its ratio * decrease quickly. * - This is a low-pass filter, so it causes delay. This means that a * node needs to have a track record for the ratio to be high. A node * that has been little requested will have a lower ratio than a node * that failed to answer a few polls but answered a lot of them. * - It is cheap to compute. * * This is expected to be called at a fixed interval of * AVALANCHE_STATISTICS_REFRESH_PERIOD. */ void updateAvailabilityScore(); double getAvailabilityScore() const; }; // m_avalanche_state == nullptr if we're not using avalanche with this peer std::unique_ptr<AvalancheState> m_avalanche_state; // Used for headers announcements - unfiltered blocks to relay std::vector<BlockHash> vBlockHashesToAnnounce GUARDED_BY(cs_inventory); /** * UNIX epoch time of the last block received from this peer that we had * not yet seen (e.g. not already received from another peer), that passed * preliminary validity checks and was saved to disk, even if we don't * connect the block or it eventually fails connection. Used as an inbound * peer eviction criterium in CConnman::AttemptToEvictConnection. */ std::atomic<int64_t> nLastBlockTime{0}; /** * UNIX epoch time of the last transaction received from this peer that we * had not yet seen (e.g. not already received from another peer) and that * was accepted into our mempool. Used as an inbound peer eviction criterium * in CConnman::AttemptToEvictConnection. */ std::atomic<int64_t> nLastTXTime{0}; /** * UNIX epoch time of the last proof received from this peer that we * had not yet seen (e.g. not already received from another peer) and that * was accepted into our proof pool. Used as an inbound peer eviction * criterium in CConnman::AttemptToEvictConnection. */ std::atomic<int64_t> nLastProofTime{0}; // Ping time measurement: // The pong reply we're expecting, or 0 if no pong expected. std::atomic<uint64_t> nPingNonceSent{0}; /** When the last ping was sent, or 0 if no ping was ever sent */ std::atomic<std::chrono::microseconds> m_ping_start{ std::chrono::microseconds{0}}; // Last measured round-trip time. std::atomic<int64_t> nPingUsecTime{0}; // Best measured round-trip time. std::atomic<int64_t> nMinPingUsecTime{std::numeric_limits<int64_t>::max()}; // Whether a ping is requested. std::atomic<bool> fPingQueued{false}; std::set<TxId> orphan_work_set; CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, uint64_t nLocalExtraEntropyIn, const CAddress &addrBindIn, const std::string &addrNameIn, ConnectionType conn_type_in); ~CNode(); CNode(const CNode &) = delete; CNode &operator=(const CNode &) = delete; private: const NodeId id; const uint64_t nLocalHostNonce; const uint64_t nLocalExtraEntropy; const ConnectionType m_conn_type; std::atomic<int> m_greatest_common_version{INIT_PROTO_VERSION}; //! Services offered to this peer. //! //! This is supplied by the parent CConnman during peer connection //! (CConnman::ConnectNode()) from its attribute of the same name. //! //! This is const because there is no protocol defined for renegotiating //! services initially offered to a peer. The set of local services we //! offer should not change after initialization. //! //! An interesting example of this is NODE_NETWORK and initial block //! download: a node which starts up from scratch doesn't have any blocks //! to serve, but still advertises NODE_NETWORK because it will eventually //! fulfill this role after IBD completes. P2P code is written in such a //! way that it can gracefully handle peers who don't make good on their //! service advertisements. const ServiceFlags nLocalServices; const int nMyStartingHeight; NetPermissionFlags m_permissionFlags{PF_NONE}; // Used only by SocketHandler thread std::list<CNetMessage> vRecvMsg; mutable RecursiveMutex cs_addrName; std::string addrName GUARDED_BY(cs_addrName); // Our address, as reported by the peer CService addrLocal GUARDED_BY(cs_addrLocal); mutable RecursiveMutex cs_addrLocal; public: NodeId GetId() const { return id; } uint64_t GetLocalNonce() const { return nLocalHostNonce; } uint64_t GetLocalExtraEntropy() const { return nLocalExtraEntropy; } int GetMyStartingHeight() const { return nMyStartingHeight; } int GetRefCount() const { assert(nRefCount >= 0); return nRefCount; } bool ReceiveMsgBytes(const Config &config, const char *pch, uint32_t nBytes, bool &complete); void SetCommonVersion(int greatest_common_version) { Assume(m_greatest_common_version == INIT_PROTO_VERSION); m_greatest_common_version = greatest_common_version; } int GetCommonVersion() const { return m_greatest_common_version; } CService GetAddrLocal() const; //! May not be called more than once void SetAddrLocal(const CService &addrLocalIn); CNode *AddRef() { nRefCount++; return this; } void Release() { nRefCount--; } void AddAddressKnown(const CAddress &_addr) { assert(m_addr_known); m_addr_known->insert(_addr.GetKey()); } void PushAddress(const CAddress &_addr, FastRandomContext &insecure_rand) { // Whether the peer supports the address in `_addr`. For example, // nodes that do not implement BIP155 cannot receive Tor v3 addresses // because they require ADDRv2 (BIP155) encoding. const bool addr_format_supported = m_wants_addrv2 || _addr.IsAddrV1Compatible(); // Known checking here is only to save space from duplicates. // SendMessages will filter it again for knowns that were added // after addresses were pushed. assert(m_addr_known); if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey()) && addr_format_supported) { if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) { vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] = _addr; } else { vAddrToSend.push_back(_addr); } } } void AddKnownTx(const TxId &txid) { if (m_tx_relay != nullptr) { LOCK(m_tx_relay->cs_tx_inventory); m_tx_relay->filterInventoryKnown.insert(txid); } } void PushTxInventory(const TxId &txid) { if (m_tx_relay == nullptr) { return; } LOCK(m_tx_relay->cs_tx_inventory); if (!m_tx_relay->filterInventoryKnown.contains(txid)) { m_tx_relay->setInventoryTxToSend.insert(txid); } } void AddKnownProof(const avalanche::ProofId &proofid) { if (m_proof_relay != nullptr) { LOCK(m_proof_relay->cs_proof_inventory); m_proof_relay->filterProofKnown.insert(proofid); } } void PushProofInventory(const avalanche::ProofId &proofid) { if (m_proof_relay == nullptr) { return; } LOCK(m_proof_relay->cs_proof_inventory); if (!m_proof_relay->filterProofKnown.contains(proofid)) { m_proof_relay->setInventoryProofToSend.insert(proofid); } } void CloseSocketDisconnect(); void copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap); ServiceFlags GetLocalServices() const { return nLocalServices; } std::string GetAddrName() const; //! Sets the addrName only if it was not previously set void MaybeSetAddrName(const std::string &addrNameIn); std::string ConnectionTypeAsString() const; }; /** * Return a timestamp in the future (in microseconds) for exponentially * distributed events. */ int64_t PoissonNextSend(int64_t now, int average_interval_seconds); /** Wrapper to return mockable type */ inline std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval) { return std::chrono::microseconds{ PoissonNextSend(now.count(), average_interval.count())}; } std::string getSubVersionEB(uint64_t MaxBlockSize); std::string userAgent(const Config &config); struct NodeEvictionCandidate { NodeId id; int64_t nTimeConnected; int64_t nMinPingUsecTime; int64_t nLastBlockTime; int64_t nLastProofTime; int64_t nLastTXTime; bool fRelevantServices; bool fRelayTxes; bool fBloomFilter; uint64_t nKeyedNetGroup; bool prefer_evict; bool m_is_local; double availabilityScore; }; [[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate> &&vEvictionCandidates); #endif // BITCOIN_NET_H