Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/init.cpp b/src/init.cpp
index 0a230c0d6..d4887c8ac 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1,3236 +1,3236 @@
// 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 <avalanche/avalanche.h>
#include <avalanche/processor.h>
#include <avalanche/proof.h> // For AVALANCHE_LEGACY_PROOF_DEFAULT
#include <avalanche/validation.h>
#include <avalanche/voterecord.h> // For AVALANCHE_VOTE_STALE_*
#include <banman.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <compat/sanity.h>
#include <config.h>
#include <consensus/amount.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/coinstatsindex.h>
#include <index/txindex.h>
#include <interfaces/chain.h>
#include <interfaces/node.h>
#include <key.h>
#include <mapport.h>
#include <miner.h>
#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
#include <netbase.h>
#include <node/blockstorage.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 <txorphanage.h>
#include <util/asmap.h>
#include <util/check.h>
#include <util/moneystr.h>
#include <util/string.h>
#include <util/thread.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>
#if ENABLE_CHRONIK
#include <chronik-cpp/chronik.h>
#endif
#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>
#include <thread>
#include <vector>
static const bool DEFAULT_PROXYRANDOMIZE = true;
static const bool DEFAULT_REST_ENABLE = 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::PathFromString(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"),
fs::PathToString(GetPidFile(args)),
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;
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(); });
if (g_coin_stats_index) {
g_coin_stats_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());
}
if (node.connman) {
node.connman->Stop();
}
#if ENABLE_CHRONIK
chronik::Stop();
#endif
StopTorControl();
// After everything has been shut down, but before things get flushed, stop
// the CScheduler/checkqueue, scheduler and load block thread.
if (node.scheduler) {
node.scheduler->stop();
}
if (node.chainman && node.chainman->m_load_block.joinable()) {
node.chainman->m_load_block.join();
}
StopScriptCheckWorkerThreads();
// 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();
node.addrman.reset();
if (node.mempool && node.mempool->IsLoaded() &&
node.args->GetBoolArg("-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();
}
if (g_coin_stats_index) {
g_coin_stats_index->Stop();
g_coin_stats_index.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();
}
}
}
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.reset();
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 = {
// Don't apply addrman network group limit for outbound connections
"-bypassnetgrouplimit",
"-dbcrashratio",
"-forcecompactdb",
"-maxaddrtosend",
"-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 May 2023 upgrade
"-wellingtonactivationtime",
};
// 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);
argsman.AddArg("-fastprune",
"Use smaller block files and lower minimum prune height for "
"testing purposes",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
#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("-coinstatsindex",
strprintf("Maintain coinstats index used by the "
"gettxoutsetinfo RPC (default: %u)",
DEFAULT_COINSTATSINDEX),
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(
"-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, -coinstatsindex "
"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>[:<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",
strprintf(
"Query for peer addresses via DNS lookup, if low on addresses "
"(default: %u unless -connect used)",
DEFAULT_DNSSEED),
ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION);
argsman.AddArg("-externalip=<ip>", "Specify your own public address",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-fixedseeds",
strprintf(
"Allow fixed seeds if DNS seeds don't provide peers (default: %u)",
DEFAULT_FIXEDSEEDS),
ArgsManager::ALLOW_BOOL, 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("-i2psam=<ip:port>",
"I2P SAM proxy to reach I2P peers and accept I2P "
"connections (default: none)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg(
"-i2pacceptincoming",
"If set and -i2psam is also set then incoming I2P connections are "
"accepted via the SAM proxy. If this is not set but -i2psam is set "
"then only outgoing connections will be made to the I2P network. "
"Ignored if -i2psam is not set. Listening for incoming I2P connections "
"is done through the SAM proxy, not by binding to a local address and "
"port (default: 1)",
ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION);
argsman.AddArg(
"-onlynet=<net>",
"Make outgoing connections only through network <net> (" +
Join(GetNetworkNames(), ", ") +
"). Incoming connections are not affected by this option. This "
"option can be specified multiple times to allow multiple "
"networks. Warning: if it is used with non-onion networks "
"and the -onion or -proxy option is set, then outbound onion "
"connections will still be made; use -noonion or -onion=0 to "
"disable outbound onion connections in this case",
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);
// TODO: remove the sentence "Nodes not using ... incoming connections."
// once the changes from https://github.com/bitcoin/bitcoin/pull/23542 have
// become widespread.
argsman.AddArg("-port=<port>",
strprintf("Listen for connections on <port>. Nodes not "
"using the default ports (default: %u, "
"testnet: %u, regtest: %u) are unlikely to get "
"incoming connections. Not relevant for I2P (see "
"doc/i2p.md).",
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
#ifdef USE_NATPMP
argsman.AddArg(
"-natpmp",
strprintf("Use NAT-PMP to map the listening port (default: %s)",
DEFAULT_NATPMP ? "1 when listening and no -proxy" : "0"),
ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION);
#else
hidden_args.emplace_back("-natpmp");
#endif // USE_NATPMP
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("-checkaddrman=<n>",
strprintf("Run addrman consistency checks every <n> "
"operations. Use 0 to disable. (default: %u)",
DEFAULT_ADDRMAN_CONSISTENCY_CHECKS),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
argsman.AddArg(
"-checkmempool=<n>",
strprintf("Run mempool consistency checks every <n> transactions. Use "
"0 to disable. (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(
"-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("-capturemessages", "Capture all P2P messages to disk",
ArgsManager::ALLOW_BOOL | 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(
"-logsourcelocations",
strprintf(
"Prepend debug output with name of the originating source location "
"(source file, line number and function name) (default: %u)",
DEFAULT_LOGSOURCELOCATIONS),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
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_FORK
argsman.AddArg("-daemon",
strprintf("Run in the background as a daemon and accept "
"commands (default: %d)",
DEFAULT_DAEMON),
ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
argsman.AddArg("-daemonwait",
strprintf("Wait for initialization to be finished before "
"exiting. This implies -daemon (default: %d)",
DEFAULT_DAEMONWAIT),
ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
#else
hidden_args.emplace_back("-daemon");
hidden_args.emplace_back("-daemonwait");
#endif
// Avalanche options.
argsman.AddArg("-avalanche",
strprintf("Enable the avalanche feature (default: %u)",
AVALANCHE_DEFAULT_ENABLED),
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
argsman.AddArg("-avalancheconflictingproofcooldown",
strprintf("Mandatory cooldown before a proof conflicting "
"with an already registered one can be considered "
"in seconds (default: %u)",
AVALANCHE_DEFAULT_CONFLICTING_PROOF_COOLDOWN),
ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE);
argsman.AddArg("-avalanchepeerreplacementcooldown",
strprintf("Mandatory cooldown before a peer can be replaced "
"in seconds (default: %u)",
AVALANCHE_DEFAULT_PEER_REPLACEMENT_COOLDOWN),
ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE);
argsman.AddArg(
"-avaminquorumstake",
strprintf(
"Minimum amount of known stake for a usable quorum (default: %s)",
FormatMoney(AVALANCHE_DEFAULT_MIN_QUORUM_STAKE)),
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
argsman.AddArg(
"-avaminquorumconnectedstakeratio",
strprintf("Minimum proportion of known stake we"
" need nodes for to have a usable quorum (default: %s)",
AVALANCHE_DEFAULT_MIN_QUORUM_CONNECTED_STAKE_RATIO),
ArgsManager::ALLOW_STRING, OptionsCategory::AVALANCHE);
argsman.AddArg(
"-avaminavaproofsnodecount",
strprintf("Minimum number of node that needs to send us an avaproofs"
" message before we consider we have a usable quorum"
" (default: %s)",
AVALANCHE_DEFAULT_MIN_AVAPROOFS_NODE_COUNT),
ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE);
argsman.AddArg(
"-avastalevotethreshold",
strprintf("Number of avalanche votes before a voted item goes stale "
"when voting confidence is low (default: %u)",
AVALANCHE_VOTE_STALE_THRESHOLD),
ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE);
argsman.AddArg(
"-avastalevotefactor",
strprintf(
"Factor affecting the number of avalanche votes before a voted "
"item goes stale when voting confidence is high (default: %u)",
AVALANCHE_VOTE_STALE_FACTOR),
ArgsManager::ALLOW_INT, OptionsCategory::AVALANCHE);
argsman.AddArg("-avacooldown",
strprintf("Mandatory cooldown between two avapoll in "
"milliseconds (default: %u)",
AVALANCHE_DEFAULT_COOLDOWN),
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
argsman.AddArg(
"-avatimeout",
strprintf("Avalanche query timeout in milliseconds (default: %u)",
AVALANCHE_DEFAULT_QUERY_TIMEOUT.count()),
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(
"-avaproofstakeutxoconfirmations",
strprintf(
"Minimum number of confirmations before a stake utxo is mature"
" enough to be included into a proof. Utxos in the mempool are not "
"accepted (i.e this value must be greater than 0) (default: %s)",
AVALANCHE_DEFAULT_STAKE_UTXO_CONFIRMATIONS),
ArgsManager::ALLOW_INT, OptionsCategory::HIDDEN);
argsman.AddArg("-avaproofstakeutxodustthreshold",
strprintf("Minimum value each stake utxo must have to be "
"considered valid (default: %s)",
avalanche::PROOF_DUST_THRESHOLD),
ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN);
argsman.AddArg("-avamasterkey",
"Master key associated with the proof. If a proof is "
"required, this is mandatory.",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE,
OptionsCategory::AVALANCHE);
argsman.AddArg("-avasessionkey", "Avalanche session key (default: random)",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE,
OptionsCategory::HIDDEN);
argsman.AddArg(
"-maxavalancheoutbound",
strprintf(
"Set the maximum number of avalanche outbound peers to connect to. "
"Note that the -maxconnections option takes precedence (default: "
"%u).",
DEFAULT_MAX_AVALANCHE_OUTBOUND_CONNECTIONS),
ArgsManager::ALLOW_INT, 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();
}
}
#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
/** 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."));
}
if (!ChronoSanityCheck()) {
return InitError(Untranslated("Clock epoch mismatch. 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(&node);
}
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 map ports 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__);
}
if (args.SoftSetBoolArg("-natpmp", false)) {
LogPrintf(
"%s: parameter interaction: -proxy set -> setting -natpmp=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("-natpmp", false)) {
LogPrintf(
"%s: parameter interaction: -listen=0 -> setting -natpmp=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.SoftSetBoolArg("-i2pacceptincoming", false)) {
LogPrintf("%s: parameter interaction: -listen=0 -> setting "
"-i2pacceptincoming=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__);
}
}
// If avalanche is set, soft set all the feature flags accordingly.
if (args.IsArgSet("-avalanche")) {
const bool fAvalanche =
args.GetBoolArg("-avalanche", AVALANCHE_DEFAULT_ENABLED);
args.SoftSetBoolArg("-automaticunparking", !fAvalanche);
}
}
/**
* 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(
fs::PathFromString(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
LogInstance().m_log_sourcelocations =
args.GetBoolArg("-logsourcelocations", DEFAULT_LOGSOURCELOCATIONS);
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(const 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 (!InitShutdownState()) {
return InitError(
Untranslated("Initializing wait-for-shutdown state failed."));
}
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 &section : 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(gArgs.GetBlocksDirPath())) {
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 and coinstatsindex
if (args.GetIntArg("-prune", 0)) {
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
return InitError(_("Prune mode is incompatible with -txindex."));
}
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) {
return InitError(
_("Prune mode is incompatible with -coinstatsindex."));
}
}
// -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.GetIntArg("-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 + NUM_FDS_MESSAGE_CAPTURE);
#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 - NUM_FDS_MESSAGE_CAPTURE),
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.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
int64_t nMempoolSizeMin =
args.GetIntArg("-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.GetIntArg("-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.GetIntArg("-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.GetIntArg("-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.GetIntArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
if (nConnectTimeout <= 0) {
nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
}
peer_connect_timeout =
args.GetIntArg("-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::Create()
::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.GetIntArg("-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.GetIntArg("-mocktime", 0)); // SetMockTime(0) is a no-op
if (args.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS)) {
nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);
}
nMaxTipAge = args.GetIntArg("-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>."));
}
// Avalanche parameters
const int64_t stakeUtxoMinConfirmations =
args.GetIntArg("-avaproofstakeutxoconfirmations",
AVALANCHE_DEFAULT_STAKE_UTXO_CONFIRMATIONS);
if (!chainparams.IsTestChain() &&
stakeUtxoMinConfirmations !=
AVALANCHE_DEFAULT_STAKE_UTXO_CONFIRMATIONS) {
return InitError(_("Avalanche stake UTXO minimum confirmations can "
"only be set on test chains."));
}
if (stakeUtxoMinConfirmations <= 0) {
return InitError(_("Avalanche stake UTXO minimum confirmations must be "
"a positive integer."));
}
if (args.IsArgSet("-avaproofstakeutxodustthreshold")) {
Amount amount = Amount::zero();
auto parsed = ParseMoney(
args.GetArg("-avaproofstakeutxodustthreshold", ""), amount);
if (!parsed || Amount::zero() == amount) {
return InitError(AmountErrMsg(
"avaproofstakeutxodustthreshold",
args.GetArg("-avaproofstakeutxodustthreshold", "")));
}
if (!chainparams.IsTestChain() &&
amount != avalanche::PROOF_DUST_THRESHOLD) {
return InitError(_("Avalanche stake UTXO dust threshold can "
"only be set on test chains."));
}
}
return true;
}
static bool LockDataDirectory(bool probeOnly) {
// Make sure only a single Bitcoin process is using the data directory.
fs::path datadir = gArgs.GetDataDirNet();
if (!DirIsWritable(datadir)) {
return InitError(strprintf(
_("Cannot write to data directory '%s'; check permissions."),
fs::PathToString(datadir)));
}
if (!LockDirectory(datadir, ".lock", probeOnly)) {
return InitError(strprintf(_("Cannot obtain a lock on data directory "
"%s. %s is probably already running."),
fs::PathToString(datadir), 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"),
fs::PathToString(logger.m_file_path)));
}
if (!logger.m_log_timestamps) {
LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime()));
}
LogPrintf("Default data directory %s\n",
fs::PathToString(GetDefaultDataDir()));
LogPrintf("Using data directory %s\n",
fs::PathToString(gArgs.GetDataDirNet()));
// 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", fs::PathToString(config_file_path));
} 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"),
fs::PathToString(config_file_path)));
} else {
// Not categorizing as "Warning" because it's the default behavior
LogPrintf("Config file: %s (not found, skipping)\n",
fs::PathToString(config_file_path));
}
// 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::PathFromString(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::PathToString(fs::current_path()));
}
InitSignatureCache();
InitScriptExecutionCache();
int script_threads = args.GetIntArg("-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) {
StartScriptCheckWorkerThreads(script_threads);
}
assert(!node.scheduler);
node.scheduler = std::make_unique<CScheduler>();
// Start the lightweight task scheduler thread
node.scheduler->m_service_thread =
std::thread(&util::TraceThread, "scheduler",
[&] { node.scheduler->serviceQueue(); });
// 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.
fListen = args.GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = args.GetBoolArg("-discover", true);
{
// Initialize addrman
assert(!node.addrman);
// Read asmap file if configured
std::vector<bool> asmap;
if (args.IsArgSet("-asmap")) {
fs::path asmap_path = fs::PathFromString(args.GetArg("-asmap", ""));
if (asmap_path.empty()) {
asmap_path = fs::PathFromString(DEFAULT_ASMAP_FILENAME);
}
if (!asmap_path.is_absolute()) {
asmap_path = gArgs.GetDataDirNet() / asmap_path;
}
if (!fs::exists(asmap_path)) {
InitError(strprintf(_("Could not find asmap file %s"),
fs::quoted(fs::PathToString(asmap_path))));
return false;
}
asmap = DecodeAsmap(asmap_path);
if (asmap.size() == 0) {
InitError(strprintf(_("Could not parse asmap file %s"),
fs::quoted(fs::PathToString(asmap_path))));
return false;
}
const uint256 asmap_version = SerializeHash(asmap);
LogPrintf("Using asmap version %s for IP bucketing\n",
asmap_version.ToString());
} else {
LogPrintf("Using /16 prefix for IP bucketing\n");
}
uiInterface.InitMessage(_("Loading P2P addresses...").translated);
if (const auto error{
LoadAddrman(chainparams, asmap, args, node.addrman)}) {
return InitError(*error);
}
}
assert(!node.banman);
node.banman = std::make_unique<BanMan>(
gArgs.GetDataDirNet() / "banlist.dat", config.GetChainParams(),
&uiInterface, args.GetIntArg("-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()), *node.addrman,
gArgs.GetBoolArg("-networkactive", true));
assert(!node.mempool);
int check_ratio = std::min<int>(
std::max<int>(
args.GetIntArg("-checkmempool",
chainparams.DefaultConsistencyChecks() ? 1 : 0),
0),
1000000);
node.mempool = std::make_unique<CTxMemPool>(check_ratio);
assert(!node.chainman);
node.chainman = std::make_unique<ChainstateManager>();
ChainstateManager &chainman = *node.chainman;
assert(!node.peerman);
node.peerman = PeerManager::make(
chainparams, *node.connman, *node.addrman, node.banman.get(), chainman,
*node.mempool, args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY));
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);
}
}
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));
}
}
#if ENABLE_ZMQ
g_zmq_notification_interface = CZMQNotificationInterface::Create();
if (g_zmq_notification_interface) {
RegisterValidationInterface(g_zmq_notification_interface);
}
#endif
// Step 6.5 (I guess ?): Initialize Avalanche.
bilingual_str avalancheError;
g_avalanche = avalanche::Processor::MakeProcessor(
args, *node.chain, node.connman.get(), chainman, *node.scheduler,
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.GetIntArg("-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.GetIntArg("-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.get()));
chainman.m_total_coinstip_cache = nCoinCacheUsage;
chainman.m_total_coinsdb_cache = nCoinDBCache;
UnloadBlockIndex(node.mempool.get(), chainman);
auto &pblocktree{chainman.m_blockman.m_block_tree_db};
// 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 &params = 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()) {
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() &&
!chainman.m_blockman.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 &&
!chainman.ActiveChainstate().LoadGenesisBlock()) {
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()) {
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()) {
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.GetIntArg("-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) {
+ tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) {
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;
}
if (!CVerifyDB().VerifyDB(
*chainstate, config, chainstate->CoinsDB(),
args.GetIntArg("-checklevel",
DEFAULT_CHECKLEVEL),
args.GetIntArg("-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)) {
if (const auto error{CheckLegacyTxindex(
*Assert(chainman.m_blockman.m_block_tree_db))}) {
return InitError(*error);
}
g_txindex = std::make_unique<TxIndex>(nTxIndexCache, false, fReindex);
g_txindex->Start(chainman.ActiveChainstate());
}
for (const auto &filter_type : g_enabled_filter_types) {
InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex);
GetBlockFilterIndex(filter_type)->Start(chainman.ActiveChainstate());
}
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) {
g_coin_stats_index = std::make_unique<CoinStatsIndex>(
/* cache size */ 0, false, fReindex);
g_coin_stats_index->Start(chainman.ActiveChainstate());
}
#if ENABLE_CHRONIK
chronik::Start(config, node);
#endif
// 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(gArgs.GetDataDirNet())) {
InitError(
strprintf(_("Error: Disk space is low for %s"),
fs::quoted(fs::PathToString(gArgs.GetDataDirNet()))));
return false;
}
if (!CheckDiskSpace(gArgs.GetBlocksDirPath())) {
InitError(
strprintf(_("Error: Disk space is low for %s"),
fs::quoted(fs::PathToString(gArgs.GetBlocksDirPath()))));
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 (chainman.ActiveTip() == 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(fs::PathFromString(strFile));
}
chainman.m_load_block = std::thread(
&util::TraceThread, "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 (node.peerman) {
node.peerman->SetBestHeight(chain_active_height);
}
Discover();
// Map ports with UPnP or NAT-PMP.
StartMapPort(args.GetBoolArg("-upnp", DEFAULT_UPNP),
gArgs.GetBoolArg("-natpmp", DEFAULT_NATPMP));
CConnman::Options connOptions;
connOptions.nLocalServices = nLocalServices;
connOptions.nMaxConnections = nMaxConnections;
connOptions.m_max_avalanche_outbound = std::min<int64_t>(
g_avalanche && isAvalancheEnabled(args)
? args.GetIntArg("-maxavalancheoutbound",
DEFAULT_MAX_AVALANCHE_OUTBOUND_CONNECTIONS)
: 0,
connOptions.nMaxConnections);
connOptions.m_max_outbound_full_relay = std::min(
MAX_OUTBOUND_FULL_RELAY_CONNECTIONS,
connOptions.nMaxConnections - connOptions.m_max_avalanche_outbound);
connOptions.m_max_outbound_block_relay = std::min(
MAX_BLOCK_RELAY_ONLY_CONNECTIONS,
connOptions.nMaxConnections - connOptions.m_max_avalanche_outbound -
connOptions.m_max_outbound_full_relay);
connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
connOptions.nMaxFeeler = MAX_FEELER_CONNECTIONS;
connOptions.uiInterface = &uiInterface;
connOptions.m_banman = node.banman.get();
connOptions.m_msgproc.push_back(node.peerman.get());
if (g_avalanche) {
connOptions.m_msgproc.push_back(g_avalanche.get());
}
connOptions.nSendBufferMaxSize =
1000 * args.GetIntArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
connOptions.nReceiveFloodSize =
1000 * args.GetIntArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
connOptions.m_added_nodes = args.GetArgs("-addnode");
connOptions.nMaxOutboundLimit =
1024 * 1024 *
args.GetIntArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET);
connOptions.m_peer_connect_timeout = peer_connect_timeout;
const auto BadPortWarning = [](const char *prefix, uint16_t port) {
return strprintf(_("%s request to listen on port %u. This port is "
"considered \"bad\" and "
"thus it is unlikely that any Bitcoin ABC peers "
"connect to it. See "
"doc/p2p-bad-ports.md for details and a full list."),
prefix, port);
};
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);
if (IsBadPort(bind_addr.GetPort())) {
InitWarning(BadPortWarning("-bind", bind_addr.GetPort()));
}
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));
}
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);
}
// If the user did not specify -bind= or -whitebind= then we bind
// on any address - 0.0.0.0 (IPv4) and :: (IPv6).
connOptions.bind_on_any =
args.GetArgs("-bind").empty() && args.GetArgs("-whitebind").empty();
// Emit a warning if a bad port is given to -port= but only if -bind and
// -whitebind are not given, because if they are, then -port= is ignored.
if (connOptions.bind_on_any && args.IsArgSet("-port")) {
const uint16_t port_arg = args.GetIntArg("-port", 0);
if (IsBadPort(port_arg)) {
InitWarning(BadPortWarning("-port", port_arg));
}
}
CService onion_service_target;
if (!connOptions.onion_binds.empty()) {
onion_service_target = connOptions.onion_binds.front();
} else {
onion_service_target = DefaultOnionServiceTarget();
connOptions.onion_binds.push_back(onion_service_target);
}
if (args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
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."),
onion_service_target.ToStringIPPort()));
}
StartTorControl(onion_service_target);
}
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;
}
}
const std::string &i2psam_arg = args.GetArg("-i2psam", "");
if (!i2psam_arg.empty()) {
CService addr;
if (!Lookup(i2psam_arg, addr, 7656, fNameLookup) || !addr.IsValid()) {
return InitError(strprintf(
_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg));
}
SetReachable(NET_I2P, true);
SetProxy(NET_I2P, proxyType{addr});
} else {
SetReachable(NET_I2P, false);
}
connOptions.m_i2p_accept_incoming =
args.GetBoolArg("-i2pacceptincoming", true);
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);
if (node.peerman) {
node.peerman->StartScheduledTasks(*node.scheduler);
}
// 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 8048d282c..a7282d8ea 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1,3663 +1,3664 @@
// 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 <addrdb.h>
+#include <addrman.h>
#include <avalanche/avalanche.h>
#include <banman.h>
#include <clientversion.h>
#include <compat.h>
#include <config.h>
#include <consensus/consensus.h>
#include <crypto/sha256.h>
#include <dnsseeds.h>
#include <i2p.h>
#include <netaddress.h>
#include <netbase.h>
#include <node/ui_interface.h>
#include <protocol.h>
#include <random.h>
#include <scheduler.h>
#include <util/sock.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <util/thread.h>
#include <util/translation.h>
#ifdef WIN32
#include <cstring>
#else
#include <fcntl.h>
#endif
#ifdef USE_POLL
#include <poll.h>
#endif
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <functional>
#include <limits>
#include <optional>
#include <unordered_map>
/** Maximum number of block-relay-only anchor connections */
static constexpr size_t MAX_BLOCK_RELAY_ONLY_ANCHORS = 2;
static_assert(MAX_BLOCK_RELAY_ONLY_ANCHORS <=
static_cast<size_t>(MAX_BLOCK_RELAY_ONLY_CONNECTIONS),
"MAX_BLOCK_RELAY_ONLY_ANCHORS must not exceed "
"MAX_BLOCK_RELAY_ONLY_CONNECTIONS.");
/** Anchor IP address database file name */
const char *const ANCHORS_DATABASE_FILENAME = "anchors.dat";
// 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;
/** The default timeframe for -maxuploadtarget. 1 day. */
static constexpr std::chrono::seconds MAX_UPLOAD_TIMEFRAME{60 * 60 * 24};
// We add a random period time (0 to 1 seconds) to feeler connections to prevent
// synchronization.
#define FEELER_SLEEP_WINDOW 1
/** 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;
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 static_cast<uint16_t>(
gArgs.GetIntArg("-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());
}
std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode) {
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, "Advertising address %s to peer=%d\n",
addrLocal.ToString(), pnode->GetId());
return addrLocal;
}
// Address is unroutable. Don't advertise.
return std::nullopt;
}
// 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::AlreadyConnectedToAddress(const CAddress &addr) {
return FindNode(static_cast<CNetAddr>(addr)) ||
FindNode(addr.ToStringIPPort());
}
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 uint16_t default_port{pszDest != nullptr
? Params().GetDefaultPort(pszDest)
: 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;
std::unique_ptr<Sock> sock;
proxyType proxy;
CAddress addr_bind;
assert(!addr_bind.IsValid());
if (addrConnect.IsValid()) {
bool proxyConnectionFailed = false;
if (addrConnect.GetNetwork() == NET_I2P &&
m_i2p_sam_session.get() != nullptr) {
i2p::Connection conn;
if (m_i2p_sam_session->Connect(addrConnect, conn,
proxyConnectionFailed)) {
connected = true;
sock = std::move(conn.sock);
addr_bind = CAddress{conn.me, NODE_NONE};
}
} else if (GetProxy(addrConnect.GetNetwork(), proxy)) {
sock = CreateSock(proxy.proxy);
if (!sock) {
return nullptr;
}
connected = ConnectThroughProxy(
proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), *sock,
nConnectTimeout, proxyConnectionFailed);
} else {
// no proxy needed (none set for target network)
sock = CreateSock(addrConnect);
if (!sock) {
return nullptr;
}
connected =
ConnectSocketDirectly(addrConnect, *sock, 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)) {
sock = CreateSock(proxy.proxy);
if (!sock) {
return nullptr;
}
std::string host;
uint16_t port{default_port};
SplitHostPort(std::string(pszDest), port, host);
bool proxyConnectionFailed;
connected = ConnectThroughProxy(proxy, host, port, *sock,
nConnectTimeout, proxyConnectionFailed);
}
if (!connected) {
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();
if (!addr_bind.IsValid()) {
addr_bind = GetBindAddress(sock->Get());
}
CNode *pnode =
new CNode(id, nLocalServices, sock->Release(), addrConnect,
CalculateKeyedNetGroup(addrConnect), nonce, extra_entropy,
addr_bind, pszDest ? pszDest : "", conn_type,
/* inbound_onion */ false);
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";
case ConnectionType::AVALANCHE_OUTBOUND:
return "avalanche";
} // 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;
}
}
Network CNode::ConnectedThroughNetwork() const {
return m_inbound_onion ? NET_ONION : addr.GetNetClass();
}
void CNode::copyStats(CNodeStats &stats) {
stats.nodeid = this->GetId();
stats.nServices = nServices;
stats.addr = addr;
stats.addrBind = addrBind;
stats.m_network = ConnectedThroughNetwork();
if (m_tx_relay != nullptr) {
LOCK(m_tx_relay->cs_filter);
stats.fRelayTxes = m_tx_relay->fRelayTxes;
} else {
stats.fRelayTxes = false;
}
stats.m_last_send = m_last_send;
stats.m_last_recv = m_last_recv;
stats.m_last_tx_time = m_last_tx_time;
stats.m_last_proof_time = m_last_proof_time;
stats.m_last_block_time = m_last_block_time;
stats.m_connected = m_connected;
stats.nTimeOffset = nTimeOffset;
stats.addrName = GetAddrName();
stats.nVersion = nVersion;
{
LOCK(cs_SubVer);
stats.cleanSubVer = cleanSubVer;
}
stats.fInbound = IsInboundConn();
stats.m_manual_connection = IsManualConn();
stats.m_bip152_highbandwidth_to = m_bip152_highbandwidth_to;
stats.m_bip152_highbandwidth_from = m_bip152_highbandwidth_from;
{
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();
}
stats.m_last_ping_time = m_last_ping_time;
stats.m_min_ping_time = m_min_ping_time;
// 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();
stats.m_availabilityScore = m_avalanche_enabled
? std::make_optional(getAvailabilityScore())
: std::nullopt;
}
bool CNode::ReceiveMsgBytes(const Config &config, Span<const uint8_t> msg_bytes,
bool &complete) {
complete = false;
const auto time = GetTime<std::chrono::microseconds>();
LOCK(cs_vRecv);
m_last_recv = std::chrono::duration_cast<std::chrono::seconds>(time);
nRecvBytes += msg_bytes.size();
while (msg_bytes.size() > 0) {
// Absorb network data.
int handled = m_deserializer->Read(config, msg_bytes);
if (handled < 0) {
return false;
}
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,
Span<const uint8_t> msg_bytes) {
// copy data to temporary parsing buffer
uint32_t nRemaining = CMessageHeader::HEADER_SIZE - nHdrPos;
uint32_t nCopy = std::min<unsigned int>(nRemaining, msg_bytes.size());
memcpy(&hdrbuf[nHdrPos], msg_bytes.data(), 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(Span<const uint8_t> msg_bytes) {
unsigned int nRemaining = hdr.nMessageSize - nDataPos;
unsigned int nCopy = std::min<unsigned int>(nRemaining, msg_bytes.size());
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(msg_bytes.first(nCopy));
memcpy(&vRecv[nDataPos], msg_bytes.data(), 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 &node) const {
size_t nSentSize = 0;
size_t nMsgCount = 0;
for (const auto &data : node.vSendMsg) {
assert(data.size() > node.nSendOffset);
int nBytes = 0;
{
LOCK(node.cs_hSocket);
if (node.hSocket == INVALID_SOCKET) {
break;
}
nBytes = send(
node.hSocket,
reinterpret_cast<const char *>(data.data()) + node.nSendOffset,
data.size() - node.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) {
LogPrint(BCLog::NET, "socket send error for peer=%d: %s\n",
node.GetId(), NetworkErrorString(nErr));
node.CloseSocketDisconnect();
}
break;
}
assert(nBytes > 0);
node.m_last_send = GetTime<std::chrono::seconds>();
node.nSendBytes += nBytes;
node.nSendOffset += nBytes;
nSentSize += nBytes;
if (node.nSendOffset != data.size()) {
// could not send full message; stop sending more
break;
}
node.nSendOffset = 0;
node.nSendSize -= data.size();
node.fPauseSend = node.nSendSize > nSendBufferMaxSize;
nMsgCount++;
}
node.vSendMsg.erase(node.vSendMsg.begin(),
node.vSendMsg.begin() + nMsgCount);
if (node.vSendMsg.empty()) {
assert(node.nSendOffset == 0);
assert(node.nSendSize == 0);
}
return nSentSize;
}
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
return a.m_min_ping_time > b.m_min_ping_time;
}
static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
return a.m_connected > b.m_connected;
}
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.m_last_block_time != b.m_last_block_time) {
return a.m_last_block_time < b.m_last_block_time;
}
if (a.fRelevantServices != b.fRelevantServices) {
return b.fRelevantServices;
}
return a.m_connected > b.m_connected;
}
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.m_last_tx_time != b.m_last_tx_time) {
return a.m_last_tx_time < b.m_last_tx_time;
}
if (a.fRelayTxes != b.fRelayTxes) {
return b.fRelayTxes;
}
if (a.fBloomFilter != b.fBloomFilter) {
return a.fBloomFilter;
}
return a.m_connected > b.m_connected;
}
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.m_last_proof_time != b.m_last_proof_time) {
return a.m_last_proof_time < b.m_last_proof_time;
}
return a.m_connected > b.m_connected;
}
// 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.m_last_block_time != b.m_last_block_time) {
return a.m_last_block_time < b.m_last_block_time;
}
if (a.fRelevantServices != b.fRelevantServices) {
return b.fRelevantServices;
}
return a.m_connected > b.m_connected;
}
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.m_connected > b.m_connected;
}
/**
* Sort eviction candidates by network/localhost and connection uptime.
* Candidates near the beginning are more likely to be evicted, and those
* near the end are more likely to be protected, e.g. less likely to be evicted.
* - First, nodes that are not `is_local` and that do not belong to `network`,
* sorted by increasing uptime (from most recently connected to connected
* longer).
* - Then, nodes that are `is_local` or belong to `network`, sorted by
* increasing uptime.
*/
struct CompareNodeNetworkTime {
const bool m_is_local;
const Network m_network;
CompareNodeNetworkTime(bool is_local, Network network)
: m_is_local(is_local), m_network(network) {}
bool operator()(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) const {
if (m_is_local && a.m_is_local != b.m_is_local) {
return b.m_is_local;
}
if ((a.m_network == m_network) != (b.m_network == m_network)) {
return b.m_network == m_network;
}
return a.m_connected > b.m_connected;
};
};
//! Sort an array by the specified comparator, then erase the last K elements
//! where predicate is true.
template <typename T, typename Comparator>
static void EraseLastKElements(
std::vector<T> &elements, Comparator comparator, size_t k,
std::function<bool(const NodeEvictionCandidate &)> predicate =
[](const NodeEvictionCandidate &n) { return true; }) {
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(), predicate),
elements.end());
}
void ProtectEvictionCandidatesByRatio(
std::vector<NodeEvictionCandidate> &eviction_candidates) {
// 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.
// To promote the diversity of our peer connections, reserve up to half of
// these protected spots for Tor/onion, localhost and I2P peers, even if
// they're not the longest uptime overall. This helps protect these
// higher-latency peers that tend to be otherwise disadvantaged under our
// eviction criteria.
const size_t initial_size = eviction_candidates.size();
const size_t total_protect_size{initial_size / 2};
// Disadvantaged networks to protect: I2P, localhost and Tor/onion. In case
// of equal counts, earlier array members have first opportunity to recover
// unused slots from the previous iteration.
struct Net {
bool is_local;
Network id;
size_t count;
};
std::array<Net, 3> networks{{{false, NET_I2P, 0},
{/* localhost */ true, NET_MAX, 0},
{false, NET_ONION, 0}}};
// Count and store the number of eviction candidates per network.
for (Net &n : networks) {
n.count = std::count_if(
eviction_candidates.cbegin(), eviction_candidates.cend(),
[&n](const NodeEvictionCandidate &c) {
return n.is_local ? c.m_is_local : c.m_network == n.id;
});
}
// Sort `networks` by ascending candidate count, to give networks having
// fewer candidates the first opportunity to recover unused protected slots
// from the previous iteration.
std::stable_sort(networks.begin(), networks.end(),
[](Net a, Net b) { return a.count < b.count; });
// Protect up to 25% of the eviction candidates by disadvantaged network.
const size_t max_protect_by_network{total_protect_size / 2};
size_t num_protected{0};
while (num_protected < max_protect_by_network) {
// Count the number of disadvantaged networks from which we have peers
// to protect.
auto num_networks = std::count_if(networks.begin(), networks.end(),
[](const Net &n) { return n.count; });
if (num_networks == 0) {
break;
}
const size_t disadvantaged_to_protect{max_protect_by_network -
num_protected};
const size_t protect_per_network{std::max(
disadvantaged_to_protect / num_networks, static_cast<size_t>(1))};
// Early exit flag if there are no remaining candidates by disadvantaged
// network.
bool protected_at_least_one{false};
for (Net &n : networks) {
if (n.count == 0) {
continue;
}
const size_t before = eviction_candidates.size();
EraseLastKElements(
eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id),
protect_per_network, [&n](const NodeEvictionCandidate &c) {
return n.is_local ? c.m_is_local : c.m_network == n.id;
});
const size_t after = eviction_candidates.size();
if (before > after) {
protected_at_least_one = true;
const size_t delta{before - after};
num_protected += delta;
if (num_protected >= max_protect_by_network) {
break;
}
n.count -= delta;
}
}
if (!protected_at_least_one) {
break;
}
}
// Calculate how many we removed, and update our total number of peers that
// we want to protect based on uptime accordingly.
assert(num_protected == initial_size - eviction_candidates.size());
const size_t remaining_to_protect{total_protect_size - num_protected};
EraseLastKElements(eviction_candidates, ReverseCompareNodeTimeConnected,
remaining_to_protect);
}
[[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.
EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8,
[](const NodeEvictionCandidate &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.
EraseLastKElements(vEvictionCandidates, CompareNodeAvailabilityScore, 128,
[](NodeEvictionCandidate const &n) {
return n.availabilityScore > 0.;
});
// Protect some of the remaining eviction candidates by ratios of desirable
// or disadvantaged characteristics.
ProtectEvictionCandidatesByRatio(vEvictionCandidates);
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;
std::chrono::seconds 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);
const auto grouptime{group[0].m_connected};
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->m_connected,
node->m_min_ping_time,
node->m_last_block_time,
node->m_last_proof_time,
node->m_last_tx_time,
HasAllDesirableServiceFlags(node->nServices),
peer_relay_txes,
peer_filter_not_null,
node->nKeyedNetGroup,
node->m_prefer_evict,
node->addr.IsLocal(),
node->ConnectedThroughNetwork(),
node->m_avalanche_enabled
? node->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) {
LogPrint(
BCLog::NET,
"selected %s connection for eviction peer=%d; disconnecting\n",
pnode->ConnectionTypeAsString(), pnode->GetId());
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;
if (hSocket == INVALID_SOCKET) {
const int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK) {
LogPrintf("socket error accept failed: %s\n",
NetworkErrorString(nErr));
}
return;
}
if (!addr.SetSockAddr((const struct sockaddr *)&sockaddr)) {
LogPrintf("Warning: Unknown socket family\n");
}
const CAddress addr_bind = GetBindAddress(hSocket);
NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE;
hListenSocket.AddSocketPermissionFlags(permissionFlags);
CreateNodeFromAcceptedSocket(hSocket, permissionFlags, addr_bind, addr);
}
void CConnman::CreateNodeFromAcceptedSocket(SOCKET hSocket,
NetPermissionFlags permissionFlags,
const CAddress &addr_bind,
const CAddress &addr) {
int nInbound = 0;
int nMaxInbound = nMaxConnections - m_max_outbound;
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 (!fNetworkActive) {
LogPrint(BCLog::NET,
"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();
ServiceFlags nodeServices = nLocalServices;
if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) {
nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM);
}
const bool inbound_onion =
std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) !=
m_onion_binds.end();
CNode *pnode = new CNode(
id, nodeServices, hSocket, addr, CalculateKeyedNetGroup(addr), nonce,
extra_entropy, addr_bind, "", ConnectionType::INBOUND, inbound_onion);
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;
for (auto interface : m_msgproc) {
interface->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));
}
bool CConnman::AddConnection(const std::string &address,
ConnectionType conn_type) {
std::optional<int> max_connections;
switch (conn_type) {
case ConnectionType::INBOUND:
case ConnectionType::MANUAL:
return false;
case ConnectionType::OUTBOUND_FULL_RELAY:
max_connections = m_max_outbound_full_relay;
break;
case ConnectionType::BLOCK_RELAY:
max_connections = m_max_outbound_block_relay;
break;
// no limit for ADDR_FETCH because -seednode has no limit either
case ConnectionType::ADDR_FETCH:
break;
// no limit for FEELER connections since they're short-lived
case ConnectionType::FEELER:
break;
case ConnectionType::AVALANCHE_OUTBOUND:
max_connections = m_max_avalanche_outbound;
break;
} // no default case, so the compiler can warn about missing cases
// Count existing connections
int existing_connections = WITH_LOCK(
cs_vNodes, return std::count_if(
vNodes.begin(), vNodes.end(), [conn_type](CNode *node) {
return node->m_conn_type == conn_type;
}););
// Max connections of specified type already exist
if (max_connections != std::nullopt &&
existing_connections >= max_connections) {
return false;
}
// Max total outbound connections already exist
CSemaphoreGrant grant(*semOutbound, true);
if (!grant) {
return false;
}
OpenNetworkConnection(CAddress(), false, &grant, address.c_str(),
conn_type);
return true;
}
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);
}
}
}
bool CConnman::ShouldRunInactivityChecks(const CNode &node,
std::chrono::seconds now) const {
return node.m_connected + m_peer_connect_timeout < now;
}
bool CConnman::InactivityCheck(const CNode &node) const {
// Tests that see disconnects after using mocktime can start nodes with a
// large timeout. For example, -peertimeout=999999999.
const auto now{GetTime<std::chrono::seconds>()};
const auto last_send{node.m_last_send.load()};
const auto last_recv{node.m_last_recv.load()};
if (!ShouldRunInactivityChecks(node, now)) {
return false;
}
if (last_recv.count() == 0 || last_send.count() == 0) {
LogPrint(BCLog::NET,
"socket no message in first %i seconds, %d %d peer=%d\n",
count_seconds(m_peer_connect_timeout), last_recv.count() != 0,
last_send.count() != 0, node.GetId());
return true;
}
if (now > last_send + TIMEOUT_INTERVAL) {
LogPrint(BCLog::NET, "socket sending timeout: %is peer=%d\n",
count_seconds(now - last_send), node.GetId());
return true;
}
if (now > last_recv + TIMEOUT_INTERVAL) {
LogPrint(BCLog::NET, "socket receive timeout: %is peer=%d\n",
count_seconds(now - last_recv), node.GetId());
return true;
}
if (!node.fSuccessfullyConnected) {
LogPrint(BCLog::NET, "version handshake timeout peer=%d\n",
node.GetId());
return true;
}
return false;
}
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
uint8_t pchBuf[0x10000];
int32_t nBytes = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
continue;
}
nBytes = recv(pnode->hSocket, (char *)pchBuf, sizeof(pchBuf),
MSG_DONTWAIT);
}
if (nBytes > 0) {
bool notify = false;
if (!pnode->ReceiveMsgBytes(
*config, Span<const uint8_t>(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);
}
}
if (InactivityCheck(*pnode)) {
pnode->fDisconnect = true;
}
}
{
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();
}
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->IsFullOutboundConn()) {
++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();
DumpPeerAddresses(config->GetChainParams(), ::gArgs, 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::GetExtraFullOutboundCount() {
int full_outbound_peers = 0;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect &&
pnode->IsFullOutboundConn()) {
++full_outbound_peers;
}
}
}
return std::max(full_outbound_peers - m_max_outbound_full_relay -
m_max_avalanche_outbound,
0);
}
int CConnman::GetExtraBlockRelayCount() {
int block_relay_peers = 0;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect &&
pnode->IsBlockOnlyConn()) {
++block_relay_peers;
}
}
}
return std::max(block_relay_peers - m_max_outbound_block_relay, 0);
}
void CConnman::ThreadOpenConnections(
const std::vector<std::string> connect,
std::function<void(const CAddress &, ConnectionType)> mockOpenConnection) {
// 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
auto start = GetTime<std::chrono::microseconds>();
// Minimum time before next feeler connection (in microseconds).
auto next_feeler = PoissonNextSend(start, FEELER_INTERVAL);
auto next_extra_block_relay =
PoissonNextSend(start, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED);
bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS);
if (!add_fixed_seeds) {
LogPrintf("Fixed seeds are disabled\n");
}
while (!interruptNet) {
ProcessAddrFetch();
// No need to sleep the thread if we are mocking the network connection
if (!mockOpenConnection &&
!interruptNet.sleep_for(std::chrono::milliseconds(500))) {
return;
}
CSemaphoreGrant grant(*semOutbound);
if (interruptNet) {
return;
}
if (add_fixed_seeds && addrman.size() == 0) {
// When the node starts with an empty peers.dat, there are a few
// other sources of peers before we fallback on to fixed seeds:
// -dnsseed, -seednode, -addnode If none of those are available, we
// fallback on to fixed seeds immediately, else we allow 60 seconds
// for any of those sources to populate addrman.
bool add_fixed_seeds_now = false;
// It is cheapest to check if enough time has passed first.
if (GetTime<std::chrono::seconds>() >
start + std::chrono::minutes{1}) {
add_fixed_seeds_now = true;
LogPrintf("Adding fixed seeds as 60 seconds have passed and "
"addrman is empty\n");
}
// Checking !dnsseed is cheaper before locking 2 mutexes.
if (!add_fixed_seeds_now && !dnsseed) {
LOCK2(m_addr_fetches_mutex, cs_vAddedNodes);
if (m_addr_fetches.empty() && vAddedNodes.empty()) {
add_fixed_seeds_now = true;
LogPrintf(
"Adding fixed seeds as -dnsseed=0, -addnode is not "
"provided and all -seednode(s) attempted\n");
}
}
if (add_fixed_seeds_now) {
CNetAddr local;
local.SetInternal("fixedseeds");
addrman.Add(convertSeed6(config->GetChainParams().FixedSeeds()),
local);
add_fixed_seeds = false;
}
}
//
// 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;
int nOutboundAvalanche = 0;
std::set<std::vector<uint8_t>> setConnected;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (pnode->IsAvalancheOutboundConnection()) {
nOutboundAvalanche++;
} else if (pnode->IsFullOutboundConn()) {
nOutboundFullRelay++;
} else 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::AVALANCHE_OUTBOUND:
case ConnectionType::OUTBOUND_FULL_RELAY:
case ConnectionType::BLOCK_RELAY:
case ConnectionType::ADDR_FETCH:
case ConnectionType::FEELER:
setConnected.insert(
pnode->addr.GetGroup(addrman.GetAsmap()));
} // no default case, so the compiler can warn about missing
// cases
}
}
ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
auto now = GetTime<std::chrono::microseconds>();
bool anchor = false;
bool fFeeler = false;
// Determine what type of connection to open. Opening
// BLOCK_RELAY connections to addresses from anchors.dat gets the
// highest priority. Then we open AVALANCHE_OUTBOUND connection until we
// hit our avalanche outbound peer limit, which is 0 if avalanche is not
// enabled. We fallback after 50 retries to OUTBOUND_FULL_RELAY if the
// peer is not avalanche capable 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 to see if it's time to try an extra
// block-relay-only peer (to confirm our tip is current, see below) or
// the next_feeler timer to decide if we should open a FEELER.
if (!m_anchors.empty() &&
(nOutboundBlockRelay < m_max_outbound_block_relay)) {
conn_type = ConnectionType::BLOCK_RELAY;
anchor = true;
} else if (g_avalanche &&
(nOutboundAvalanche < m_max_avalanche_outbound)) {
conn_type = ConnectionType::AVALANCHE_OUTBOUND;
} else 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 (now > next_extra_block_relay &&
m_start_extra_block_relay_peers) {
// Periodically connect to a peer (using regular outbound selection
// methodology from addrman) and stay connected long enough to sync
// headers, but not much else.
//
// Then disconnect the peer, if we haven't learned anything new.
//
// The idea is to make eclipse attacks very difficult to pull off,
// because every few minutes we're finding a new peer to learn
// headers from.
//
// This is similar to the logic for trying extra outbound
// (full-relay) peers, except:
// - we do this all the time on a poisson timer, rather than just
// when our tip is stale
// - we potentially disconnect our next-youngest block-relay-only
// peer, if our newest block-relay-only peer delivers a block more
// recently.
// See the eviction logic in net_processing.cpp.
//
// Because we can promote these connections to block-relay-only
// connections, they do not get their own ConnectionType enum
// (similar to how we deal with extra outbound peers).
next_extra_block_relay =
PoissonNextSend(now, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
conn_type = ConnectionType::BLOCK_RELAY;
} else if (now > next_feeler) {
next_feeler = PoissonNextSend(now, 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) {
if (anchor && !m_anchors.empty()) {
const CAddress addr = m_anchors.back();
m_anchors.pop_back();
if (!addr.IsValid() || IsLocal(addr) || !IsReachable(addr) ||
!HasAllDesirableServiceFlags(addr.nServices) ||
setConnected.count(addr.GetGroup(addrman.GetAsmap()))) {
continue;
}
addrConnect = addr;
LogPrint(BCLog::NET,
"Trying to make an anchor connection to %s\n",
addrConnect.ToString());
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;
}
CAddress addr;
int64_t addr_last_try{0};
if (fFeeler) {
// First, try to get a tried table collision address. This
// returns an empty (invalid) address if there are no collisions
// to try.
std::tie(addr, addr_last_try) = addrman.SelectTriedCollision();
if (!addr.IsValid()) {
// No tried table collisions. Select a new table address
// for our feeler.
std::tie(addr, addr_last_try) = addrman.Select(true);
} else if (AlreadyConnectedToAddress(addr)) {
// If test-before-evict logic would have us connect to a
// peer that we're already connected to, just mark that
// address as Good(). We won't be able to initiate the
// connection anyway, so this avoids inadvertently evicting
// a currently-connected peer.
addrman.Good(addr);
// Select a new table address for our feeler instead.
std::tie(addr, addr_last_try) = addrman.Select(true);
}
} else {
// Not a feeler
std::tie(addr, addr_last_try) = addrman.Select();
}
// Require outbound connections, other than feelers and avalanche,
// to be to distinct network groups
if (!fFeeler && conn_type != ConnectionType::AVALANCHE_OUTBOUND &&
setConnected.count(addr.GetGroup(addrman.GetAsmap()))) {
break;
}
// if we selected an invalid or local address, restart
if (!addr.IsValid() || IsLocal(addr)) {
break;
}
if (!IsReachable(addr)) {
continue;
}
// only consider very recently tried nodes after 30 failed attempts
if (nANow - addr_last_try < 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 connect to bad ports, unless 50 invalid addresses have
// been selected already.
if (nTries < 50 && (addr.IsIPv4() || addr.IsIPv6()) &&
IsBadPort(addr.GetPort())) {
continue;
}
// For avalanche peers, check they have the avalanche service bit
// set.
if (conn_type == ConnectionType::AVALANCHE_OUTBOUND &&
!(addr.nServices & NODE_AVALANCHE)) {
// If this peer is not suitable as an avalanche one and we tried
// over 50 addresses already, see if we can fallback to a non
// avalanche full outbound.
if (nTries < 50 ||
nOutboundFullRelay >= m_max_outbound_full_relay ||
setConnected.count(addr.GetGroup(addrman.GetAsmap()))) {
// Fallback is not desirable or possible, try another one
continue;
}
// Fallback is possible, update the connection type accordingly
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
}
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());
}
// This mock is for testing purpose only. It prevents the thread
// from attempting the connection which is useful for testing.
if (mockOpenConnection) {
mockOpenConnection(addrConnect, conn_type);
} else {
OpenNetworkConnection(addrConnect,
int(setConnected.size()) >=
std::min(nMaxConnections - 1, 2),
&grant, nullptr, conn_type);
}
}
}
}
std::vector<CAddress> CConnman::GetCurrentBlockRelayOnlyConns() const {
std::vector<CAddress> ret;
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (pnode->IsBlockOnlyConn()) {
ret.push_back(pnode->addr);
}
}
return ret;
}
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(strAddNode)));
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) || banned_or_discouraged ||
AlreadyConnectedToAddress(addrConnect)) {
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);
}
for (auto interface : m_msgproc) {
interface->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;
}
bool fMoreNodeWork = false;
// Receive messages
for (auto interface : m_msgproc) {
fMoreNodeWork |= interface->ProcessMessages(
*config, pnode, flagInterruptMsgProc);
}
fMoreWork |= (fMoreNodeWork && !pnode->fPauseSend);
if (flagInterruptMsgProc) {
return;
}
// Send messages
{
LOCK(pnode->cs_sendProcessing);
for (auto interface : m_msgproc) {
interface->SendMessages(*config, pnode);
}
}
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;
}
}
void CConnman::ThreadI2PAcceptIncoming() {
static constexpr auto err_wait_begin = 1s;
static constexpr auto err_wait_cap = 5min;
auto err_wait = err_wait_begin;
bool advertising_listen_addr = false;
i2p::Connection conn;
while (!interruptNet) {
if (!m_i2p_sam_session->Listen(conn)) {
if (advertising_listen_addr && conn.me.IsValid()) {
RemoveLocal(conn.me);
advertising_listen_addr = false;
}
interruptNet.sleep_for(err_wait);
if (err_wait < err_wait_cap) {
err_wait *= 2;
}
continue;
}
if (!advertising_listen_addr) {
AddLocal(conn.me, LOCAL_MANUAL);
advertising_listen_addr = true;
}
if (!m_i2p_sam_session->Accept(conn)) {
continue;
}
CreateNodeFromAcceptedSocket(
conn.sock->Release(), NetPermissionFlags::PF_NONE,
CAddress{conn.me, NODE_NONE}, CAddress{conn.peer, NODE_NONE});
}
}
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;
}
std::unique_ptr<Sock> sock = CreateSock(addrBind);
if (!sock) {
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(sock->Get(), 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(sock->Get(), IPPROTO_IPV6, IPV6_V6ONLY,
(sockopt_arg_type)&nOne, sizeof(int));
#endif
#ifdef WIN32
int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED;
setsockopt(sock->Get(), IPPROTO_IPV6, IPV6_PROTECTION_LEVEL,
(sockopt_arg_type)&nProtLevel, sizeof(int));
#endif
}
if (::bind(sock->Get(), (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);
return false;
}
LogPrintf("Bound to %s\n", addrBind.ToString());
// Listen for incoming connections
if (listen(sock->Get(), SOMAXCONN) == SOCKET_ERROR) {
strError = strprintf(_("Error: Listening for incoming connections "
"failed (listen returned error %s)"),
NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError.original);
return false;
}
vhListenSocket.push_back(ListenSocket(sock->Release(), 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,
AddrMan &addrmanIn, bool network_active)
: config(&configIn), addrman(addrmanIn), 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 && !(flags & BF_DONT_ADVERTISE) &&
!(permissions & PF_NOBAN)) {
AddLocal(addr, LOCAL_BIND);
}
return true;
}
bool CConnman::InitBinds(const Options &options) {
bool fBound = false;
for (const auto &addrBind : options.vBinds) {
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR),
NetPermissionFlags::PF_NONE);
}
for (const auto &addrBind : options.vWhiteBinds) {
fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR),
addrBind.m_flags);
}
for (const auto &addr_bind : options.onion_binds) {
fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE,
NetPermissionFlags::PF_NONE);
}
if (options.bind_on_any) {
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);
}
return fBound;
}
bool CConnman::Start(CScheduler &scheduler, const Options &connOptions) {
Init(connOptions);
if (fListen && !InitBinds(connOptions)) {
if (clientInterface) {
clientInterface->ThreadSafeMessageBox(
_("Failed to listen on any port. Use -listen=0 if you want "
"this."),
"", CClientUIInterface::MSG_ERROR);
}
return false;
}
proxyType i2p_sam;
if (GetProxy(NET_I2P, i2p_sam)) {
m_i2p_sam_session = std::make_unique<i2p::sam::Session>(
gArgs.GetDataDirNet() / "i2p_private_key", i2p_sam.proxy,
&interruptNet);
}
for (const auto &strDest : connOptions.vSeedNodes) {
AddAddrFetch(strDest);
}
if (m_use_addrman_outgoing) {
// Load addresses from anchors.dat
m_anchors =
ReadAnchors(config->GetChainParams(),
gArgs.GetDataDirNet() / ANCHORS_DATABASE_FILENAME);
if (m_anchors.size() > MAX_BLOCK_RELAY_ONLY_ANCHORS) {
m_anchors.resize(MAX_BLOCK_RELAY_ONLY_ANCHORS);
}
LogPrintf(
"%i block-relay-only anchors will be tried for connections.\n",
m_anchors.size());
}
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.size() > 0);
InterruptSocks5(false);
interruptNet.reset();
flagInterruptMsgProc = false;
{
LOCK(mutexMsgProc);
fMsgProcWake = false;
}
// Send and receive from sockets, accept connections
threadSocketHandler = std::thread(&util::TraceThread, "net",
[this] { ThreadSocketHandler(); });
if (!gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED)) {
LogPrintf("DNS seeding disabled\n");
} else {
threadDNSAddressSeed = std::thread(&util::TraceThread, "dnsseed",
[this] { ThreadDNSAddressSeed(); });
}
// Initiate manual connections
threadOpenAddedConnections = std::thread(
&util::TraceThread, "addcon", [this] { ThreadOpenAddedConnections(); });
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(&util::TraceThread, "opencon",
[this, connect = connOptions.m_specified_outgoing] {
ThreadOpenConnections(connect, nullptr);
});
}
// Process messages
threadMessageHandler = std::thread(&util::TraceThread, "msghand",
[this] { ThreadMessageHandler(); });
if (connOptions.m_i2p_accept_incoming &&
m_i2p_sam_session.get() != nullptr) {
threadI2PAcceptIncoming =
std::thread(&util::TraceThread, "i2paccept",
[this] { ThreadI2PAcceptIncoming(); });
}
// 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 (threadI2PAcceptIncoming.joinable()) {
threadI2PAcceptIncoming.join();
}
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;
if (m_use_addrman_outgoing) {
// Anchor connections are only dumped during clean shutdown.
std::vector<CAddress> anchors_to_dump =
GetCurrentBlockRelayOnlyConns();
if (anchors_to_dump.size() > MAX_BLOCK_RELAY_ONLY_ANCHORS) {
anchors_to_dump.resize(MAX_BLOCK_RELAY_ONLY_ANCHORS);
}
DumpAnchors(config->GetChainParams(),
gArgs.GetDataDirNet() / ANCHORS_DATABASE_FILENAME,
anchors_to_dump);
}
}
// Delete peer connections.
std::vector<CNode *> nodes;
WITH_LOCK(cs_vNodes, nodes.swap(vNodes));
for (CNode *pnode : nodes) {
pnode->CloseSocketDisconnect();
DeleteNode(pnode);
}
// Close listening sockets.
for (ListenSocket &hListenSocket : vhListenSocket) {
if (hListenSocket.socket != INVALID_SOCKET) {
if (!CloseSocket(hListenSocket.socket)) {
LogPrintf("CloseSocket(hListenSocket) failed with error %s\n",
NetworkErrorString(WSAGetLastError()));
}
}
}
for (CNode *pnode : vNodesDisconnected) {
DeleteNode(pnode);
}
vNodesDisconnected.clear();
vhListenSocket.clear();
semOutbound.reset();
semAddnode.reset();
}
void CConnman::DeleteNode(CNode *pnode) {
assert(pnode);
for (auto interface : m_msgproc) {
interface->FinalizeNode(*config, *pnode);
}
delete pnode;
}
CConnman::~CConnman() {
Interrupt();
Stop();
}
std::vector<CAddress> CConnman::GetAddresses(size_t max_addresses,
size_t max_pct,
std::optional<Network> network) {
std::vector<CAddress> addresses =
addrman.GetAddr(max_addresses, max_pct, network);
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) {
auto local_socket_bytes = requestor.addrBind.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, /* network */ std::nullopt);
// 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());
vstats.back().m_mapped_as = pnode->addr.GetMappedAS(addrman.GetAsmap());
}
}
bool CConnman::DisconnectNode(const std::string &strNode) {
LOCK(cs_vNodes);
if (CNode *pnode = FindNode(strNode)) {
LogPrint(BCLog::NET,
"disconnect by address%s matched peer=%d; disconnecting\n",
(fLogIPs ? strprintf("=%s", strNode) : ""), pnode->GetId());
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)) {
LogPrint(BCLog::NET,
"disconnect by subnet%s matched peer=%d; disconnecting\n",
(fLogIPs ? strprintf("=%s", subnet.ToString()) : ""),
pnode->GetId());
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()) {
LogPrint(BCLog::NET, "disconnect by id peer=%d; disconnecting\n",
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;
const auto now = GetTime<std::chrono::seconds>();
if (nMaxOutboundCycleStartTime + MAX_UPLOAD_TIMEFRAME < now) {
// timeframe expired, reset cycle
nMaxOutboundCycleStartTime = now;
nMaxOutboundTotalBytesSentInCycle = 0;
}
// TODO, exclude peers with download permission
nMaxOutboundTotalBytesSentInCycle += bytes;
}
uint64_t CConnman::GetMaxOutboundTarget() {
LOCK(cs_totalBytesSent);
return nMaxOutboundLimit;
}
std::chrono::seconds CConnman::GetMaxOutboundTimeframe() {
return MAX_UPLOAD_TIMEFRAME;
}
std::chrono::seconds CConnman::GetMaxOutboundTimeLeftInCycle() {
LOCK(cs_totalBytesSent);
if (nMaxOutboundLimit == 0) {
return 0s;
}
if (nMaxOutboundCycleStartTime.count() == 0) {
return MAX_UPLOAD_TIMEFRAME;
}
const std::chrono::seconds cycleEndTime =
nMaxOutboundCycleStartTime + MAX_UPLOAD_TIMEFRAME;
const auto now = GetTime<std::chrono::seconds>();
return (cycleEndTime < now) ? 0s : cycleEndTime - now;
}
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.
const std::chrono::seconds timeLeftInCycle =
GetMaxOutboundTimeLeftInCycle();
const uint64_t buffer =
timeLeftInCycle / std::chrono::minutes{10} * 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;
}
unsigned int CConnman::GetReceiveFloodSize() const {
return nReceiveFloodSize;
}
void CNode::invsPolled(uint32_t count) {
invCounters += count;
}
void CNode::invsVoted(uint32_t count) {
invCounters += uint64_t(count) << 32;
}
void CNode::updateAvailabilityScore() {
if (!m_avalanche_enabled) {
return;
}
uint64_t windowInvCounters = invCounters.exchange(0);
double previousScore = availabilityScore;
int64_t polls = windowInvCounters & std::numeric_limits<uint32_t>::max();
int64_t votes = windowInvCounters >> 32;
availabilityScore =
AVALANCHE_STATISTICS_DECAY_FACTOR * (2 * votes - polls) +
(1. - AVALANCHE_STATISTICS_DECAY_FACTOR) * previousScore;
}
double CNode::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, 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, bool inbound_onion)
: m_connected(GetTime<std::chrono::seconds>()), addr(addrIn),
addrBind(addrBindIn), m_inbound_onion(inbound_onion),
nKeyedNetGroup(nKeyedNetGroupIn),
m_tx_relay(conn_type_in != ConnectionType::BLOCK_RELAY
? std::make_unique<TxRelay>()
: nullptr),
m_proof_relay(isAvalancheEnabled(gArgs) ? std::make_unique<ProofRelay>()
: nullptr),
// 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) {
if (inbound_onion) {
assert(conn_type_in == ConnectionType::INBOUND);
}
hSocket = hSocketIn;
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
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());
if (gArgs.GetBoolArg("-capturemessages", false)) {
CaptureMessage(pnode->addr, msg.m_type, msg.data, /*incoming=*/false);
}
// 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);
}
std::chrono::microseconds
CConnman::PoissonNextSendInbound(std::chrono::microseconds now,
std::chrono::seconds average_interval) {
if (m_next_send_inv_to_incoming.load() < 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);
}
return m_next_send_inv_to_incoming;
}
std::chrono::microseconds
PoissonNextSend(std::chrono::microseconds now,
std::chrono::seconds average_interval) {
double unscaled = -log1p(GetRand(1ULL << 48) *
-0.0000000000000035527136788 /* -1/2^48 */);
return now + std::chrono::duration_cast<std::chrono::microseconds>(
unscaled * average_interval + 0.5us);
}
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.GetAsmap()));
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);
}
void CaptureMessage(const CAddress &addr, const std::string &msg_type,
const Span<const uint8_t> &data, bool is_incoming) {
// Note: This function captures the message at the time of processing,
// not at socket receive/send time.
// This ensures that the messages are always in order from an application
// layer (processing) perspective.
auto now = GetTime<std::chrono::microseconds>();
// Windows folder names can not include a colon
std::string clean_addr = addr.ToString();
std::replace(clean_addr.begin(), clean_addr.end(), ':', '_');
fs::path base_path = gArgs.GetDataDirNet() / "message_capture" / clean_addr;
fs::create_directories(base_path);
fs::path path =
base_path / (is_incoming ? "msgs_recv.dat" : "msgs_sent.dat");
CAutoFile f(fsbridge::fopen(path, "ab"), SER_DISK, CLIENT_VERSION);
ser_writedata64(f, now.count());
f.write(msg_type.data(), msg_type.length());
for (auto i = msg_type.length(); i < CMessageHeader::COMMAND_SIZE; ++i) {
f << '\0';
}
uint32_t size = data.size();
ser_writedata32(f, size);
f.write((const char *)data.data(), data.size());
}
diff --git a/src/net.h b/src/net.h
index de41207f9..adc5cd8ff 100644
--- a/src/net.h
+++ b/src/net.h
@@ -1,1500 +1,1500 @@
// 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 <addrman.h>
#include <avalanche/proofid.h>
#include <avalanche/proofradixtreeadapter.h>
#include <bloom.h>
#include <chainparams.h>
#include <compat.h>
#include <consensus/amount.h>
#include <crypto/siphash.h>
#include <hash.h>
#include <i2p.h>
#include <logging.h>
#include <net_permissions.h>
#include <netaddress.h>
#include <nodeid.h>
#include <protocol.h>
#include <pubkey.h>
#include <radix.h>
#include <random.h>
#include <span.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 <list>
#include <map>
#include <memory>
#include <thread>
#include <vector>
+class AddrMan;
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 constexpr std::chrono::minutes TIMEOUT_INTERVAL{20};
/** Run the feeler connection loop once every 2 minutes. **/
static constexpr auto FEELER_INTERVAL = 2min;
/** Run the extra block-relay-only connection loop once every 5 minutes. **/
static constexpr auto EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 5min;
/** 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 = 16;
/** 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 avalanche enabled outgoing connections by default.
* Can be overridden with the -maxavalancheoutbound option.
*/
static const int DEFAULT_MAX_AVALANCHE_OUTBOUND_CONNECTIONS = 300;
/** Maximum number of feeler connections */
static const int MAX_FEELER_CONNECTIONS = 1;
/** -listen default */
static const bool DEFAULT_LISTEN = true;
/**
* 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 constexpr uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0;
/** Default for blocks only*/
static const bool DEFAULT_BLOCKSONLY = false;
/** -peertimeout default */
static const int64_t DEFAULT_PEER_CONNECT_TIMEOUT = 60;
/** Number of file descriptors required for message capture **/
static const int NUM_FDS_MESSAGE_CAPTURE = 1;
static const bool DEFAULT_FORCEDNSSEED = false;
static const bool DEFAULT_DNSSEED = true;
static const bool DEFAULT_FIXEDSEEDS = true;
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_ANCHORS using addresses from our anchors.dat. Then
* addresses from our AddrMan if MAX_BLOCK_RELAY_ONLY_CONNECTIONS
* isn't reached yet.
*/
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,
/**
* Special case of connection to a full relay outbound with avalanche
* service enabled.
*/
AVALANCHE_OUTBOUND,
};
void Discover();
uint16_t GetListenPort();
enum {
// unknown
LOCAL_NONE,
// address a local interface listens on
LOCAL_IF,
// address explicit bound to
LOCAL_BIND,
// address reported by UPnP or NAT-PMP
LOCAL_MAPPED,
// address explicitly specified (-externalip=)
LOCAL_MANUAL,
LOCAL_MAX
};
bool IsPeerAddrLocalGood(CNode *pnode);
/** Returns a local address that we should advertise to this peer */
std::optional<CAddress> GetLocalAddrForPeer(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;
struct LocalServiceInfo {
int nScore;
uint16_t 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;
std::chrono::seconds m_last_send;
std::chrono::seconds m_last_recv;
std::chrono::seconds m_last_tx_time;
std::chrono::seconds m_last_proof_time;
std::chrono::seconds m_last_block_time;
std::chrono::seconds m_connected;
int64_t nTimeOffset;
std::string addrName;
int nVersion;
std::string cleanSubVer;
bool fInbound;
bool m_manual_connection;
bool m_bip152_highbandwidth_to;
bool m_bip152_highbandwidth_from;
int m_starting_height;
uint64_t nSendBytes;
mapMsgCmdSize mapSendBytesPerMsgCmd;
uint64_t nRecvBytes;
mapMsgCmdSize mapRecvBytesPerMsgCmd;
NetPermissionFlags m_permissionFlags;
bool m_legacyWhitelisted;
std::chrono::microseconds m_last_ping_time;
std::chrono::microseconds m_min_ping_time;
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;
// Network the peer connected through
Network m_network;
uint32_t m_mapped_as;
std::string m_conn_type_string;
std::optional<double> m_availabilityScore;
};
/**
* 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, advances msg_bytes data pointer */
virtual int Read(const Config &config, Span<const uint8_t> &msg_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, Span<const uint8_t> msg_bytes);
int readData(Span<const uint8_t> msg_bytes);
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, Span<const uint8_t> &msg_bytes) override {
int ret = in_data ? readData(msg_bytes) : readHeader(config, msg_bytes);
if (ret < 0) {
Reset();
} else {
msg_bytes = msg_bytes.subspan(ret);
}
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;
uint64_t nRecvBytes GUARDED_BY(cs_vRecv){0};
std::atomic<std::chrono::seconds> m_last_send{0s};
std::atomic<std::chrono::seconds> m_last_recv{0s};
//! Unix epoch time at peer connection
const std::chrono::seconds m_connected;
std::atomic<int64_t> nTimeOffset{0};
// Address of this peer
const CAddress addr;
// Bind address of our side of the connection
const CAddress addrBind;
//! Whether this peer is an inbound onion, i.e. connected via our Tor onion
//! service.
const bool m_inbound_onion;
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};
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};
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:
case ConnectionType::AVALANCHE_OUTBOUND:
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 ||
m_conn_type == ConnectionType::AVALANCHE_OUTBOUND;
}
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;
}
bool IsAvalancheOutboundConnection() const {
return m_conn_type == ConnectionType::AVALANCHE_OUTBOUND;
}
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:
case ConnectionType::AVALANCHE_OUTBOUND:
return true;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
/**
* Get network the peer connected through.
*
* Returns Network::NET_ONION for *inbound* onion connections,
* and CNetAddr::GetNetClass() otherwise. The latter cannot be used directly
* because it doesn't detect the former, and it's not the responsibility of
* the CNetAddr class to know the actual network a peer is connected
* through.
*
* @return network the peer connected through.
*/
Network ConnectedThroughNetwork() const;
protected:
mapMsgCmdSize mapSendBytesPerMsgCmd;
mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv);
public:
// We selected peer as (compact blocks) high-bandwidth peer (BIP152)
std::atomic<bool> m_bip152_highbandwidth_to{false};
// Peer selected us as (compact blocks) high-bandwidth peer (BIP152)
std::atomic<bool> m_bip152_highbandwidth_from{false};
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{0s};
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()};
std::chrono::microseconds m_next_send_feefilter{0};
};
// m_tx_relay == nullptr if we're not relaying transactions with this peer
const 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};
RadixTree<const avalanche::Proof, avalanche::ProofRadixTreeAdapter>
sharedProofs;
std::atomic<std::chrono::seconds> lastSharedProofsUpdate{0s};
std::atomic<bool> compactproofs_requested{false};
};
// m_proof_relay == nullptr if we're not relaying proofs with this peer
const std::unique_ptr<ProofRelay> m_proof_relay;
// True if we know this peer is using Avalanche (at least polling)
std::atomic<bool> m_avalanche_enabled{false};
// Pubkey used to verify signatures on Avalanche messages from this peer
std::optional<CPubKey> m_avalanche_pubkey;
/** 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;
// Store the next time we will consider a getavaaddr message from this peer
std::chrono::seconds m_nextGetAvaAddr{0};
/**
* 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<std::chrono::seconds> m_last_block_time{0s};
/**
* 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<std::chrono::seconds> m_last_tx_time{0s};
/**
* 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<std::chrono::seconds> m_last_proof_time{0s};
/** Last measured round-trip time. Used only for RPC/GUI stats/debugging.*/
std::atomic<std::chrono::microseconds> m_last_ping_time{0us};
/**
* Lowest measured round-trip time. Used as an inbound peer eviction
* criterium in CConnman::AttemptToEvictConnection.
*/
std::atomic<std::chrono::microseconds> m_min_ping_time{
std::chrono::microseconds::max()};
CNode(NodeId id, ServiceFlags nLocalServicesIn, 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, bool inbound_onion);
~CNode();
CNode(const CNode &) = delete;
CNode &operator=(const CNode &) = delete;
/**
* A ping-pong round trip has completed successfully. Update latest and
* minimum ping times.
*/
void PongReceived(std::chrono::microseconds ping_time) {
m_last_ping_time = ping_time;
m_min_ping_time = std::min(m_min_ping_time.load(), ping_time);
}
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;
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;
/**
* The inventories polled and voted counters 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{0};
/** The last computed score */
std::atomic<double> availabilityScore{0.};
public:
NodeId GetId() const { return id; }
uint64_t GetLocalNonce() const { return nLocalHostNonce; }
uint64_t GetLocalExtraEntropy() const { return nLocalExtraEntropy; }
int GetRefCount() const {
assert(nRefCount >= 0);
return nRefCount;
}
/**
* Receive bytes from the buffer and deserialize them into messages.
*
* @param[in] msg_bytes The raw data
* @param[out] complete Set True if at least one message has been
* deserialized and is ready to be processed
* @return True if the peer should stay connected,
* False if the peer should be disconnected from.
*/
bool ReceiveMsgBytes(const Config &config, Span<const uint8_t> msg_bytes,
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 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);
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;
};
/**
* Interface for message handling
*/
class NetEventsInterface {
public:
/** Initialize a peer (setup state, queue any initial messages) */
virtual void InitializeNode(const Config &config, CNode *pnode) = 0;
/** Handle removal of a peer (clear state) */
virtual void FinalizeNode(const Config &config, const CNode &node) = 0;
/**
* Process protocol messages received from a given node
*
* @param[in] config The applicable configuration object.
* @param[in] pnode The node which we have received messages
* from.
* @param[in] interrupt Interrupt condition for processing threads
* @return True if there is more work to be done
*/
virtual bool ProcessMessages(const Config &config, CNode *pnode,
std::atomic<bool> &interrupt) = 0;
/**
* Send queued protocol messages to a given node.
*
* @param[in] config The applicable configuration object.
* @param[in] pnode The node which we are sending messages to.
* @return True if there is more work to be done
*/
virtual bool SendMessages(const Config &config, CNode *pnode)
EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_sendProcessing) = 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;
};
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 m_max_avalanche_outbound = 0;
int nMaxAddnode = 0;
int nMaxFeeler = 0;
CClientUIInterface *uiInterface = nullptr;
std::vector<NetEventsInterface *> m_msgproc;
BanMan *m_banman = nullptr;
unsigned int nSendBufferMaxSize = 0;
unsigned int nReceiveFloodSize = 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;
/// True if the user did not specify -bind= or -whitebind= and thus
/// we should bind on `0.0.0.0` (IPv4) and `::` (IPv6).
bool bind_on_any;
bool m_use_addrman_outgoing = true;
std::vector<std::string> m_specified_outgoing;
std::vector<std::string> m_added_nodes;
bool m_i2p_accept_incoming = true;
};
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_avalanche_outbound = connOptions.m_max_avalanche_outbound;
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 +
m_max_avalanche_outbound;
}
clientInterface = connOptions.uiInterface;
m_banman = connOptions.m_banman;
m_msgproc = connOptions.m_msgproc;
nSendBufferMaxSize = connOptions.nSendBufferMaxSize;
nReceiveFloodSize = connOptions.nReceiveFloodSize;
m_peer_connect_timeout =
std::chrono::seconds{connOptions.m_peer_connect_timeout};
{
LOCK(cs_totalBytesSent);
nMaxOutboundLimit = connOptions.nMaxOutboundLimit;
}
vWhitelistedRange = connOptions.vWhitelistedRange;
{
LOCK(cs_vAddedNodes);
vAddedNodes = connOptions.m_added_nodes;
}
m_onion_binds = connOptions.onion_binds;
}
CConnman(const Config &configIn, uint64_t seed0, uint64_t seed1,
AddrMan &addrmanIn, 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);
}
}
};
// Addrman functions
/**
* Return all or many randomly selected addresses, optionally by network.
*
* @param[in] max_addresses Maximum number of addresses to return
* (0 = all).
* @param[in] max_pct Maximum percentage of addresses to return
* (0 = all).
* @param[in] network Select only addresses of this network
* (nullopt = all).
*/
std::vector<CAddress> GetAddresses(size_t max_addresses, size_t max_pct,
std::optional<Network> network);
/**
* 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();
void StartExtraBlockRelayPeers() {
LogPrint(BCLog::NET, "net: enabling extra block-relay-only peers\n");
m_start_extra_block_relay_peers = true;
}
// 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 GetExtraFullOutboundCount();
// Count the number of block-relay-only peers we have over our limit.
int GetExtraBlockRelayCount();
bool AddNode(const std::string &node);
bool RemoveAddedNode(const std::string &node);
std::vector<AddedNodeInfo> GetAddedNodeInfo();
/**
* Attempts to open a connection. Currently only used from tests.
*
* @param[in] address Address of node to try connecting to
* @param[in] conn_type ConnectionType::OUTBOUND,
* ConnectionType::BLOCK_RELAY,
* ConnectionType::ADDR_FETCH, or
* ConnectionType::FEELER
* @return bool Returns false if there are no available
* slots for this connection:
* - conn_type not a supported ConnectionType
* - Max total outbound connection capacity filled
* - Max connection capacity for type is filled
*/
bool AddConnection(const std::string &address, ConnectionType conn_type);
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;
uint64_t GetMaxOutboundTarget();
std::chrono::seconds 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();
//! returns the time in second left in the current max outbound cycle in
//! case of no limit, it will always return 0
std::chrono::seconds GetMaxOutboundTimeLeftInCycle();
uint64_t GetTotalBytesRecv();
uint64_t GetTotalBytesSent();
/** 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.
*/
std::chrono::microseconds
PoissonNextSendInbound(std::chrono::microseconds now,
std::chrono::seconds average_interval);
/**
* Return true if we should disconnect the peer for failing an inactivity
* check.
*/
bool ShouldRunInactivityChecks(const CNode &node,
std::chrono::seconds now) const;
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 Options &options);
void ThreadOpenAddedConnections();
void AddAddrFetch(const std::string &strDest);
void ProcessAddrFetch();
void
ThreadOpenConnections(std::vector<std::string> connect,
std::function<void(const CAddress &, ConnectionType)>
mockOpenConnection);
void ThreadMessageHandler();
void ThreadI2PAcceptIncoming();
void AcceptConnection(const ListenSocket &hListenSocket);
/**
* Create a `CNode` object from a socket that has just been accepted and add
* the node to the `vNodes` member.
* @param[in] hSocket Connected socket to communicate with the peer.
* @param[in] permissionFlags The peer's permissions.
* @param[in] addr_bind The address and port at our side of the connection.
* @param[in] addr The address and port at the peer's side of the connection
*/
void CreateNodeFromAcceptedSocket(SOCKET hSocket,
NetPermissionFlags permissionFlags,
const CAddress &addr_bind,
const CAddress &addr);
void DisconnectNodes();
void NotifyNumConnectionsChanged();
/** Return true if the peer is inactive and should be disconnected. */
bool InactivityCheck(const CNode &node) const;
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);
/**
* Determine whether we're already connected to a given address, in order to
* avoid initiating duplicate connections.
*/
bool AlreadyConnectedToAddress(const CAddress &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 &node) const
EXCLUSIVE_LOCKS_REQUIRED(node.cs_vSend);
void DumpAddresses();
// Network stats
void RecordBytesRecv(uint64_t bytes);
void RecordBytesSent(uint64_t bytes);
/**
* Return vector of current BLOCK_RELAY peers.
*/
std::vector<CAddress> GetCurrentBlockRelayOnlyConns() const;
// 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){0};
std::chrono::seconds
nMaxOutboundCycleStartTime GUARDED_BY(cs_totalBytesSent){0};
uint64_t nMaxOutboundLimit GUARDED_BY(cs_totalBytesSent);
// P2P timeout in seconds
std::chrono::seconds 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};
AddrMan &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;
// How many avalanche enabled outbound peers we want
int m_max_avalanche_outbound;
int nMaxAddnode;
int nMaxFeeler;
int m_max_outbound;
bool m_use_addrman_outgoing;
CClientUIInterface *clientInterface;
// FIXME m_msgproc is a terrible name
std::vector<NetEventsInterface *> m_msgproc;
/**
* Pointer to this node's banman. May be nullptr - check existence before
* dereferencing.
*/
BanMan *m_banman;
/**
* Addresses that were saved during the previous clean shutdown. We'll
* attempt to make block-relay-only connections to them.
*/
std::vector<CAddress> m_anchors;
/** 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};
/**
* This is signaled when network activity should cease.
* A pointer to it is saved in `m_i2p_sam_session`, so make sure that
* the lifetime of `interruptNet` is not shorter than
* the lifetime of `m_i2p_sam_session`.
*/
CThreadInterrupt interruptNet;
/**
* I2P SAM session.
* Used to accept incoming and make outgoing I2P connections.
*/
std::unique_ptr<i2p::sam::Session> m_i2p_sam_session;
std::thread threadDNSAddressSeed;
std::thread threadSocketHandler;
std::thread threadOpenAddedConnections;
std::thread threadOpenConnections;
std::thread threadMessageHandler;
std::thread threadI2PAcceptIncoming;
/**
* 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;
/**
* flag for initiating extra block-relay-only peer connections.
* this should only be enabled after initial chain sync has occurred,
* as these connections are intended to be short-lived and low-bandwidth.
*/
std::atomic_bool m_start_extra_block_relay_peers{false};
std::atomic<std::chrono::microseconds> m_next_send_inv_to_incoming{0us};
/**
* A vector of -bind=<address>:<port>=onion arguments each of which is
* an address and port that are designated for incoming Tor connections.
*/
std::vector<CService> m_onion_binds;
friend struct ::CConnmanTest;
friend struct ConnmanTestMsg;
};
/**
* Return a timestamp in the future (in microseconds) for exponentially
* distributed events.
*/
std::chrono::microseconds
PoissonNextSend(std::chrono::microseconds now,
std::chrono::seconds average_interval);
std::string getSubVersionEB(uint64_t MaxBlockSize);
std::string userAgent(const Config &config);
/** Dump binary message to file, with timestamp */
void CaptureMessage(const CAddress &addr, const std::string &msg_type,
const Span<const uint8_t> &data, bool is_incoming);
struct NodeEvictionCandidate {
NodeId id;
std::chrono::seconds m_connected;
std::chrono::microseconds m_min_ping_time;
std::chrono::seconds m_last_block_time;
std::chrono::seconds m_last_proof_time;
std::chrono::seconds m_last_tx_time;
bool fRelevantServices;
bool fRelayTxes;
bool fBloomFilter;
uint64_t nKeyedNetGroup;
bool prefer_evict;
bool m_is_local;
Network m_network;
double availabilityScore;
};
/**
* Select an inbound peer to evict after filtering out (protecting) peers having
* distinct, difficult-to-forge characteristics. The protection logic picks out
* fixed numbers of desirable peers per various criteria, followed by (mostly)
* ratios of desirable or disadvantaged peers. If any eviction candidates
* remain, the selection logic chooses a peer to evict.
*/
[[nodiscard]] std::optional<NodeId>
SelectNodeToEvict(std::vector<NodeEvictionCandidate> &&vEvictionCandidates);
/**
* Protect desirable or disadvantaged inbound peers from eviction by ratio.
*
* This function protects half of the peers which have been connected the
* longest, to replicate the non-eviction implicit behavior and preclude attacks
* that start later.
*
* Half of these protected spots (1/4 of the total) are reserved for the
* following categories of peers, sorted by longest uptime, even if they're not
* longest uptime overall:
*
* - onion peers connected via our tor control service
*
* - localhost peers, as manually configured hidden services not using
* `-bind=addr[:port]=onion` will not be detected as inbound onion connections
*
* - I2P peers
*
* This helps protect these privacy network peers, which tend to be otherwise
* disadvantaged under our eviction criteria for their higher min ping times
* relative to IPv4/IPv6 peers, and favorise the diversity of peer connections.
*/
void ProtectEvictionCandidatesByRatio(
std::vector<NodeEvictionCandidate> &vEvictionCandidates);
#endif // BITCOIN_NET_H
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index aa28323d4..86c31119d 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -1,1235 +1,1236 @@
// 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.
#include <rpc/server.h>
+#include <addrman.h>
#include <avalanche/avalanche.h>
#include <banman.h>
#include <chainparams.h>
#include <clientversion.h>
#include <config.h>
#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
#include <net_types.h> // For banmap_t
#include <netbase.h>
#include <node/context.h>
#include <policy/settings.h>
#include <rpc/blockchain.h>
#include <rpc/protocol.h>
#include <rpc/util.h>
#include <sync.h>
#include <timedata.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <validation.h>
#include <version.h>
#include <warnings.h>
#include <optional>
#include <univalue.h>
static RPCHelpMan getconnectioncount() {
return RPCHelpMan{
"getconnectioncount",
"Returns the number of connections to other nodes.\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The connection count"},
RPCExamples{HelpExampleCli("getconnectioncount", "") +
HelpExampleRpc("getconnectioncount", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
return int(node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL));
},
};
}
static RPCHelpMan ping() {
return RPCHelpMan{
"ping",
"Requests that a ping be sent to all other nodes, to measure ping "
"time.\n"
"Results provided in getpeerinfo, pingtime and pingwait fields are "
"decimal seconds.\n"
"Ping command is handled in queue with all other commands, so it "
"measures processing backlog, not just network ping.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("ping", "") + HelpExampleRpc("ping", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.peerman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
// Request that each node send a ping during next message processing
// pass
node.peerman->SendPings();
return NullUniValue;
},
};
}
static RPCHelpMan getpeerinfo() {
return RPCHelpMan{
"getpeerinfo",
"Returns data about each connected network node as a json array of "
"objects.\n",
{},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{{
RPCResult::Type::OBJ,
"",
"",
{{
{RPCResult::Type::NUM, "id", "Peer index"},
{RPCResult::Type::STR, "addr",
"(host:port) The IP address and port of the peer"},
{RPCResult::Type::STR, "addrbind",
"(ip:port) Bind address of the connection to the peer"},
{RPCResult::Type::STR, "addrlocal",
"(ip:port) Local address as reported by the peer"},
{RPCResult::Type::BOOL, "addr_relay_enabled",
"Whether we participate in address relay with this peer"},
{RPCResult::Type::NUM, "addr_processed",
"The total number of addresses processed, excluding those "
"dropped due to rate limiting"},
{RPCResult::Type::NUM, "addr_rate_limited",
"The total number of addresses dropped due to rate "
"limiting"},
{RPCResult::Type::STR, "network",
"Network (" +
Join(GetNetworkNames(/* append_unroutable */ true),
", ") +
")"},
{RPCResult::Type::NUM, "mapped_as",
"The AS in the BGP route to the peer used for "
"diversifying\n"
"peer selection (only available if the asmap config flag "
"is set)\n"},
{RPCResult::Type::STR_HEX, "services",
"The services offered"},
{RPCResult::Type::ARR,
"servicesnames",
"the services offered, in human-readable form",
{{RPCResult::Type::STR, "SERVICE_NAME",
"the service name if it is recognised"}}},
{RPCResult::Type::BOOL, "relaytxes",
"Whether peer has asked us to relay transactions to it"},
{RPCResult::Type::NUM_TIME, "lastsend",
"The " + UNIX_EPOCH_TIME + " of the last send"},
{RPCResult::Type::NUM_TIME, "lastrecv",
"The " + UNIX_EPOCH_TIME + " of the last receive"},
{RPCResult::Type::NUM_TIME, "last_transaction",
"The " + UNIX_EPOCH_TIME +
" of the last valid transaction received from this "
"peer"},
{RPCResult::Type::NUM_TIME, "last_block",
"The " + UNIX_EPOCH_TIME +
" of the last block received from this peer"},
{RPCResult::Type::NUM, "bytessent", "The total bytes sent"},
{RPCResult::Type::NUM, "bytesrecv",
"The total bytes received"},
{RPCResult::Type::NUM_TIME, "conntime",
"The " + UNIX_EPOCH_TIME + " of the connection"},
{RPCResult::Type::NUM, "timeoffset",
"The time offset in seconds"},
{RPCResult::Type::NUM, "pingtime",
"ping time (if available)"},
{RPCResult::Type::NUM, "minping",
"minimum observed ping time (if any at all)"},
{RPCResult::Type::NUM, "pingwait",
"ping wait (if non-zero)"},
{RPCResult::Type::NUM, "version",
"The peer version, such as 70001"},
{RPCResult::Type::STR, "subver", "The string version"},
{RPCResult::Type::BOOL, "inbound",
"Inbound (true) or Outbound (false)"},
{RPCResult::Type::BOOL, "bip152_hb_to",
"Whether we selected peer as (compact blocks) "
"high-bandwidth peer"},
{RPCResult::Type::BOOL, "bip152_hb_from",
"Whether peer selected us as (compact blocks) "
"high-bandwidth peer"},
{RPCResult::Type::STR, "connection_type",
"Type of connection: \n" +
Join(CONNECTION_TYPE_DOC, ",\n") + "."},
{RPCResult::Type::NUM, "startingheight",
"The starting height (block) of the peer"},
{RPCResult::Type::NUM, "synced_headers",
"The last header we have in common with this peer"},
{RPCResult::Type::NUM, "synced_blocks",
"The last block we have in common with this peer"},
{RPCResult::Type::ARR,
"inflight",
"",
{
{RPCResult::Type::NUM, "n",
"The heights of blocks we're currently asking from "
"this peer"},
}},
{RPCResult::Type::NUM, "minfeefilter",
"The minimum fee rate for transactions this peer accepts"},
{RPCResult::Type::OBJ_DYN,
"bytessent_per_msg",
"",
{{RPCResult::Type::NUM, "msg",
"The total bytes sent aggregated by message type\n"
"When a message type is not listed in this json object, "
"the bytes sent are 0.\n"
"Only known message types can appear as keys in the "
"object."}}},
{RPCResult::Type::OBJ,
"bytesrecv_per_msg",
"",
{{RPCResult::Type::NUM, "msg",
"The total bytes received aggregated by message type\n"
"When a message type is not listed in this json object, "
"the bytes received are 0.\n"
"Only known message types can appear as keys in the "
"object and all bytes received\n"
"of unknown message types are listed under '" +
NET_MESSAGE_COMMAND_OTHER + "'."}}},
{RPCResult::Type::NUM, "activity_score",
"Avalanche activity score of this node (if any)"},
}},
}},
},
RPCExamples{HelpExampleCli("getpeerinfo", "") +
HelpExampleRpc("getpeerinfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.connman || !node.peerman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector<CNodeStats> vstats;
node.connman->GetNodeStats(vstats);
UniValue ret(UniValue::VARR);
for (const CNodeStats &stats : vstats) {
UniValue obj(UniValue::VOBJ);
CNodeStateStats statestats;
bool fStateStats =
node.peerman->GetNodeStateStats(stats.nodeid, statestats);
obj.pushKV("id", stats.nodeid);
obj.pushKV("addr", stats.addrName);
if (stats.addrBind.IsValid()) {
obj.pushKV("addrbind", stats.addrBind.ToString());
}
if (!(stats.addrLocal.empty())) {
obj.pushKV("addrlocal", stats.addrLocal);
}
obj.pushKV("addr_relay_enabled",
statestats.m_addr_relay_enabled);
obj.pushKV("network", GetNetworkName(stats.m_network));
if (stats.m_mapped_as != 0) {
obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
}
obj.pushKV("services", strprintf("%016x", stats.nServices));
obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
obj.pushKV("relaytxes", stats.fRelayTxes);
obj.pushKV("lastsend", count_seconds(stats.m_last_send));
obj.pushKV("lastrecv", count_seconds(stats.m_last_recv));
obj.pushKV("last_transaction",
count_seconds(stats.m_last_tx_time));
if (g_avalanche) {
obj.pushKV("last_proof",
count_seconds(stats.m_last_proof_time));
}
obj.pushKV("last_block",
count_seconds(stats.m_last_block_time));
obj.pushKV("bytessent", stats.nSendBytes);
obj.pushKV("bytesrecv", stats.nRecvBytes);
obj.pushKV("conntime", count_seconds(stats.m_connected));
obj.pushKV("timeoffset", stats.nTimeOffset);
if (stats.m_last_ping_time > 0us) {
obj.pushKV("pingtime",
CountSecondsDouble(stats.m_last_ping_time));
}
if (stats.m_min_ping_time < std::chrono::microseconds::max()) {
obj.pushKV("minping",
CountSecondsDouble(stats.m_min_ping_time));
}
if (fStateStats && statestats.m_ping_wait > 0s) {
obj.pushKV("pingwait",
CountSecondsDouble(statestats.m_ping_wait));
}
obj.pushKV("version", stats.nVersion);
// Use the sanitized form of subver here, to avoid tricksy
// remote peers from corrupting or modifying the JSON output by
// putting special characters in their ver message.
obj.pushKV("subver", stats.cleanSubVer);
obj.pushKV("inbound", stats.fInbound);
obj.pushKV("bip152_hb_to", stats.m_bip152_highbandwidth_to);
obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from);
if (fStateStats) {
obj.pushKV("startingheight", statestats.m_starting_height);
obj.pushKV("synced_headers", statestats.nSyncHeight);
obj.pushKV("synced_blocks", statestats.nCommonHeight);
UniValue heights(UniValue::VARR);
for (const int height : statestats.vHeightInFlight) {
heights.push_back(height);
}
obj.pushKV("inflight", heights);
obj.pushKV("addr_processed", statestats.m_addr_processed);
obj.pushKV("addr_rate_limited",
statestats.m_addr_rate_limited);
}
UniValue permissions(UniValue::VARR);
for (const auto &permission :
NetPermissions::ToStrings(stats.m_permissionFlags)) {
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
obj.pushKV("minfeefilter", stats.minFeeFilter);
UniValue sendPerMsgCmd(UniValue::VOBJ);
for (const auto &i : stats.mapSendBytesPerMsgCmd) {
if (i.second > 0) {
sendPerMsgCmd.pushKV(i.first, i.second);
}
}
obj.pushKV("bytessent_per_msg", sendPerMsgCmd);
UniValue recvPerMsgCmd(UniValue::VOBJ);
for (const auto &i : stats.mapRecvBytesPerMsgCmd) {
if (i.second > 0) {
recvPerMsgCmd.pushKV(i.first, i.second);
}
}
obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd);
obj.pushKV("connection_type", stats.m_conn_type_string);
if (stats.m_availabilityScore) {
obj.pushKV("availability_score",
*stats.m_availabilityScore);
}
ret.push_back(obj);
}
return ret;
},
};
}
static RPCHelpMan addnode() {
return RPCHelpMan{
"addnode",
"Attempts to add or remove a node from the addnode list.\n"
"Or try a connection to a node once.\n"
"Nodes added using addnode (or -connect) are protected from "
"DoS disconnection and are not required to be\n"
"full nodes as other outbound peers are (though such peers "
"will not be synced from).\n",
{
{"node", RPCArg::Type::STR, RPCArg::Optional::NO,
"The node (see getpeerinfo for nodes)"},
{"command", RPCArg::Type::STR, RPCArg::Optional::NO,
"'add' to add a node to the list, 'remove' to remove a "
"node from the list, 'onetry' to try a connection to the "
"node once"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") +
HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (strCommand != "onetry" && strCommand != "add" &&
strCommand != "remove") {
throw std::runtime_error(self.ToString());
}
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::string strNode = request.params[0].get_str();
if (strCommand == "onetry") {
CAddress addr;
node.connman->OpenNetworkConnection(addr, false, nullptr,
strNode.c_str(),
ConnectionType::MANUAL);
return NullUniValue;
}
if ((strCommand == "add") && (!node.connman->AddNode(strNode))) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: Node already added");
} else if ((strCommand == "remove") &&
(!node.connman->RemoveAddedNode(strNode))) {
throw JSONRPCError(
RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node could not be removed. It has not been "
"added previously.");
}
return NullUniValue;
},
};
}
static RPCHelpMan addconnection() {
return RPCHelpMan{
"addconnection",
"\nOpen an outbound connection to a specified node. This RPC is for "
"testing only.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The IP address and port to attempt connecting to."},
{"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO,
"Type of connection to open (\"outbound-full-relay\", "
"\"block-relay-only\", \"addr-fetch\", \"feeler\" or "
"\"avalanche\")."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address",
"Address of newly added connection."},
{RPCResult::Type::STR, "connection_type",
"Type of connection opened."},
}},
RPCExamples{
HelpExampleCli("addconnection",
"\"192.168.0.6:8333\" \"outbound-full-relay\"") +
HelpExampleRpc("addconnection",
"\"192.168.0.6:8333\" \"outbound-full-relay\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
if (config.GetChainParams().NetworkIDString() !=
CBaseChainParams::REGTEST) {
throw std::runtime_error("addconnection is for regression "
"testing (-regtest mode) only.");
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR});
const std::string address = request.params[0].get_str();
const std::string conn_type_in{
TrimString(request.params[1].get_str())};
ConnectionType conn_type{};
if (conn_type_in == "outbound-full-relay") {
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
} else if (conn_type_in == "block-relay-only") {
conn_type = ConnectionType::BLOCK_RELAY;
} else if (conn_type_in == "addr-fetch") {
conn_type = ConnectionType::ADDR_FETCH;
} else if (conn_type_in == "feeler") {
conn_type = ConnectionType::FEELER;
} else if (conn_type_in == "avalanche") {
if (!g_avalanche || !isAvalancheEnabled(gArgs)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Error: avalanche outbound requested "
"but avalanche is not enabled.");
}
conn_type = ConnectionType::AVALANCHE_OUTBOUND;
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, self.ToString());
}
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled.");
}
const bool success =
node.connman->AddConnection(address, conn_type);
if (!success) {
throw JSONRPCError(RPC_CLIENT_NODE_CAPACITY_REACHED,
"Error: Already at capacity for specified "
"connection type.");
}
UniValue info(UniValue::VOBJ);
info.pushKV("address", address);
info.pushKV("connection_type", conn_type_in);
return info;
},
};
}
static RPCHelpMan disconnectnode() {
return RPCHelpMan{
"disconnectnode",
"Immediately disconnects from the specified peer node.\n"
"\nStrictly one out of 'address' and 'nodeid' can be provided to "
"identify the node.\n"
"\nTo disconnect by nodeid, either set 'address' to the empty string, "
"or call using the named 'nodeid' argument only.\n",
{
{"address", RPCArg::Type::STR,
/* default */ "fallback to nodeid",
"The IP address/port of the node"},
{"nodeid", RPCArg::Type::NUM,
/* default */ "fallback to address",
"The node ID (see getpeerinfo for node IDs)"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleCli("disconnectnode", "\"\" 1") +
HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleRpc("disconnectnode", "\"\", 1")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
bool success;
const UniValue &address_arg = request.params[0];
const UniValue &id_arg = request.params[1];
if (!address_arg.isNull() && id_arg.isNull()) {
/* handle disconnect-by-address */
success = node.connman->DisconnectNode(address_arg.get_str());
} else if (!id_arg.isNull() && (address_arg.isNull() ||
(address_arg.isStr() &&
address_arg.get_str().empty()))) {
/* handle disconnect-by-id */
NodeId nodeid = (NodeId)id_arg.get_int64();
success = node.connman->DisconnectNode(nodeid);
} else {
throw JSONRPCError(
RPC_INVALID_PARAMS,
"Only one of address and nodeid should be provided.");
}
if (!success) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED,
"Node not found in connected nodes");
}
return NullUniValue;
},
};
}
static RPCHelpMan getaddednodeinfo() {
return RPCHelpMan{
"getaddednodeinfo",
"Returns information about the given added node, or all added nodes\n"
"(note that onetry addnodes are not listed here)\n",
{
{"node", RPCArg::Type::STR, /* default */ "all nodes",
"If provided, return information about this specific node, "
"otherwise all nodes are returned."},
},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "addednode",
"The node IP address or name (as provided to addnode)"},
{RPCResult::Type::BOOL, "connected", "If connected"},
{RPCResult::Type::ARR,
"addresses",
"Only when connected = true",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address",
"The bitcoin server IP and port we're "
"connected to"},
{RPCResult::Type::STR, "connected",
"connection, inbound or outbound"},
}},
}},
}},
}},
RPCExamples{HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") +
HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector<AddedNodeInfo> vInfo = node.connman->GetAddedNodeInfo();
if (!request.params[0].isNull()) {
bool found = false;
for (const AddedNodeInfo &info : vInfo) {
if (info.strAddedNode == request.params[0].get_str()) {
vInfo.assign(1, info);
found = true;
break;
}
}
if (!found) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node has not been added.");
}
}
UniValue ret(UniValue::VARR);
for (const AddedNodeInfo &info : vInfo) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("addednode", info.strAddedNode);
obj.pushKV("connected", info.fConnected);
UniValue addresses(UniValue::VARR);
if (info.fConnected) {
UniValue address(UniValue::VOBJ);
address.pushKV("address", info.resolvedAddress.ToString());
address.pushKV("connected",
info.fInbound ? "inbound" : "outbound");
addresses.push_back(address);
}
obj.pushKV("addresses", addresses);
ret.push_back(obj);
}
return ret;
},
};
}
static RPCHelpMan getnettotals() {
return RPCHelpMan{
"getnettotals",
"Returns information about network traffic, including bytes in, "
"bytes out,\n"
"and current time.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "totalbytesrecv",
"Total bytes received"},
{RPCResult::Type::NUM, "totalbytessent", "Total bytes sent"},
{RPCResult::Type::NUM_TIME, "timemillis",
"Current " + UNIX_EPOCH_TIME + " in milliseconds"},
{RPCResult::Type::OBJ,
"uploadtarget",
"",
{
{RPCResult::Type::NUM, "timeframe",
"Length of the measuring timeframe in seconds"},
{RPCResult::Type::NUM, "target", "Target in bytes"},
{RPCResult::Type::BOOL, "target_reached",
"True if target is reached"},
{RPCResult::Type::BOOL, "serve_historical_blocks",
"True if serving historical blocks"},
{RPCResult::Type::NUM, "bytes_left_in_cycle",
"Bytes left in current time cycle"},
{RPCResult::Type::NUM, "time_left_in_cycle",
"Seconds left in current time cycle"},
}},
}},
RPCExamples{HelpExampleCli("getnettotals", "") +
HelpExampleRpc("getnettotals", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("totalbytesrecv", node.connman->GetTotalBytesRecv());
obj.pushKV("totalbytessent", node.connman->GetTotalBytesSent());
obj.pushKV("timemillis", GetTimeMillis());
UniValue outboundLimit(UniValue::VOBJ);
outboundLimit.pushKV(
"timeframe",
count_seconds(node.connman->GetMaxOutboundTimeframe()));
outboundLimit.pushKV("target",
node.connman->GetMaxOutboundTarget());
outboundLimit.pushKV("target_reached",
node.connman->OutboundTargetReached(false));
outboundLimit.pushKV("serve_historical_blocks",
!node.connman->OutboundTargetReached(true));
outboundLimit.pushKV("bytes_left_in_cycle",
node.connman->GetOutboundTargetBytesLeft());
outboundLimit.pushKV(
"time_left_in_cycle",
count_seconds(node.connman->GetMaxOutboundTimeLeftInCycle()));
obj.pushKV("uploadtarget", outboundLimit);
return obj;
},
};
}
static UniValue GetNetworksInfo() {
UniValue networks(UniValue::VARR);
for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast<enum Network>(n);
if (network == NET_UNROUTABLE || network == NET_CJDNS ||
network == NET_INTERNAL) {
continue;
}
proxyType proxy;
UniValue obj(UniValue::VOBJ);
GetProxy(network, proxy);
obj.pushKV("name", GetNetworkName(network));
obj.pushKV("limited", !IsReachable(network));
obj.pushKV("reachable", IsReachable(network));
obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringIPPort()
: std::string());
obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials);
networks.push_back(obj);
}
return networks;
}
static RPCHelpMan getnetworkinfo() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"getnetworkinfo",
"Returns an object containing various state info regarding P2P "
"networking.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "version", "the server version"},
{RPCResult::Type::STR, "subversion",
"the server subversion string"},
{RPCResult::Type::NUM, "protocolversion",
"the protocol version"},
{RPCResult::Type::STR_HEX, "localservices",
"the services we offer to the network"},
{RPCResult::Type::ARR,
"localservicesnames",
"the services we offer to the network, in human-readable form",
{
{RPCResult::Type::STR, "SERVICE_NAME", "the service name"},
}},
{RPCResult::Type::BOOL, "localrelay",
"true if transaction relay is requested from peers"},
{RPCResult::Type::NUM, "timeoffset", "the time offset"},
{RPCResult::Type::NUM, "connections",
"the total number of connections"},
{RPCResult::Type::NUM, "connections_in",
"the number of inbound connections"},
{RPCResult::Type::NUM, "connections_out",
"the number of outbound connections"},
{RPCResult::Type::BOOL, "networkactive",
"whether p2p networking is enabled"},
{RPCResult::Type::ARR,
"networks",
"information per network",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "name",
"network (" + Join(GetNetworkNames(), ", ") + ")"},
{RPCResult::Type::BOOL, "limited",
"is the network limited using -onlynet?"},
{RPCResult::Type::BOOL, "reachable",
"is the network reachable?"},
{RPCResult::Type::STR, "proxy",
"(\"host:port\") the proxy that is used for this "
"network, or empty if none"},
{RPCResult::Type::BOOL, "proxy_randomize_credentials",
"Whether randomized credentials are used"},
}},
}},
{RPCResult::Type::NUM, "relayfee",
"minimum relay fee for transactions in " + ticker + "/kB"},
{RPCResult::Type::NUM, "excessutxocharge",
"minimum charge for excess utxos in " + ticker},
{RPCResult::Type::ARR,
"localaddresses",
"list of local addresses",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address", "network address"},
{RPCResult::Type::NUM, "port", "network port"},
{RPCResult::Type::NUM, "score", "relative score"},
}},
}},
{RPCResult::Type::STR, "warnings",
"any network and blockchain warnings"},
}},
RPCExamples{HelpExampleCli("getnetworkinfo", "") +
HelpExampleRpc("getnetworkinfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
obj.pushKV("version", CLIENT_VERSION);
obj.pushKV("subversion", userAgent(config));
obj.pushKV("protocolversion", PROTOCOL_VERSION);
NodeContext &node = EnsureAnyNodeContext(request.context);
if (node.connman) {
ServiceFlags services = node.connman->GetLocalServices();
obj.pushKV("localservices", strprintf("%016x", services));
obj.pushKV("localservicesnames", GetServicesNames(services));
}
if (node.peerman) {
obj.pushKV("localrelay", !node.peerman->IgnoresIncomingTxs());
}
obj.pushKV("timeoffset", GetTimeOffset());
if (node.connman) {
obj.pushKV("networkactive", node.connman->GetNetworkActive());
obj.pushKV("connections", int(node.connman->GetNodeCount(
CConnman::CONNECTIONS_ALL)));
obj.pushKV("connections_in", int(node.connman->GetNodeCount(
CConnman::CONNECTIONS_IN)));
obj.pushKV("connections_out", int(node.connman->GetNodeCount(
CConnman::CONNECTIONS_OUT)));
}
obj.pushKV("networks", GetNetworksInfo());
obj.pushKV("relayfee", ::minRelayTxFee.GetFeePerK());
obj.pushKV("excessutxocharge", config.GetExcessUTXOCharge());
UniValue localAddresses(UniValue::VARR);
{
LOCK(cs_mapLocalHost);
for (const std::pair<const CNetAddr, LocalServiceInfo> &item :
mapLocalHost) {
UniValue rec(UniValue::VOBJ);
rec.pushKV("address", item.first.ToString());
rec.pushKV("port", item.second.nPort);
rec.pushKV("score", item.second.nScore);
localAddresses.push_back(rec);
}
}
obj.pushKV("localaddresses", localAddresses);
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
};
}
static RPCHelpMan setban() {
return RPCHelpMan{
"setban",
"Attempts to add or remove an IP/Subnet from the banned list.\n",
{
{"subnet", RPCArg::Type::STR, RPCArg::Optional::NO,
"The IP/Subnet (see getpeerinfo for nodes IP) with an optional "
"netmask (default is /32 = single IP)"},
{"command", RPCArg::Type::STR, RPCArg::Optional::NO,
"'add' to add an IP/Subnet to the list, 'remove' to remove an "
"IP/Subnet from the list"},
{"bantime", RPCArg::Type::NUM, /* default */ "0",
"time in seconds how long (or until when if [absolute] is set) "
"the IP is banned (0 or empty means using the default time of 24h "
"which can also be overwritten by the -bantime startup argument)"},
{"absolute", RPCArg::Type::BOOL, /* default */ "false",
"If set, the bantime must be an absolute timestamp expressed in " +
UNIX_EPOCH_TIME},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") +
HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") +
HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400")},
[&](const RPCHelpMan &help, const Config &config,
const JSONRPCRequest &request) -> UniValue {
std::string strCommand;
if (!request.params[1].isNull()) {
strCommand = request.params[1].get_str();
}
if (strCommand != "add" && strCommand != "remove") {
throw std::runtime_error(help.ToString());
}
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(RPC_DATABASE_ERROR,
"Error: Ban database not loaded");
}
CSubNet subNet;
CNetAddr netAddr;
bool isSubnet = false;
if (request.params[0].get_str().find('/') != std::string::npos) {
isSubnet = true;
}
if (!isSubnet) {
CNetAddr resolved;
LookupHost(request.params[0].get_str(), resolved, false);
netAddr = resolved;
} else {
LookupSubNet(request.params[0].get_str(), subNet);
}
if (!(isSubnet ? subNet.IsValid() : netAddr.IsValid())) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Invalid IP/Subnet");
}
if (strCommand == "add") {
if (isSubnet ? node.banman->IsBanned(subNet)
: node.banman->IsBanned(netAddr)) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: IP/Subnet already banned");
}
// Use standard bantime if not specified.
int64_t banTime = 0;
if (!request.params[2].isNull()) {
banTime = request.params[2].get_int64();
}
bool absolute = false;
if (request.params[3].isTrue()) {
absolute = true;
}
if (isSubnet) {
node.banman->Ban(subNet, banTime, absolute);
if (node.connman) {
node.connman->DisconnectNode(subNet);
}
} else {
node.banman->Ban(netAddr, banTime, absolute);
if (node.connman) {
node.connman->DisconnectNode(netAddr);
}
}
} else if (strCommand == "remove") {
if (!(isSubnet ? node.banman->Unban(subNet)
: node.banman->Unban(netAddr))) {
throw JSONRPCError(
RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Unban failed. Requested address/subnet "
"was not previously manually banned.");
}
}
return NullUniValue;
},
};
}
static RPCHelpMan listbanned() {
return RPCHelpMan{
"listbanned",
"List all manually banned IPs/Subnets.\n",
{},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "address", ""},
{RPCResult::Type::NUM_TIME, "banned_until", ""},
{RPCResult::Type::NUM_TIME, "ban_created", ""},
{RPCResult::Type::STR, "ban_reason", ""},
}},
}},
RPCExamples{HelpExampleCli("listbanned", "") +
HelpExampleRpc("listbanned", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(RPC_DATABASE_ERROR,
"Error: Ban database not loaded");
}
banmap_t banMap;
node.banman->GetBanned(banMap);
UniValue bannedAddresses(UniValue::VARR);
for (const auto &entry : banMap) {
const CBanEntry &banEntry = entry.second;
UniValue rec(UniValue::VOBJ);
rec.pushKV("address", entry.first.ToString());
rec.pushKV("banned_until", banEntry.nBanUntil);
rec.pushKV("ban_created", banEntry.nCreateTime);
bannedAddresses.push_back(rec);
}
return bannedAddresses;
},
};
}
static RPCHelpMan clearbanned() {
return RPCHelpMan{
"clearbanned",
"Clear all banned IPs.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("clearbanned", "") +
HelpExampleRpc("clearbanned", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
node.banman->ClearBanned();
return NullUniValue;
},
};
}
static RPCHelpMan setnetworkactive() {
return RPCHelpMan{
"setnetworkactive",
"Disable/enable all p2p network activity.\n",
{
{"state", RPCArg::Type::BOOL, RPCArg::Optional::NO,
"true to enable networking, false to disable"},
},
RPCResult{RPCResult::Type::BOOL, "", "The value that was passed in"},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
node.connman->SetNetworkActive(request.params[0].get_bool());
return node.connman->GetNetworkActive();
},
};
}
static RPCHelpMan getnodeaddresses() {
return RPCHelpMan{
"getnodeaddresses",
"Return known addresses, which can potentially be used to find new "
"nodes in the network.\n",
{
{"count", RPCArg::Type::NUM, /* default */ "1",
"The maximum number of addresses to return. Specify 0 to return "
"all known addresses."},
{"network", RPCArg::Type::STR, "all networks",
"Return only addresses of the specified network. Can be one of: " +
Join(GetNetworkNames(), ", ") + "."},
},
RPCResult{RPCResult::Type::ARR,
"",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM_TIME, "time",
"The " + UNIX_EPOCH_TIME +
" when the node was last seen"},
{RPCResult::Type::NUM, "services",
"The services offered by the node"},
{RPCResult::Type::STR, "address",
"The address of the node"},
{RPCResult::Type::NUM, "port",
"The port number of the node"},
{RPCResult::Type::STR, "network",
"The network (" + Join(GetNetworkNames(), ", ") +
") the node connected through"},
}},
}},
RPCExamples{HelpExampleCli("getnodeaddresses", "8") +
HelpExampleCli("getnodeaddresses", "4 \"i2p\"") +
HelpExampleCli("-named getnodeaddresses",
"network=onion count=12") +
HelpExampleRpc("getnodeaddresses", "8") +
HelpExampleRpc("getnodeaddresses", "4, \"i2p\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.banman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
const int count{
request.params[0].isNull() ? 1 : request.params[0].get_int()};
if (count < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Address count out of range");
}
const std::optional<Network> network{
request.params[1].isNull()
? std::nullopt
: std::optional<Network>{
ParseNetwork(request.params[1].get_str())}};
if (network == NET_UNROUTABLE) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Network not recognized: %s",
request.params[1].get_str()));
}
// returns a shuffled list of CAddress
const std::vector<CAddress> vAddr{
node.connman->GetAddresses(count, /* max_pct */ 0, network)};
UniValue ret(UniValue::VARR);
for (const CAddress &addr : vAddr) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("time", int(addr.nTime));
obj.pushKV("services", uint64_t(addr.nServices));
obj.pushKV("address", addr.ToStringIP());
obj.pushKV("port", addr.GetPort());
obj.pushKV("network", GetNetworkName(addr.GetNetClass()));
ret.push_back(obj);
}
return ret;
},
};
}
static RPCHelpMan addpeeraddress() {
return RPCHelpMan{
"addpeeraddress",
"Add the address of a potential peer to the address manager. This "
"RPC is for testing only.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO,
"The IP address of the peer"},
{"port", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The port of the peer"},
{"tried", RPCArg::Type::BOOL, "false",
"If true, attempt to add the peer to the tried addresses table"},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "success",
"whether the peer address was successfully added to the "
"address manager"},
},
},
RPCExamples{
HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333 true") +
HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333, true")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
if (!node.addrman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Address manager functionality missing or disabled");
}
const std::string &addr_string{request.params[0].get_str()};
const uint16_t port{
static_cast<uint16_t>(request.params[1].get_int())};
const bool tried{request.params[2].isTrue()};
UniValue obj(UniValue::VOBJ);
CNetAddr net_addr;
bool success{false};
if (LookupHost(addr_string, net_addr, false)) {
CAddress address{{net_addr, port}, ServiceFlags(NODE_NETWORK)};
address.nTime = GetAdjustedTime();
// The source address is set equal to the address. This is
// equivalent to the peer announcing itself.
if (node.addrman->Add({address}, address)) {
success = true;
if (tried) {
// Attempt to move the address to the tried addresses
// table.
node.addrman->Good(address);
}
}
}
obj.pushKV("success", success);
return obj;
},
};
}
void RegisterNetRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ------------------ ----------------------
{ "network", getconnectioncount, },
{ "network", ping, },
{ "network", getpeerinfo, },
{ "network", addnode, },
{ "network", disconnectnode, },
{ "network", getaddednodeinfo, },
{ "network", getnettotals, },
{ "network", getnetworkinfo, },
{ "network", setban, },
{ "network", listbanned, },
{ "network", clearbanned, },
{ "network", setnetworkactive, },
{ "network", getnodeaddresses, },
{ "hidden", addconnection, },
{ "hidden", addpeeraddress, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index 1643ec74c..aa0dc1a80 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -1,1170 +1,1171 @@
// Copyright (c) 2012-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.
#include <net.h>
+#include <addrman.h>
#include <avalanche/avalanche.h>
#include <avalanche/processor.h>
#include <chainparams.h>
#include <clientversion.h>
#include <config.h>
#include <net_processing.h>
#include <netaddress.h>
#include <netbase.h>
#include <serialize.h>
#include <span.h>
#include <streams.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h> // for bilingual_str
#include <version.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <cstdint>
#include <functional>
#include <ios>
#include <memory>
#include <string>
using namespace std::literals;
static CNetAddr ip(uint32_t ip) {
struct in_addr s;
s.s_addr = ip;
return CNetAddr(s);
}
namespace {
struct CConnmanTest : public CConnman {
using CConnman::CConnman;
Mutex cs;
size_t outboundFullRelayCount GUARDED_BY(cs);
size_t avalancheOutboundsCount GUARDED_BY(cs);
std::condition_variable cvar;
NodeId nodeid = 0;
void AddNode(ConnectionType type) {
CAddress addr(
CService(ip(GetRandInt(0xffffffff)), Params().GetDefaultPort()),
NODE_NONE);
return AddNode(addr, type);
}
void AddNode(const CAddress &addr, ConnectionType type) {
ServiceFlags services = NODE_NETWORK;
if (type == ConnectionType::AVALANCHE_OUTBOUND) {
services = ServiceFlags(services | NODE_AVALANCHE);
}
CNode *pnode = new CNode(nodeid++, services, INVALID_SOCKET, addr,
CalculateKeyedNetGroup(addr),
/* nLocalHostNonceIn */ 0,
/* nLocalExtraEntropyIn */ 0, addr,
/* pszDest */ "", type,
/* inbound_onion */ false);
LOCK(cs_vNodes);
vNodes.push_back(pnode);
pnode->fSuccessfullyConnected = true;
}
void ClearNodes() {
LOCK(cs_vNodes);
for (CNode *node : vNodes) {
delete node;
}
vNodes.clear();
}
void SetMaxOutbounds(int maxFullRelayOutbounds, int maxAvalancheOutbounds) {
Options options;
options.nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS;
options.m_max_outbound_full_relay = maxFullRelayOutbounds;
options.m_max_avalanche_outbound = maxAvalancheOutbounds;
Init(options);
};
void MakeAddrmanDeterministic() { addrman.MakeDeterministic(); }
void Init(const Options &connOptions) {
CConnman::Init(connOptions);
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);
}
}
void openNetworkConnection(const CAddress &addrConnect,
ConnectionType connType) {
bool newConnection = !AlreadyConnectedToAddress(addrConnect);
addrman.Attempt(addrConnect, true);
if (newConnection) {
{
LOCK(cs);
if (connType == ConnectionType::AVALANCHE_OUTBOUND) {
avalancheOutboundsCount++;
}
if (connType == ConnectionType::OUTBOUND_FULL_RELAY) {
outboundFullRelayCount++;
}
}
AddNode(addrConnect, connType);
BOOST_CHECK(AlreadyConnectedToAddress(addrConnect));
addrman.Connected(addrConnect);
}
cvar.notify_all();
}
struct TestAddresses {
uint32_t group;
uint32_t services;
size_t quantity;
};
bool checkContiguousAddressesConnection(
const std::vector<TestAddresses> &testAddresses,
size_t expectedOutboundFullRelayCount,
size_t expectedAvalancheOutboundsCount) {
{
LOCK(cs);
// Reset
outboundFullRelayCount = 0;
avalancheOutboundsCount = 0;
}
addrman.Clear();
ClearNodes();
struct IpGen {
uint32_t baseIp;
uint32_t offset;
};
std::vector<IpGen> ipGroups{
{0x00010101, 1}, {0x00010164, 1}, {0x000101c8, 1}, {0x00010201, 1},
{0x00010264, 1}, {0x000102c8, 1}, {0x00010301, 1}, {0x00010364, 1},
{0x000103c8, 1}, {0x00010401, 1}, {0x00010464, 1}, {0x000104c8, 1}};
{
// Make sure we produce addresses in different groups as expected
std::set<std::vector<uint8_t>> groups;
for (auto &[baseIp, _] : ipGroups) {
for (uint32_t j = 0; j < 255; j++) {
CNetAddr addr = ip(baseIp + (j << 24));
groups.insert(addr.GetGroup({}));
}
}
BOOST_CHECK_EQUAL(groups.size(), ipGroups.size());
}
// Generate contiguous addresses
auto getAddrGroup = [&](size_t group, uint64_t services) {
CNetAddr addr =
ip(ipGroups[group].baseIp + (ipGroups[group].offset++ << 24));
return CAddress(CService(addr, Params().GetDefaultPort()),
ServiceFlags(services));
};
size_t addressCount = 0;
for (const TestAddresses &addresses : testAddresses) {
assert(addresses.group < ipGroups.size());
addressCount += addresses.quantity;
do {
addrman.Add({getAddrGroup(addresses.group,
ServiceFlags(addresses.services))},
CNetAddr());
} while (addrman.size() < addressCount);
}
interruptNet.reset();
std::vector<std::string> empty;
threadOpenConnections = std::thread(
&CConnman::ThreadOpenConnections, this, empty,
std::bind(&CConnmanTest::openNetworkConnection, this,
std::placeholders::_1, std::placeholders::_2));
Mutex mutex;
WAIT_LOCK(mutex, lock);
bool ret = cvar.wait_for(lock, 10s, [&]() {
LOCK(cs);
return outboundFullRelayCount == expectedOutboundFullRelayCount &&
avalancheOutboundsCount == expectedAvalancheOutboundsCount;
});
interruptNet();
if (threadOpenConnections.joinable()) {
threadOpenConnections.join();
}
// Check each non avalanche outbound node belongs to a different group
std::set<std::vector<uint8_t>> groups;
ForEachNode([&](const CNode *pnode) {
if (!pnode->IsAvalancheOutboundConnection()) {
groups.insert(pnode->addr.GetGroup({}));
}
});
BOOST_CHECK_EQUAL(groups.size(), expectedOutboundFullRelayCount);
return ret;
}
};
} // namespace
class NetTestConfig : public DummyConfig {
public:
bool SetMaxBlockSize(uint64_t maxBlockSize) override {
nMaxBlockSize = maxBlockSize;
return true;
}
uint64_t GetMaxBlockSize() const override { return nMaxBlockSize; }
private:
uint64_t nMaxBlockSize;
};
// Use TestingSetup or a daughter class so that m_node.addrman is non-null
BOOST_FIXTURE_TEST_SUITE(net_tests, RegTestingSetup)
BOOST_AUTO_TEST_CASE(cnode_listen_port) {
// test default
uint16_t port{GetListenPort()};
BOOST_CHECK(port == Params().GetDefaultPort());
// test set port
uint16_t altPort = 12345;
BOOST_CHECK(gArgs.SoftSetArg("-port", ToString(altPort)));
port = GetListenPort();
BOOST_CHECK(port == altPort);
}
BOOST_AUTO_TEST_CASE(cnode_simple_test) {
SOCKET hSocket = INVALID_SOCKET;
NodeId id = 0;
in_addr ipv4Addr;
ipv4Addr.s_addr = 0xa0b0c001;
CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK);
std::string pszDest;
auto pnode1 =
std::make_unique<CNode>(id++, NODE_NETWORK, hSocket, addr,
/* nKeyedNetGroupIn = */ 0,
/* nLocalHostNonceIn = */ 0,
/* nLocalExtraEntropyIn */ 0, CAddress(),
pszDest, ConnectionType::OUTBOUND_FULL_RELAY,
/* inbound_onion = */ false);
BOOST_CHECK(pnode1->IsFullOutboundConn() == true);
BOOST_CHECK(pnode1->IsManualConn() == false);
BOOST_CHECK(pnode1->IsBlockOnlyConn() == false);
BOOST_CHECK(pnode1->IsFeelerConn() == false);
BOOST_CHECK(pnode1->IsAddrFetchConn() == false);
BOOST_CHECK(pnode1->IsInboundConn() == false);
BOOST_CHECK(pnode1->m_inbound_onion == false);
BOOST_CHECK_EQUAL(pnode1->ConnectedThroughNetwork(), Network::NET_IPV4);
auto pnode2 = std::make_unique<CNode>(id++, NODE_NETWORK, hSocket, addr, 1,
1, 1, CAddress(), pszDest,
ConnectionType::INBOUND, false);
BOOST_CHECK(pnode2->IsFullOutboundConn() == false);
BOOST_CHECK(pnode2->IsManualConn() == false);
BOOST_CHECK(pnode2->IsBlockOnlyConn() == false);
BOOST_CHECK(pnode2->IsFeelerConn() == false);
BOOST_CHECK(pnode2->IsAddrFetchConn() == false);
BOOST_CHECK(pnode2->IsInboundConn() == true);
BOOST_CHECK(pnode2->m_inbound_onion == false);
BOOST_CHECK_EQUAL(pnode2->ConnectedThroughNetwork(), Network::NET_IPV4);
auto pnode3 = std::make_unique<CNode>(
id++, NODE_NETWORK, hSocket, addr, 0, 0, 0, CAddress(), pszDest,
ConnectionType::OUTBOUND_FULL_RELAY, false);
BOOST_CHECK(pnode3->IsFullOutboundConn() == true);
BOOST_CHECK(pnode3->IsManualConn() == false);
BOOST_CHECK(pnode3->IsBlockOnlyConn() == false);
BOOST_CHECK(pnode3->IsFeelerConn() == false);
BOOST_CHECK(pnode3->IsAddrFetchConn() == false);
BOOST_CHECK(pnode3->IsInboundConn() == false);
BOOST_CHECK(pnode3->m_inbound_onion == false);
BOOST_CHECK_EQUAL(pnode3->ConnectedThroughNetwork(), Network::NET_IPV4);
auto pnode4 = std::make_unique<CNode>(id++, NODE_NETWORK, hSocket, addr, 1,
1, 1, CAddress(), pszDest,
ConnectionType::INBOUND, true);
BOOST_CHECK(pnode4->IsFullOutboundConn() == false);
BOOST_CHECK(pnode4->IsManualConn() == false);
BOOST_CHECK(pnode4->IsBlockOnlyConn() == false);
BOOST_CHECK(pnode4->IsFeelerConn() == false);
BOOST_CHECK(pnode4->IsAddrFetchConn() == false);
BOOST_CHECK(pnode4->IsInboundConn() == true);
BOOST_CHECK(pnode4->m_inbound_onion == true);
BOOST_CHECK_EQUAL(pnode4->ConnectedThroughNetwork(), Network::NET_ONION);
}
BOOST_AUTO_TEST_CASE(test_getSubVersionEB) {
BOOST_CHECK_EQUAL(getSubVersionEB(13800000000), "13800.0");
BOOST_CHECK_EQUAL(getSubVersionEB(3800000000), "3800.0");
BOOST_CHECK_EQUAL(getSubVersionEB(14000000), "14.0");
BOOST_CHECK_EQUAL(getSubVersionEB(1540000), "1.5");
BOOST_CHECK_EQUAL(getSubVersionEB(1560000), "1.5");
BOOST_CHECK_EQUAL(getSubVersionEB(210000), "0.2");
BOOST_CHECK_EQUAL(getSubVersionEB(10000), "0.0");
BOOST_CHECK_EQUAL(getSubVersionEB(0), "0.0");
}
BOOST_AUTO_TEST_CASE(test_userAgent) {
NetTestConfig config;
config.SetMaxBlockSize(8000000);
const std::string uacomment = "A very nice comment";
gArgs.ForceSetMultiArg("-uacomment", {uacomment});
const std::string versionMessage =
"/Bitcoin ABC:" + ToString(CLIENT_VERSION_MAJOR) + "." +
ToString(CLIENT_VERSION_MINOR) + "." +
ToString(CLIENT_VERSION_REVISION) + "(EB8.0; " + uacomment + ")/";
BOOST_CHECK_EQUAL(userAgent(config), versionMessage);
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network) {
BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true);
BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true);
SetReachable(NET_IPV4, false);
SetReachable(NET_IPV6, false);
SetReachable(NET_ONION, false);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), false);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), false);
BOOST_CHECK_EQUAL(IsReachable(NET_ONION), false);
SetReachable(NET_IPV4, true);
SetReachable(NET_IPV6, true);
SetReachable(NET_ONION, true);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true);
BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true);
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal) {
BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true);
BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true);
SetReachable(NET_UNROUTABLE, false);
SetReachable(NET_INTERNAL, false);
// Ignored for both networks
BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true);
BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true);
}
CNetAddr UtilBuildAddress(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4) {
uint8_t ip[] = {p1, p2, p3, p4};
struct sockaddr_in sa;
// initialize the memory block
memset(&sa, 0, sizeof(sockaddr_in));
memcpy(&(sa.sin_addr), &ip, sizeof(ip));
return CNetAddr(sa.sin_addr);
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_CNetAddr) {
// 1.1.1.1
CNetAddr addr = UtilBuildAddress(0x001, 0x001, 0x001, 0x001);
SetReachable(NET_IPV4, true);
BOOST_CHECK_EQUAL(IsReachable(addr), true);
SetReachable(NET_IPV4, false);
BOOST_CHECK_EQUAL(IsReachable(addr), false);
// have to reset this, because this is stateful.
SetReachable(NET_IPV4, true);
}
BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) {
// 2.1.1.1:1000
CService addr =
CService(UtilBuildAddress(0x002, 0x001, 0x001, 0x001), 1000);
SetReachable(NET_IPV4, true);
BOOST_CHECK_EQUAL(IsLocal(addr), false);
BOOST_CHECK_EQUAL(AddLocal(addr, 1000), true);
BOOST_CHECK_EQUAL(IsLocal(addr), true);
RemoveLocal(addr);
BOOST_CHECK_EQUAL(IsLocal(addr), false);
}
BOOST_AUTO_TEST_CASE(cnetaddr_basic) {
CNetAddr addr;
// IPv4, INADDR_ANY
BOOST_REQUIRE(LookupHost("0.0.0.0", addr, false));
BOOST_REQUIRE(!addr.IsValid());
BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "0.0.0.0");
// IPv4, INADDR_NONE
BOOST_REQUIRE(LookupHost("255.255.255.255", addr, false));
BOOST_REQUIRE(!addr.IsValid());
BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "255.255.255.255");
// IPv4, casual
BOOST_REQUIRE(LookupHost("12.34.56.78", addr, false));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsIPv4());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "12.34.56.78");
// IPv6, in6addr_any
BOOST_REQUIRE(LookupHost("::", addr, false));
BOOST_REQUIRE(!addr.IsValid());
BOOST_REQUIRE(addr.IsIPv6());
BOOST_CHECK(addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "::");
// IPv6, casual
BOOST_REQUIRE(
LookupHost("1122:3344:5566:7788:9900:aabb:ccdd:eeff", addr, false));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsIPv6());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(),
"1122:3344:5566:7788:9900:aabb:ccdd:eeff");
// IPv6, scoped/link-local. See https://tools.ietf.org/html/rfc4007
// We support non-negative decimal integers (uint32_t) as zone id indices.
// Test with a fairly-high value, e.g. 32, to avoid locally reserved ids.
const std::string link_local{"fe80::1"};
const std::string scoped_addr{link_local + "%32"};
BOOST_REQUIRE(LookupHost(scoped_addr, addr, false));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsIPv6());
BOOST_CHECK(!addr.IsBindAny());
// Test that the delimiter "%" and default zone id of 0 can be omitted for
// the default scope.
BOOST_REQUIRE(LookupHost(link_local + "%0", addr, false));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsIPv6());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK_EQUAL(addr.ToString(), link_local);
// TORv2
BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion"));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsTor());
BOOST_CHECK(!addr.IsI2P());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion");
// TORv3
const char *torv3_addr =
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion";
BOOST_REQUIRE(addr.SetSpecial(torv3_addr));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsTor());
BOOST_CHECK(!addr.IsI2P());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr);
// TORv3, broken, with wrong checksum
BOOST_CHECK(!addr.SetSpecial(
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.onion"));
// TORv3, broken, with wrong version
BOOST_CHECK(!addr.SetSpecial(
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscrye.onion"));
// TORv3, malicious
BOOST_CHECK(!addr.SetSpecial(std::string{
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd\0wtf.onion",
66}));
// TOR, bogus length
BOOST_CHECK(!addr.SetSpecial(std::string{"mfrggzak.onion"}));
// TOR, invalid base32
BOOST_CHECK(!addr.SetSpecial(std::string{"mf*g zak.onion"}));
// I2P
const char *i2p_addr =
"UDHDrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.I2P";
BOOST_REQUIRE(addr.SetSpecial(i2p_addr));
BOOST_REQUIRE(addr.IsValid());
BOOST_REQUIRE(addr.IsI2P());
BOOST_CHECK(!addr.IsTor());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), ToLower(i2p_addr));
// I2P, correct length, but decodes to less than the expected number of
// bytes.
BOOST_CHECK(!addr.SetSpecial(
"udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jn=.b32.i2p"));
// I2P, extra unnecessary padding
BOOST_CHECK(!addr.SetSpecial(
"udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna=.b32.i2p"));
// I2P, malicious
BOOST_CHECK(!addr.SetSpecial(
"udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v\0wtf.b32.i2p"s));
// I2P, valid but unsupported (56 Base32 characters)
// See "Encrypted LS with Base 32 Addresses" in
// https://geti2p.net/spec/encryptedleaseset.txt
BOOST_CHECK(!addr.SetSpecial(
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.b32.i2p"));
// I2P, invalid base32
BOOST_CHECK(!addr.SetSpecial(std::string{"tp*szydbh4dp.b32.i2p"}));
// Internal
addr.SetInternal("esffpp");
// "internal" is considered invalid
BOOST_REQUIRE(!addr.IsValid());
BOOST_REQUIRE(addr.IsInternal());
BOOST_CHECK(!addr.IsBindAny());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal");
// Totally bogus
BOOST_CHECK(!addr.SetSpecial("totally bogus"));
}
BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v1) {
CNetAddr addr;
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000000000000000");
s.clear();
BOOST_REQUIRE(LookupHost("1.2.3.4", addr, false));
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000ffff01020304");
s.clear();
BOOST_REQUIRE(
LookupHost("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b", addr, false));
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "1a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b");
s.clear();
BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion"));
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "fd87d87eeb43f1f2f3f4f5f6f7f8f9fa");
s.clear();
BOOST_REQUIRE(addr.SetSpecial(
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"));
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000000000000000");
s.clear();
addr.SetInternal("a");
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "fd6b88c08724ca978112ca1bbdcafac2");
s.clear();
}
BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v2) {
CNetAddr addr;
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
// Add ADDRV2_FORMAT to the version so that the CNetAddr
// serialize method produces an address in v2 format.
s.SetVersion(s.GetVersion() | ADDRV2_FORMAT);
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "021000000000000000000000000000000000");
s.clear();
BOOST_REQUIRE(LookupHost("1.2.3.4", addr, false));
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "010401020304");
s.clear();
BOOST_REQUIRE(
LookupHost("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b", addr, false));
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "02101a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b");
s.clear();
BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion"));
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "030af1f2f3f4f5f6f7f8f9fa");
s.clear();
BOOST_REQUIRE(addr.SetSpecial(
"kpgvmscirrdqpekbqjsvw5teanhatztpp2gl6eee4zkowvwfxwenqaid.onion"));
s << addr;
BOOST_CHECK_EQUAL(
HexStr(s),
"042053cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88");
s.clear();
BOOST_REQUIRE(addr.SetInternal("a"));
s << addr;
BOOST_CHECK_EQUAL(HexStr(s), "0210fd6b88c08724ca978112ca1bbdcafac2");
s.clear();
}
BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) {
CNetAddr addr;
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
// Add ADDRV2_FORMAT to the version so that the CNetAddr
// unserialize method expects an address in v2 format.
s.SetVersion(s.GetVersion() | ADDRV2_FORMAT);
// Valid IPv4.
s << MakeSpan(ParseHex("01" // network type (IPv4)
"04" // address length
"01020304")); // address
s >> addr;
BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsIPv4());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "1.2.3.4");
BOOST_REQUIRE(s.empty());
// Invalid IPv4, valid length but address itself is shorter.
s << MakeSpan(ParseHex("01" // network type (IPv4)
"04" // address length
"0102")); // address
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure,
HasReason("end of data"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Invalid IPv4, with bogus length.
s << MakeSpan(ParseHex("01" // network type (IPv4)
"05" // address length
"01020304")); // address
BOOST_CHECK_EXCEPTION(
s >> addr, std::ios_base::failure,
HasReason("BIP155 IPv4 address with length 5 (should be 4)"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Invalid IPv4, with extreme length.
s << MakeSpan(ParseHex("01" // network type (IPv4)
"fd0102" // address length (513 as CompactSize)
"01020304")); // address
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure,
HasReason("Address too long: 513 > 512"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Valid IPv6.
s << MakeSpan(ParseHex("02" // network type (IPv6)
"10" // address length
"0102030405060708090a0b0c0d0e0f10")); // address
s >> addr;
BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsIPv6());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "102:304:506:708:90a:b0c:d0e:f10");
BOOST_REQUIRE(s.empty());
// Valid IPv6, contains embedded "internal".
s << MakeSpan(
ParseHex("02" // network type (IPv6)
"10" // address length
"fd6b88c08724ca978112ca1bbdcafac2")); // address: 0xfd +
// sha256("bitcoin")[0:5]
// + sha256(name)[0:10]
s >> addr;
BOOST_CHECK(addr.IsInternal());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "zklycewkdo64v6wc.internal");
BOOST_REQUIRE(s.empty());
// Invalid IPv6, with bogus length.
s << MakeSpan(ParseHex("02" // network type (IPv6)
"04" // address length
"00")); // address
BOOST_CHECK_EXCEPTION(
s >> addr, std::ios_base::failure,
HasReason("BIP155 IPv6 address with length 4 (should be 16)"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Invalid IPv6, contains embedded IPv4.
s << MakeSpan(ParseHex("02" // network type (IPv6)
"10" // address length
"00000000000000000000ffff01020304")); // address
s >> addr;
BOOST_CHECK(!addr.IsValid());
BOOST_REQUIRE(s.empty());
// Invalid IPv6, contains embedded TORv2.
s << MakeSpan(ParseHex("02" // network type (IPv6)
"10" // address length
"fd87d87eeb430102030405060708090a")); // address
s >> addr;
BOOST_CHECK(!addr.IsValid());
BOOST_REQUIRE(s.empty());
// Valid TORv2.
s << MakeSpan(ParseHex("03" // network type (TORv2)
"0a" // address length
"f1f2f3f4f5f6f7f8f9fa")); // address
s >> addr;
BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsTor());
BOOST_CHECK(addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion");
BOOST_REQUIRE(s.empty());
// Invalid TORv2, with bogus length.
s << MakeSpan(ParseHex("03" // network type (TORv2)
"07" // address length
"00")); // address
BOOST_CHECK_EXCEPTION(
s >> addr, std::ios_base::failure,
HasReason("BIP155 TORv2 address with length 7 (should be 10)"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Valid TORv3.
s << MakeSpan(ParseHex("04" // network type (TORv3)
"20" // address length
"79bcc625184b05194975c28b66b66b04" // address
"69f7f6556fb1ac3189a79b40dda32f1f"));
s >> addr;
BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsTor());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(
addr.ToString(),
"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion");
BOOST_REQUIRE(s.empty());
// Invalid TORv3, with bogus length.
s << MakeSpan(ParseHex("04" // network type (TORv3)
"00" // address length
"00" // address
));
BOOST_CHECK_EXCEPTION(
s >> addr, std::ios_base::failure,
HasReason("BIP155 TORv3 address with length 0 (should be 32)"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Valid I2P.
s << MakeSpan(ParseHex("05" // network type (I2P)
"20" // address length
"a2894dabaec08c0051a481a6dac88b64" // address
"f98232ae42d4b6fd2fa81952dfe36a87"));
s >> addr;
BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsI2P());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(
addr.ToString(),
"ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p");
BOOST_REQUIRE(s.empty());
// Invalid I2P, with bogus length.
s << MakeSpan(ParseHex("05" // network type (I2P)
"03" // address length
"00" // address
));
BOOST_CHECK_EXCEPTION(
s >> addr, std::ios_base::failure,
HasReason("BIP155 I2P address with length 3 (should be 32)"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Valid CJDNS.
s << MakeSpan(ParseHex("06" // network type (CJDNS)
"10" // address length
"fc000001000200030004000500060007" // address
));
s >> addr;
BOOST_CHECK(addr.IsValid());
BOOST_CHECK(addr.IsCJDNS());
BOOST_CHECK(!addr.IsAddrV1Compatible());
BOOST_CHECK_EQUAL(addr.ToString(), "fc00:1:2:3:4:5:6:7");
BOOST_REQUIRE(s.empty());
// Invalid CJDNS, with bogus length.
s << MakeSpan(ParseHex("06" // network type (CJDNS)
"01" // address length
"00" // address
));
BOOST_CHECK_EXCEPTION(
s >> addr, std::ios_base::failure,
HasReason("BIP155 CJDNS address with length 1 (should be 16)"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Unknown, with extreme length.
s << MakeSpan(
ParseHex("aa" // network type (unknown)
"fe00000002" // address length (CompactSize's MAX_SIZE)
"01020304050607" // address
));
BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure,
HasReason("Address too long: 33554432 > 512"));
BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input.
s.clear();
// Unknown, with reasonable length.
s << MakeSpan(ParseHex("aa" // network type (unknown)
"04" // address length
"01020304" // address
));
s >> addr;
BOOST_CHECK(!addr.IsValid());
BOOST_REQUIRE(s.empty());
// Unknown, with zero length.
s << MakeSpan(ParseHex("aa" // network type (unknown)
"00" // address length
"" // address
));
s >> addr;
BOOST_CHECK(!addr.IsValid());
BOOST_REQUIRE(s.empty());
}
// prior to PR #14728, this test triggers an undefined behavior
BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) {
// set up local addresses; all that's necessary to reproduce the bug is
// that a normal IPv4 address is among the entries, but if this address is
// !IsRoutable the undefined behavior is easier to trigger deterministically
{
LOCK(cs_mapLocalHost);
in_addr ipv4AddrLocal;
ipv4AddrLocal.s_addr = 0x0100007f;
CNetAddr addr = CNetAddr(ipv4AddrLocal);
LocalServiceInfo lsi;
lsi.nScore = 23;
lsi.nPort = 42;
mapLocalHost[addr] = lsi;
}
// create a peer with an IPv4 address
in_addr ipv4AddrPeer;
ipv4AddrPeer.s_addr = 0xa0b0c001;
CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK);
std::unique_ptr<CNode> pnode = std::make_unique<CNode>(
0, NODE_NETWORK, INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0,
/* nLocalHostNonceIn */ 0, /* nLocalExtraEntropyIn */ 0, CAddress{},
/* pszDest */ std::string{}, ConnectionType::OUTBOUND_FULL_RELAY,
/* inbound_onion = */ false);
pnode->fSuccessfullyConnected.store(true);
// the peer claims to be reaching us via IPv6
in6_addr ipv6AddrLocal;
memset(ipv6AddrLocal.s6_addr, 0, 16);
ipv6AddrLocal.s6_addr[0] = 0xcc;
CAddress addrLocal = CAddress(CService(ipv6AddrLocal, 7777), NODE_NETWORK);
pnode->SetAddrLocal(addrLocal);
// before patch, this causes undefined behavior detectable with clang's
// -fsanitize=memory
GetLocalAddrForPeer(&*pnode);
// suppress no-checks-run warning; if this test fails, it's by triggering a
// sanitizer
BOOST_CHECK(1);
}
BOOST_AUTO_TEST_CASE(avalanche_statistics) {
const uint32_t step = AVALANCHE_STATISTICS_REFRESH_PERIOD.count();
const uint32_t tau = AVALANCHE_STATISTICS_TIME_CONSTANT.count();
in_addr ipv4Addr;
ipv4Addr.s_addr = 0xa0b0c001;
CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK);
std::unique_ptr<CNode> pnode = std::make_unique<CNode>(
0, NODE_NETWORK, INVALID_SOCKET, addr, 0, 0, 0, CAddress(),
std::string{}, ConnectionType::OUTBOUND_FULL_RELAY, false);
pnode->m_avalanche_enabled = true;
double previousScore = pnode->getAvailabilityScore();
BOOST_CHECK_SMALL(previousScore, 1e-6);
// Check the statistics follow an exponential response for 1 to 10 tau
for (size_t i = 1; i <= 10; i++) {
for (uint32_t j = 0; j < tau; j += step) {
pnode->invsPolled(1);
// Always respond to everything correctly
pnode->invsVoted(1);
pnode->updateAvailabilityScore();
// Expect a monotonic rise
double currentScore = pnode->getAvailabilityScore();
BOOST_CHECK_GE(currentScore, previousScore);
previousScore = currentScore;
}
// We expect (1 - e^-i) after i * tau. The tolerance is expressed
// as a percentage, and we add a (large) 0.1% margin to account for
// floating point errors.
BOOST_CHECK_CLOSE(previousScore, -1 * std::expm1(-1. * i), 100.1 / tau);
}
// After 10 tau we should be very close to 100% (about 99.995%)
BOOST_CHECK_CLOSE(previousScore, 1., 0.01);
for (size_t i = 1; i <= 3; i++) {
for (uint32_t j = 0; j < tau; j += step) {
pnode->invsPolled(2);
// Stop responding to the polls.
pnode->invsVoted(1);
pnode->updateAvailabilityScore();
// Expect a monotonic fall
double currentScore = pnode->getAvailabilityScore();
BOOST_CHECK_LE(currentScore, previousScore);
previousScore = currentScore;
}
// There is a slight error in the expected value because we did not
// start the decay at exactly 100%, but the 0.1% margin is at least an
// order of magnitude larger than the expected error so it doesn't
// matter.
BOOST_CHECK_CLOSE(previousScore, 1. + std::expm1(-1. * i), 100.1 / tau);
}
// After 3 more tau we should be under 5%
BOOST_CHECK_LT(previousScore, .05);
for (size_t i = 1; i <= 100; i++) {
pnode->invsPolled(10);
// Completely stop responding to the polls.
pnode->invsVoted(0);
pnode->updateAvailabilityScore();
// It's still a monotonic fall, and the score should turn negative.
double currentScore = pnode->getAvailabilityScore();
BOOST_CHECK_LE(currentScore, previousScore);
BOOST_CHECK_LE(currentScore, 0.);
previousScore = currentScore;
}
}
BOOST_AUTO_TEST_CASE(get_extra_full_outbound_count) {
CConnmanTest connman(GetConfig(), 0x1337, 0x1337, *m_node.addrman);
auto checkExtraFullOutboundCount = [&](size_t fullOutboundCount,
size_t avalancheOutboundCount,
int expectedExtraCount) {
connman.ClearNodes();
for (size_t i = 0; i < fullOutboundCount; i++) {
connman.AddNode(ConnectionType::OUTBOUND_FULL_RELAY);
}
for (size_t i = 0; i < avalancheOutboundCount; i++) {
connman.AddNode(ConnectionType::AVALANCHE_OUTBOUND);
}
BOOST_CHECK_EQUAL(connman.GetExtraFullOutboundCount(),
expectedExtraCount);
};
connman.SetMaxOutbounds(0, 0);
checkExtraFullOutboundCount(0, 0, 0);
checkExtraFullOutboundCount(1, 0, 1);
checkExtraFullOutboundCount(0, 1, 1);
checkExtraFullOutboundCount(5, 5, 10);
connman.SetMaxOutbounds(4, 0);
checkExtraFullOutboundCount(0, 0, 0);
checkExtraFullOutboundCount(1, 0, 0);
checkExtraFullOutboundCount(0, 1, 0);
checkExtraFullOutboundCount(4, 0, 0);
checkExtraFullOutboundCount(0, 4, 0);
checkExtraFullOutboundCount(2, 2, 0);
checkExtraFullOutboundCount(5, 5, 6);
connman.SetMaxOutbounds(4, 4);
checkExtraFullOutboundCount(0, 0, 0);
checkExtraFullOutboundCount(1, 0, 0);
checkExtraFullOutboundCount(0, 1, 0);
checkExtraFullOutboundCount(4, 0, 0);
checkExtraFullOutboundCount(0, 4, 0);
checkExtraFullOutboundCount(4, 4, 0);
checkExtraFullOutboundCount(5, 5, 2);
}
BOOST_FIXTURE_TEST_CASE(net_group_limit, TestChain100Setup) {
const CChainParams &params = GetConfig().GetChainParams();
m_node.connman = std::make_unique<CConnmanTest>(GetConfig(), 0x1337, 0x1337,
*m_node.addrman);
m_node.peerman = PeerManager::make(params, *m_node.connman, *m_node.addrman,
m_node.banman.get(), *m_node.chainman,
*m_node.mempool, false);
bilingual_str error;
// Init the global avalanche object otherwise the avalanche outbound
// slots are not allocated.
g_avalanche = avalanche::Processor::MakeProcessor(
*m_node.args, *m_node.chain, m_node.connman.get(), *m_node.chainman,
*m_node.scheduler, error);
BOOST_CHECK(g_avalanche);
CConnman::Options options;
options.nMaxConnections = 200;
options.m_max_outbound_full_relay = 8;
options.m_max_avalanche_outbound = 60;
auto connman = static_cast<CConnmanTest *>(m_node.connman.get());
connman->MakeAddrmanDeterministic();
connman->Init(options);
// Single full relay outbound is no problem
BOOST_CHECK(connman->checkContiguousAddressesConnection(
{
// group, services, quantity
{0, NODE_NETWORK, 1},
},
1, // Expected full-relay outbound count
0 // Expected avalanche outbound count
));
// Adding more contiguous full relay outbounds fails due to network group
// limitation
BOOST_CHECK(connman->checkContiguousAddressesConnection(
{
// group, services, quantity
{0, NODE_NETWORK, 3},
},
1, // Expected full-relay outbound count
0 // Expected avalanche outbound count
));
// Outbounds from different groups can be connected
BOOST_CHECK(connman->checkContiguousAddressesConnection(
{
// group, services, quantity
{0, NODE_NETWORK, 1},
{1, NODE_NETWORK, 1},
{2, NODE_NETWORK, 1},
},
3, // Expected full-relay outbound count
0 // Expected avalanche outbound count
));
// Up to the max
BOOST_CHECK(connman->checkContiguousAddressesConnection(
{
// group, services, quantity
{0, NODE_NETWORK, 1},
{1, NODE_NETWORK, 1},
{2, NODE_NETWORK, 1},
{3, NODE_NETWORK, 1},
{4, NODE_NETWORK, 1},
{5, NODE_NETWORK, 1},
{6, NODE_NETWORK, 1},
{7, NODE_NETWORK, 1},
{8, NODE_NETWORK, 1},
{9, NODE_NETWORK, 1},
{10, NODE_NETWORK, 1},
{11, NODE_NETWORK, 1},
},
options.m_max_outbound_full_relay, // Expected full-relay outbound count
0 // Expected avalanche outbound count
));
// Avalanche outbounds are prioritized, so contiguous full relay outbounds
// will fail due to network group limitation
BOOST_CHECK(connman->checkContiguousAddressesConnection(
{
// group, services, quantity
{0, NODE_NETWORK | NODE_AVALANCHE, 1},
{0, NODE_NETWORK, 3},
},
0, // Expected full-relay outbound count
1 // Expected avalanche outbound count
));
// Adding more avalanche outbounds is fine
BOOST_CHECK(connman->checkContiguousAddressesConnection(
{
// group, services, quantity
{0, NODE_NETWORK | NODE_AVALANCHE, 3},
{0, NODE_NETWORK, 3},
},
0, // Expected full-relay outbound count
3 // Expected avalanche outbound count
));
// Group limit still applies to non avalanche outbounds, which also remain
// capped to the max from the connman options.
BOOST_CHECK(connman->checkContiguousAddressesConnection(
{
// group, services, quantity
{0, NODE_NETWORK | NODE_AVALANCHE, 50},
{1, NODE_NETWORK, 10},
{2, NODE_NETWORK, 10},
{3, NODE_NETWORK, 10},
{4, NODE_NETWORK, 10},
{5, NODE_NETWORK, 10},
{6, NODE_NETWORK, 10},
{7, NODE_NETWORK, 10},
{8, NODE_NETWORK, 10},
{9, NODE_NETWORK, 10},
{10, NODE_NETWORK, 10},
{11, NODE_NETWORK, 10},
},
options.m_max_outbound_full_relay, // Expected full-relay outbound count
50 // Expected avalanche outbound count
));
g_avalanche.reset();
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py
index 2490d286b..691690ea0 100755
--- a/test/functional/p2p_invalid_block.py
+++ b/test/functional/p2p_invalid_block.py
@@ -1,186 +1,185 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test node responses to invalid blocks.
In this test we connect to one node over p2p, and test block requests:
1) Valid blocks should be requested and become chain tip.
2) Invalid block with duplicated transaction should be re-requested.
3) Invalid block with bad coinbase value should be rejected and not
re-requested.
4) Invalid block due to future timestamp is later accepted when that timestamp
becomes valid.
"""
import copy
import time
from test_framework.blocktools import (
+ MAX_FUTURE_BLOCK_TIME,
create_block,
create_coinbase,
create_tx_with_script,
make_conform_to_ctor,
)
from test_framework.messages import COIN
from test_framework.p2p import P2PDataStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
-MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60
-
class InvalidBlockRequestTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.extra_args = [["-whitelist=noban@127.0.0.1"]]
def run_test(self):
# Add p2p connection to node0
node = self.nodes[0] # convenience reference to the node
peer = node.add_p2p_connection(P2PDataStore())
best_block = node.getblock(node.getbestblockhash())
tip = int(node.getbestblockhash(), 16)
height = best_block["height"] + 1
block_time = best_block["time"] + 1
self.log.info("Create a new block with an anyone-can-spend coinbase")
height = 1
block = create_block(tip, create_coinbase(height), block_time)
block.solve()
# Save the coinbase for later
block1 = block
tip = block.sha256
peer.send_blocks_and_test([block1], node, success=True)
self.log.info("Mature the block.")
self.generatetoaddress(
node, 100, node.get_deterministic_priv_key().address)
best_block = node.getblock(node.getbestblockhash())
tip = int(node.getbestblockhash(), 16)
height = best_block["height"] + 1
block_time = best_block["time"] + 1
# Use merkle-root malleability to generate an invalid block with
# same blockheader (CVE-2012-2459).
# Manufacture a block with 3 transactions (coinbase, spend of prior
# coinbase, spend of that spend). Duplicate the 3rd transaction to
# leave merkle root and blockheader unchanged but invalidate the block.
# For more information on merkle-root malleability see
# src/consensus/merkle.cpp.
self.log.info("Test merkle root malleability.")
block2 = create_block(tip, create_coinbase(height), block_time)
block_time += 1
# b'0x51' is OP_TRUE
tx1 = create_tx_with_script(
block1.vtx[0], 0, script_sig=b'', amount=50 * COIN)
tx2 = create_tx_with_script(
tx1, 0, script_sig=b'\x51', amount=50 * COIN)
block2.vtx.extend([tx1, tx2])
block2.vtx = [block2.vtx[0]] + \
sorted(block2.vtx[1:], key=lambda tx: tx.get_id())
block2.hashMerkleRoot = block2.calc_merkle_root()
block2.rehash()
block2.solve()
orig_hash = block2.sha256
block2_orig = copy.deepcopy(block2)
# Mutate block 2
block2.vtx.append(block2.vtx[2])
assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root())
assert_equal(orig_hash, block2.rehash())
assert block2_orig.vtx != block2.vtx
peer.send_blocks_and_test(
[block2], node, success=False, reject_reason='bad-txns-duplicate')
# Check transactions for duplicate inputs (CVE-2018-17144)
self.log.info("Test duplicate input block.")
block2_dup = copy.deepcopy(block2_orig)
block2_dup.vtx[2].vin.append(block2_dup.vtx[2].vin[0])
block2_dup.vtx[2].rehash()
make_conform_to_ctor(block2_dup)
block2_dup.hashMerkleRoot = block2_dup.calc_merkle_root()
block2_dup.rehash()
block2_dup.solve()
peer.send_blocks_and_test(
[block2_dup], node, success=False,
reject_reason='bad-txns-inputs-duplicate')
self.log.info("Test very broken block.")
block3 = create_block(tip, create_coinbase(height), block_time)
block_time += 1
block3.vtx[0].vout[0].nValue = 100 * COIN # Too high!
block3.vtx[0].sha256 = None
block3.vtx[0].calc_sha256()
block3.hashMerkleRoot = block3.calc_merkle_root()
block3.rehash()
block3.solve()
peer.send_blocks_and_test(
[block3], node, success=False, reject_reason='bad-cb-amount')
# Complete testing of CVE-2012-2459 by sending the original block.
# It should be accepted even though it has the same hash as the mutated
# one.
self.log.info("Test accepting original block after rejecting its"
" mutated version.")
peer.send_blocks_and_test([block2_orig], node, success=True,
timeout=5)
# Update tip info
height += 1
block_time += 1
tip = int(block2_orig.hash, 16)
# Complete testing of CVE-2018-17144, by checking for the inflation bug.
# Create a block that spends the output of a tx in a previous block.
block4 = create_block(tip, create_coinbase(height), block_time)
tx3 = create_tx_with_script(tx2, 0, script_sig=b'\x51',
amount=50 * COIN)
# Duplicates input
tx3.vin.append(tx3.vin[0])
tx3.rehash()
block4.vtx.append(tx3)
make_conform_to_ctor(block4)
block4.hashMerkleRoot = block4.calc_merkle_root()
block4.rehash()
block4.solve()
self.log.info("Test inflation by duplicating input")
peer.send_blocks_and_test([block4], node, success=False,
reject_reason='bad-txns-inputs-duplicate')
self.log.info(
"Test accepting identical block after rejecting it due to a future timestamp.")
t = int(time.time())
node.setmocktime(t)
# Set block time +1 second past max future validity
block = create_block(tip, create_coinbase(
height), t + MAX_FUTURE_BLOCK_TIME + 1)
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
# Need force_send because the block will get rejected without a getdata
# otherwise
peer.send_blocks_and_test(
[block],
node,
force_send=True,
success=False,
reject_reason='time-too-new')
node.setmocktime(t + 1)
peer.send_blocks_and_test([block], node, success=True)
if __name__ == '__main__':
InvalidBlockRequestTest().main()
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index da24f3803..c9a46b571 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -1,518 +1,539 @@
#!/usr/bin/env python3
# Copyright (c) 2014-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.
"""Test RPCs related to blockchainstate.
Test the following RPCs:
- getblockchaininfo
- getchaintxstats
- gettxoutsetinfo
- getblockheader
- getdifficulty
- getnetworkhashps
- waitforblockheight
- getblock
- getblockhash
- getbestblockhash
- verifychain
Tests correspond to code in rpc/blockchain.cpp.
"""
import http.client
import os
import subprocess
from decimal import Decimal
from test_framework.address import ADDRESS_ECREG_P2SH_OP_TRUE
from test_framework.blocktools import (
+ MAX_FUTURE_BLOCK_TIME,
TIME_GENESIS_BLOCK,
create_block,
create_coinbase,
)
from test_framework.messages import CBlockHeader, FromHex, msg_block
from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
assert_is_hash_string,
assert_is_hex_string,
assert_raises,
assert_raises_rpc_error,
get_datadir_path,
)
from test_framework.wallet import MiniWallet
# blocks mined
HEIGHT = 200
# ten-minute steps
TIME_RANGE_STEP = 600
TIME_RANGE_MTP = TIME_GENESIS_BLOCK + (HEIGHT - 6) * TIME_RANGE_STEP
+TIME_RANGE_TIP = TIME_GENESIS_BLOCK + (HEIGHT - 1) * TIME_RANGE_STEP
TIME_RANGE_END = TIME_GENESIS_BLOCK + HEIGHT * TIME_RANGE_STEP
class BlockchainTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.supports_cli = False
def run_test(self):
self.mine_chain()
+ self._test_max_future_block_time()
+
# Set extra args with pruning after rescan is complete
self.restart_node(0, extra_args=['-stopatheight=207', '-prune=1'])
self._test_getblockchaininfo()
self._test_getchaintxstats()
self._test_gettxoutsetinfo()
self._test_getblockheader()
self._test_getdifficulty()
self._test_getnetworkhashps()
self._test_stopatheight()
self._test_waitforblockheight()
if self.is_wallet_compiled():
self._test_getblock()
self._test_getblock_txfee()
assert self.nodes[0].verifychain(4, 0)
def mine_chain(self):
self.log.info(
f"Generate {HEIGHT} blocks after the genesis block in ten-minute steps")
for t in range(TIME_GENESIS_BLOCK, TIME_RANGE_END, TIME_RANGE_STEP):
self.nodes[0].setmocktime(t)
self.generatetoaddress(
self.nodes[0], 1, ADDRESS_ECREG_P2SH_OP_TRUE)
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], HEIGHT)
+ def _test_max_future_block_time(self):
+ self.stop_node(0)
+ self.log.info(
+ "A block tip of more than MAX_FUTURE_BLOCK_TIME in the future raises an error")
+ self.nodes[0].assert_start_raises_init_error(
+ extra_args=[
+ f"-mocktime={TIME_RANGE_TIP - MAX_FUTURE_BLOCK_TIME - 1}"],
+ expected_msg=": 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."
+ f" Only rebuild the block database if you are sure that your computer's date and time are correct.{os.linesep}"
+ "Please restart with -reindex or -reindex-chainstate to recover.",
+ )
+ self.log.info(
+ "A block tip of MAX_FUTURE_BLOCK_TIME in the future is fine")
+ self.start_node(
+ 0, extra_args=[f"-mocktime={TIME_RANGE_TIP - MAX_FUTURE_BLOCK_TIME}"])
+
def _test_getblockchaininfo(self):
self.log.info("Test getblockchaininfo")
keys = [
'bestblockhash',
'blocks',
'chain',
'chainwork',
'difficulty',
'headers',
'initialblockdownload',
'mediantime',
'pruned',
'size_on_disk',
'softforks',
'time',
'verificationprogress',
'warnings',
]
res = self.nodes[0].getblockchaininfo()
assert_equal(res['time'], TIME_RANGE_END - TIME_RANGE_STEP)
assert_equal(res['mediantime'], TIME_RANGE_MTP)
# result should have these additional pruning keys if manual pruning is
# enabled
assert_equal(sorted(res.keys()), sorted(
['pruneheight', 'automatic_pruning'] + keys))
# size_on_disk should be > 0
assert_greater_than(res['size_on_disk'], 0)
assert_equal(res['softforks'], {
'testdummy': {
'type': 'bip9',
'bip9': {
'status': 'started',
'bit': 28,
'start_time': 0,
# testdummy does not have a timeout so is set to the max
# int64 value
'timeout': 0x7fffffffffffffff,
'since': 144,
'statistics': {
'period': 144,
'threshold': 108,
'elapsed': HEIGHT - 143,
'count': HEIGHT - 143,
'possible': True,
},
},
'active': False,
},
})
# pruneheight should be greater or equal to 0
assert_greater_than_or_equal(res['pruneheight'], 0)
# check other pruning fields given that prune=1
assert res['pruned']
assert not res['automatic_pruning']
self.restart_node(0, ['-stopatheight=207'])
res = self.nodes[0].getblockchaininfo()
# should have exact keys
assert_equal(sorted(res.keys()), keys)
self.restart_node(0, ['-stopatheight=207', '-prune=550'])
res = self.nodes[0].getblockchaininfo()
# result should have these additional pruning keys if prune=550
assert_equal(sorted(res.keys()), sorted(
['pruneheight', 'automatic_pruning', 'prune_target_size'] + keys))
# check related fields
assert res['pruned']
assert_equal(res['pruneheight'], 0)
assert res['automatic_pruning']
assert_equal(res['prune_target_size'], 576716800)
assert_greater_than(res['size_on_disk'], 0)
def _test_getchaintxstats(self):
self.log.info("Test getchaintxstats")
# Test `getchaintxstats` invalid extra parameters
assert_raises_rpc_error(
-1, 'getchaintxstats', self.nodes[0].getchaintxstats, 0, '', 0)
# Test `getchaintxstats` invalid `nblocks`
assert_raises_rpc_error(
-1, "JSON value is not an integer as expected", self.nodes[0].getchaintxstats, '')
assert_raises_rpc_error(
-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, -1)
assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[
0].getchaintxstats, self.nodes[0].getblockcount())
# Test `getchaintxstats` invalid `blockhash`
assert_raises_rpc_error(
-1, "JSON value is not a string as expected", self.nodes[0].getchaintxstats, blockhash=0)
assert_raises_rpc_error(-8,
"blockhash must be of length 64 (not 1, for '0')",
self.nodes[0].getchaintxstats,
blockhash='0')
assert_raises_rpc_error(
-8,
"blockhash must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')",
self.nodes[0].getchaintxstats,
blockhash='ZZZ0000000000000000000000000000000000000000000000000000000000000')
assert_raises_rpc_error(
-5,
"Block not found",
self.nodes[0].getchaintxstats,
blockhash='0000000000000000000000000000000000000000000000000000000000000000')
blockhash = self.nodes[0].getblockhash(HEIGHT)
self.nodes[0].invalidateblock(blockhash)
assert_raises_rpc_error(
-8, "Block is not in main chain", self.nodes[0].getchaintxstats, blockhash=blockhash)
self.nodes[0].reconsiderblock(blockhash)
chaintxstats = self.nodes[0].getchaintxstats(nblocks=1)
# 200 txs plus genesis tx
assert_equal(chaintxstats['txcount'], HEIGHT + 1)
# tx rate should be 1 per 10 minutes, or 1/600
# we have to round because of binary math
assert_equal(
round(
chaintxstats['txrate'] *
TIME_RANGE_STEP,
10),
Decimal(1))
b1_hash = self.nodes[0].getblockhash(1)
b1 = self.nodes[0].getblock(b1_hash)
b200_hash = self.nodes[0].getblockhash(HEIGHT)
b200 = self.nodes[0].getblock(b200_hash)
time_diff = b200['mediantime'] - b1['mediantime']
chaintxstats = self.nodes[0].getchaintxstats()
assert_equal(chaintxstats['time'], b200['time'])
assert_equal(chaintxstats['txcount'], HEIGHT + 1)
assert_equal(chaintxstats['window_final_block_hash'], b200_hash)
assert_equal(chaintxstats['window_final_block_height'], HEIGHT)
assert_equal(chaintxstats['window_block_count'], HEIGHT - 1)
assert_equal(chaintxstats['window_tx_count'], HEIGHT - 1)
assert_equal(chaintxstats['window_interval'], time_diff)
assert_equal(
round(chaintxstats['txrate'] * time_diff, 10), Decimal(HEIGHT - 1))
chaintxstats = self.nodes[0].getchaintxstats(blockhash=b1_hash)
assert_equal(chaintxstats['time'], b1['time'])
assert_equal(chaintxstats['txcount'], 2)
assert_equal(chaintxstats['window_final_block_hash'], b1_hash)
assert_equal(chaintxstats['window_final_block_height'], 1)
assert_equal(chaintxstats['window_block_count'], 0)
assert 'window_tx_count' not in chaintxstats
assert 'window_interval' not in chaintxstats
assert 'txrate' not in chaintxstats
def _test_gettxoutsetinfo(self):
node = self.nodes[0]
res = node.gettxoutsetinfo()
assert_equal(res['total_amount'], Decimal('8725000000.00'))
assert_equal(res['transactions'], HEIGHT)
assert_equal(res['height'], HEIGHT)
assert_equal(res['txouts'], HEIGHT)
assert_equal(res['bogosize'], 14600),
assert_equal(res['bestblock'], node.getblockhash(HEIGHT))
size = res['disk_size']
assert size > 6400
assert size < 64000
assert_equal(len(res['bestblock']), 64)
assert_equal(len(res['hash_serialized']), 64)
self.log.info(
"Test gettxoutsetinfo works for blockchain with just the genesis block")
b1hash = node.getblockhash(1)
node.invalidateblock(b1hash)
res2 = node.gettxoutsetinfo()
assert_equal(res2['transactions'], 0)
assert_equal(res2['total_amount'], Decimal('0'))
assert_equal(res2['height'], 0)
assert_equal(res2['txouts'], 0)
assert_equal(res2['bogosize'], 0),
assert_equal(res2['bestblock'], node.getblockhash(0))
assert_equal(len(res2['hash_serialized']), 64)
self.log.info(
"Test gettxoutsetinfo returns the same result after invalidate/reconsider block")
node.reconsiderblock(b1hash)
res3 = node.gettxoutsetinfo()
# The field 'disk_size' is non-deterministic and can thus not be
# compared between res and res3. Everything else should be the same.
del res['disk_size'], res3['disk_size']
assert_equal(res, res3)
self.log.info("Test gettxoutsetinfo hash_type option")
# Adding hash_type 'hash_serialized', which is the default, should not
# change the result.
res4 = node.gettxoutsetinfo(hash_type='hash_serialized')
del res4['disk_size']
assert_equal(res, res4)
# hash_type none should not return a UTXO set hash.
res5 = node.gettxoutsetinfo(hash_type='none')
assert 'hash_serialized' not in res5
# hash_type muhash should return a different UTXO set hash.
res6 = node.gettxoutsetinfo(hash_type='muhash')
assert 'muhash' in res6
assert res['hash_serialized'] != res6['muhash']
# muhash should not be returned unless requested.
for r in [res, res2, res3, res4, res5]:
assert 'muhash' not in r
# Unknown hash_type raises an error
assert_raises_rpc_error(-8,
"foohash is not a valid hash_type",
node.gettxoutsetinfo,
"foohash")
def _test_getblockheader(self):
self.log.info("Test getblockheader")
node = self.nodes[0]
assert_raises_rpc_error(-8,
"hash must be of length 64 (not 8, for 'nonsense')",
node.getblockheader,
"nonsense")
assert_raises_rpc_error(
-8,
"hash must be hexadecimal string (not 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844')",
node.getblockheader,
"ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844")
assert_raises_rpc_error(-5, "Block not found", node.getblockheader,
"0cf7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844")
besthash = node.getbestblockhash()
secondbesthash = node.getblockhash(HEIGHT - 1)
header = node.getblockheader(blockhash=besthash)
assert_equal(header['hash'], besthash)
assert_equal(header['height'], HEIGHT)
assert_equal(header['confirmations'], 1)
assert_equal(header['previousblockhash'], secondbesthash)
assert_is_hex_string(header['chainwork'])
assert_equal(header['nTx'], 1)
assert_is_hash_string(header['hash'])
assert_is_hash_string(header['previousblockhash'])
assert_is_hash_string(header['merkleroot'])
assert_is_hash_string(header['bits'], length=None)
assert isinstance(header['time'], int)
assert_equal(header['mediantime'], TIME_RANGE_MTP)
assert isinstance(header['nonce'], int)
assert isinstance(header['version'], int)
assert isinstance(int(header['versionHex'], 16), int)
assert isinstance(header['difficulty'], Decimal)
# Test with verbose=False, which should return the header as hex.
header_hex = node.getblockheader(blockhash=besthash, verbose=False)
assert_is_hex_string(header_hex)
header = FromHex(CBlockHeader(), header_hex)
header.calc_sha256()
assert_equal(header.hash, besthash)
assert 'previousblockhash' not in node.getblockheader(
node.getblockhash(0))
assert 'nextblockhash' not in node.getblockheader(
node.getbestblockhash())
def _test_getdifficulty(self):
self.log.info("Test getdifficulty")
difficulty = self.nodes[0].getdifficulty()
# 1 hash in 2 should be valid, so difficulty should be 1/2**31
# binary => decimal => binary math is why we do this check
assert abs(difficulty * 2**31 - 1) < 0.0001
def _test_getnetworkhashps(self):
self.log.info("Test getnetworkhashps")
hashes_per_second = self.nodes[0].getnetworkhashps()
# This should be 2 hashes every 10 minutes or 1/300
assert abs(hashes_per_second * 300 - 1) < 0.0001
def _test_stopatheight(self):
self.log.info("Test stopping at height")
assert_equal(self.nodes[0].getblockcount(), HEIGHT)
self.generatetoaddress(self.nodes[0], 6, ADDRESS_ECREG_P2SH_OP_TRUE)
assert_equal(self.nodes[0].getblockcount(), HEIGHT + 6)
self.log.debug('Node should not stop at this height')
assert_raises(subprocess.TimeoutExpired,
lambda: self.nodes[0].process.wait(timeout=3))
try:
self.generatetoaddress(
self.nodes[0], 1, ADDRESS_ECREG_P2SH_OP_TRUE, sync_fun=self.no_op)
except (ConnectionError, http.client.BadStatusLine):
pass # The node already shut down before response
self.log.debug('Node should stop at this height...')
self.nodes[0].wait_until_stopped()
self.start_node(0)
assert_equal(self.nodes[0].getblockcount(), HEIGHT + 7)
def _test_waitforblockheight(self):
self.log.info("Test waitforblockheight")
node = self.nodes[0]
peer = node.add_p2p_connection(P2PInterface())
current_height = node.getblock(node.getbestblockhash())['height']
# Create a fork somewhere below our current height, invalidate the tip
# of that fork, and then ensure that waitforblockheight still
# works as expected.
#
# (Previously this was broken based on setting
# `rpc/blockchain.cpp:latestblock` incorrectly.)
#
b20hash = node.getblockhash(20)
b20 = node.getblock(b20hash)
def solve_and_send_block(prevhash, height, time):
b = create_block(prevhash, create_coinbase(height), time)
b.solve()
peer.send_and_ping(msg_block(b))
return b
b21f = solve_and_send_block(int(b20hash, 16), 21, b20['time'] + 1)
b22f = solve_and_send_block(b21f.sha256, 22, b21f.nTime + 1)
node.invalidateblock(b22f.hash)
def assert_waitforheight(height, timeout=2):
assert_equal(
node.waitforblockheight(
height=height, timeout=timeout)['height'],
current_height)
assert_waitforheight(0)
assert_waitforheight(current_height - 1)
assert_waitforheight(current_height)
assert_waitforheight(current_height + 1)
def _test_getblock(self):
# Checks for getblock verbose outputs
node = self.nodes[0]
blockcount = node.getblockcount()
blockhash = node.getblockhash(blockcount - 1)
nextblockhash = node.getblockhash(blockcount)
blockinfo = node.getblock(blockhash, 2)
blockheaderinfo = node.getblockheader(blockhash, True)
assert_equal(blockinfo['hash'], blockhash)
assert_equal(blockinfo['confirmations'], 2)
assert_equal(blockinfo['height'], blockheaderinfo['height'])
assert_equal(blockinfo['versionHex'], blockheaderinfo['versionHex'])
assert_equal(blockinfo['version'], blockheaderinfo['version'])
assert_equal(blockinfo['size'], 181)
assert_equal(blockinfo['merkleroot'], blockheaderinfo['merkleroot'])
# Verify transaction data by check the hex values
for tx in blockinfo['tx']:
rawtransaction = node.getrawtransaction(
txid=tx['txid'], verbose=True, blockhash=blockhash)
assert_equal(tx['hex'], rawtransaction['hex'])
assert_equal(blockinfo['time'], blockheaderinfo['time'])
assert_equal(blockinfo['mediantime'], blockheaderinfo['mediantime'])
assert_equal(blockinfo['nonce'], blockheaderinfo['nonce'])
assert_equal(blockinfo['bits'], blockheaderinfo['bits'])
assert_equal(blockinfo['difficulty'], blockheaderinfo['difficulty'])
assert_equal(blockinfo['chainwork'], blockheaderinfo['chainwork'])
assert_equal(
blockinfo['previousblockhash'],
blockheaderinfo['previousblockhash'])
assert_equal(blockinfo['nextblockhash'], nextblockhash)
assert_equal(
blockinfo['nextblockhash'],
blockheaderinfo['nextblockhash'])
assert 'previousblockhash' not in node.getblock(node.getblockhash(0))
assert 'nextblockhash' not in node.getblock(node.getbestblockhash())
def _test_getblock_txfee(self):
node = self.nodes[0]
miniwallet = MiniWallet(node)
miniwallet.rescan_utxos()
fee_per_byte = Decimal('0.1')
fee_per_kb = 1000 * fee_per_byte
miniwallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node)
blockhash = self.generate(node, 1)[0]
self.log.info(
"Test getblock with verbosity 1 only includes the txid")
block = node.getblock(blockhash, 1)
assert_equal(block['tx'][1], miniwallet.get_utxo()['txid'])
self.log.info('Test getblock with verbosity 2 includes expected fee')
block = node.getblock(blockhash, 2)
tx = block['tx'][1]
assert_equal(tx['fee'], tx['size'] * fee_per_byte)
self.log.info(
"Test getblock with verbosity 2 still works with pruned Undo data")
datadir = get_datadir_path(self.options.tmpdir, 0)
def move_block_file(old, new):
old_path = os.path.join(datadir, self.chain, 'blocks', old)
new_path = os.path.join(datadir, self.chain, 'blocks', new)
os.rename(old_path, new_path)
# Move instead of deleting so we can restore chain state afterwards
move_block_file('rev00000.dat', 'rev_wrong')
block = node.getblock(blockhash, 2)
assert 'fee' not in block['tx'][1]
# Restore chain state
move_block_file('rev_wrong', 'rev00000.dat')
if __name__ == '__main__':
BlockchainTest().main()
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index 4f4652113..1e4932502 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -1,250 +1,252 @@
#!/usr/bin/env python3
# Copyright (c) 2015-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.
"""Utilities for manipulating blocks and transactions."""
import struct
import time
import unittest
from typing import Optional
from .messages import (
XEC,
CBlock,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
FromHex,
ToHex,
)
from .script import (
OP_1,
OP_CHECKSIG,
OP_DUP,
OP_EQUALVERIFY,
OP_HASH160,
OP_RETURN,
OP_TRUE,
CScript,
CScriptNum,
CScriptOp,
)
from .txtools import pad_tx
from .util import assert_equal, satoshi_round
# Genesis block time (regtest)
TIME_GENESIS_BLOCK = 1296688602
+MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60
+
def create_block(
hashprev: Optional[int] = None,
coinbase: Optional[CTransaction] = None,
ntime: Optional[int] = None,
*,
version: Optional[int] = None,
tmpl: Optional[dict] = None) -> CBlock:
"""Create a block (with regtest difficulty)."""
block = CBlock()
if tmpl is None:
tmpl = {}
block.nVersion = version or tmpl.get('version', 1)
block.nTime = ntime or tmpl.get('curtime', int(time.time() + 600))
block.hashPrevBlock = hashprev or int(tmpl['previousblockhash'], 0x10)
if tmpl.get('bits') is not None:
block.nBits = struct.unpack('>I', bytes.fromhex(tmpl['bits']))[0]
else:
# difficulty retargeting is disabled in REGTEST chainparams
block.nBits = 0x207fffff
block.vtx.append(coinbase or create_coinbase(height=tmpl['height']))
block.hashMerkleRoot = block.calc_merkle_root()
block.calc_sha256()
return block
def make_conform_to_ctor(block: CBlock):
for tx in block.vtx:
tx.rehash()
block.vtx = [block.vtx[0]] + \
sorted(block.vtx[1:], key=lambda tx: tx.get_id())
def script_BIP34_coinbase_height(height: int) -> CScript:
if height <= 16:
res = CScriptOp.encode_op_n(height)
# Append dummy to increase scriptSig size above 2
# (see bad-cb-length consensus rule)
return CScript([res, OP_1])
return CScript([CScriptNum(height)])
def create_coinbase(
height: int, pubkey: Optional[bytes] = None,
nValue: int = 50_000_000) -> CTransaction:
"""Create a coinbase transaction, assuming no miner fees.
If pubkey is passed in, the coinbase output will be a P2PK output;
otherwise an anyone-can-spend output."""
coinbase = CTransaction()
coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff),
script_BIP34_coinbase_height(height),
0xffffffff))
coinbaseoutput = CTxOut()
coinbaseoutput.nValue = nValue * XEC
if nValue == 50_000_000:
halvings = int(height / 150) # regtest
coinbaseoutput.nValue >>= halvings
if pubkey is not None:
coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG])
else:
coinbaseoutput.scriptPubKey = CScript([OP_TRUE])
coinbase.vout = [coinbaseoutput]
# Make sure the coinbase is at least 100 bytes
pad_tx(coinbase)
coinbase.calc_sha256()
return coinbase
def create_tx_with_script(prevtx, n, script_sig=b"", *,
amount, script_pub_key=CScript()):
"""Return one-input, one-output transaction object
spending the prevtx's n-th output with the given amount.
Can optionally pass scriptPubKey and scriptSig, default is anyone-can-spend output.
"""
tx = CTransaction()
assert n < len(prevtx.vout)
tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, 0xffffffff))
tx.vout.append(CTxOut(amount, script_pub_key))
pad_tx(tx)
tx.calc_sha256()
return tx
def create_transaction(node, txid, to_address, *, amount):
""" Return signed transaction spending the first output of the
input txid. Note that the node must be able to sign for the
output that is being spent, and the node must not be running
multiple wallets.
"""
raw_tx = create_raw_transaction(node, txid, to_address, amount=amount)
tx = FromHex(CTransaction(), raw_tx)
return tx
def create_raw_transaction(node, txid, to_address, *, amount):
""" Return raw signed transaction spending the first output of the
input txid. Note that the node must be able to sign for the
output that is being spent, and the node must not be running
multiple wallets.
"""
rawtx = node.createrawtransaction(
inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount})
signresult = node.signrawtransactionwithwallet(rawtx)
assert_equal(signresult["complete"], True)
return signresult['hex']
def get_legacy_sigopcount_block(block, accurate=True):
count = 0
for tx in block.vtx:
count += get_legacy_sigopcount_tx(tx, accurate)
return count
def get_legacy_sigopcount_tx(tx, accurate=True):
count = 0
for i in tx.vout:
count += i.scriptPubKey.GetSigOpCount(accurate)
for j in tx.vin:
# scriptSig might be of type bytes, so convert to CScript for the
# moment
count += CScript(j.scriptSig).GetSigOpCount(accurate)
return count
def create_confirmed_utxos(test_framework, node, count, age=101, **kwargs):
"""
Helper to create at least "count" utxos
"""
to_generate = int(0.5 * count) + age
while to_generate > 0:
test_framework.generate(node, min(25, to_generate), **kwargs)
to_generate -= 25
utxos = node.listunspent()
iterations = count - len(utxos)
addr1 = node.getnewaddress()
addr2 = node.getnewaddress()
if iterations <= 0:
return utxos
for i in range(iterations):
t = utxos.pop()
inputs = []
inputs.append({"txid": t["txid"], "vout": t["vout"]})
outputs = {}
outputs[addr1] = satoshi_round(t['amount'] / 2)
outputs[addr2] = satoshi_round(t['amount'] / 2)
raw_tx = node.createrawtransaction(inputs, outputs)
ctx = FromHex(CTransaction(), raw_tx)
fee = node.calculate_fee(ctx) // 2
ctx.vout[0].nValue -= fee
# Due to possible truncation, we go ahead and take another satoshi in
# fees to ensure the transaction gets through
ctx.vout[1].nValue -= fee + 1
signed_tx = node.signrawtransactionwithwallet(ToHex(ctx))["hex"]
node.sendrawtransaction(signed_tx)
while (node.getmempoolinfo()['size'] > 0):
test_framework.generate(node, 1, **kwargs)
utxos = node.listunspent()
assert len(utxos) >= count
return utxos
def mine_big_block(test_framework, node, utxos=None):
# generate a 66k transaction,
# and 14 of them is close to the 1MB block limit
num = 14
utxos = utxos if utxos is not None else []
if len(utxos) < num:
utxos.clear()
utxos.extend(node.listunspent())
send_big_transactions(node, utxos, num, 100)
test_framework.generate(node, 1)
def send_big_transactions(node, utxos, num, fee_multiplier):
from .cashaddr import decode
txids = []
padding = "1" * 512
addrHash = decode(node.getnewaddress())[2]
for _ in range(num):
ctx = CTransaction()
utxo = utxos.pop()
txid = int(utxo['txid'], 16)
ctx.vin.append(CTxIn(COutPoint(txid, int(utxo["vout"])), b""))
ctx.vout.append(
CTxOut(int(satoshi_round(utxo['amount'] * XEC)),
CScript([OP_DUP, OP_HASH160, addrHash, OP_EQUALVERIFY, OP_CHECKSIG])))
for i in range(0, 127):
ctx.vout.append(CTxOut(0, CScript(
[OP_RETURN, bytes(padding, 'utf-8')])))
# Create a proper fee for the transaction to be mined
ctx.vout[0].nValue -= int(fee_multiplier * node.calculate_fee(ctx))
signresult = node.signrawtransactionwithwallet(
ToHex(ctx), None, "NONE|FORKID")
txid = node.sendrawtransaction(signresult["hex"], 0)
txids.append(txid)
return txids
class TestFrameworkBlockTools(unittest.TestCase):
def test_create_coinbase(self):
height = 20
coinbase_tx = create_coinbase(height=height)
assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), height)

File Metadata

Mime Type
text/x-diff
Expires
Wed, May 21, 18:39 (21 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5865710
Default Alt Text
(455 KB)

Event Timeline