diff --git a/doc/release-notes.md b/doc/release-notes.md
index 1da733d9c3..111ef4972e 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,11 +1,12 @@
Bitcoin ABC version 0.18.8 is now available from:
This release includes the following features and fixes:
- `dumpwallet` now includes hex-encoded scripts from the wallet in the dumpfile
- `importwallet` now imports these scripts, but corresponding addresses may not
be added correctly or a manual rescan may be required to find relevant
transactions
- Remove miner policy estimator in favor of minimum fees, also remove `fee_estimates.dat`.
Old copies will be left in place.
+ - The log timestamp format is now ISO 8601 (e.g. "2019-01-28T15:41:17Z")
\ No newline at end of file
diff --git a/src/init.cpp b/src/init.cpp
index 6d680d3eee..351a858030 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1,2392 +1,2391 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-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.
#if defined(HAVE_CONFIG_H)
#include "config/bitcoin-config.h"
#endif
#include "init.h"
#include "addrman.h"
#include "amount.h"
#include "chain.h"
#include "chainparams.h"
#include "checkpoints.h"
#include "compat/sanity.h"
#include "config.h"
#include "consensus/validation.h"
#include "diskblockpos.h"
#include "fs.h"
#include "httprpc.h"
#include "httpserver.h"
#include "key.h"
#include "miner.h"
#include "net.h"
#include "net_processing.h"
#include "netbase.h"
#include "policy/policy.h"
#include "rpc/register.h"
#include "rpc/safemode.h"
#include "rpc/server.h"
#include "scheduler.h"
#include "script/scriptcache.h"
#include "script/sigcache.h"
#include "script/standard.h"
#include "timedata.h"
#include "torcontrol.h"
#include "txdb.h"
#include "txmempool.h"
#include "ui_interface.h"
#include "util.h"
#include "utilmoneystr.h"
#include "validation.h"
#include "validationinterface.h"
#ifdef ENABLE_WALLET
#include "wallet/rpcdump.h"
#endif
#include "walletinitinterface.h"
#include "warnings.h"
#include
#include
#include
#ifndef WIN32
#include
#endif
#include
#include
#include
#include
#include
#include
#if ENABLE_ZMQ
#include "zmq/zmqnotificationinterface.h"
#endif
static const bool DEFAULT_PROXYRANDOMIZE = true;
static const bool DEFAULT_REST_ENABLE = false;
static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
std::unique_ptr g_connman;
std::unique_ptr peerLogic;
#if !(ENABLE_WALLET)
class DummyWalletInit : public WalletInitInterface {
public:
std::string GetHelpString(bool showDebug) override { return std::string{}; }
bool ParameterInteraction() override { return true; }
void RegisterRPC(CRPCTable &) override {}
bool Verify(const CChainParams &chainParams) override { return true; }
bool Open(const CChainParams &chainParams) override { return true; }
void Start(CScheduler &scheduler) override {}
void Flush() override {}
void Stop() override {}
void Close() override {}
};
static DummyWalletInit g_dummy_wallet_init;
WalletInitInterface *const g_wallet_init_interface = &g_dummy_wallet_init;
#endif
#if ENABLE_ZMQ
static CZMQNotificationInterface *pzmqNotificationInterface = nullptr;
#endif
#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
//////////////////////////////////////////////////////////////////////////////
//
// 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 triggers the DetectShutdownThread(), which interrupts
// the main thread group. DetectShutdownThread() then exits, which causes
// AppInit() to continue (it .joins the shutdown 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.
//
// Note that if running -daemon the parent process returns from AppInit2 before
// adding any threads to the threadGroup, so .join_all() returns immediately and
// the parent exits from main().
//
// Shutdown for Qt is very similar, only it uses a QTimer to detect
// fRequestShutdown getting set, and then does the normal Qt shutdown thing.
//
std::atomic fRequestShutdown(false);
std::atomic fDumpMempoolLater(false);
void StartShutdown() {
fRequestShutdown = true;
}
bool ShutdownRequested() {
return fRequestShutdown;
}
/**
* This is a minimally invasive approach to shutdown on LevelDB read errors from
* the chainstate, while keeping user interface out of the common library, which
* is shared between bitcoind, and bitcoin-qt and non-server tools.
*/
class CCoinsViewErrorCatcher final : public CCoinsViewBacked {
public:
explicit CCoinsViewErrorCatcher(CCoinsView *view)
: CCoinsViewBacked(view) {}
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override {
try {
return CCoinsViewBacked::GetCoin(outpoint, coin);
} catch (const std::runtime_error &e) {
uiInterface.ThreadSafeMessageBox(
_("Error reading from database, shutting down."), "",
CClientUIInterface::MSG_ERROR);
LogPrintf("Error reading from database: %s\n", e.what());
// Starting the shutdown sequence and returning false to the caller
// would be interpreted as 'entry not found' (as opposed to unable
// to read data), and could lead to invalid interpretation. Just
// exit immediately, as we can't continue anyway, and all writes
// should be atomic.
abort();
}
}
// Writes do not need similar protection, as failure to write is handled by
// the caller.
};
static std::unique_ptr pcoinscatcher;
static std::unique_ptr globalVerifyHandle;
static boost::thread_group threadGroup;
static CScheduler scheduler;
void Interrupt() {
InterruptHTTPServer();
InterruptHTTPRPC();
InterruptRPC();
InterruptREST();
InterruptTorControl();
InterruptMapPort();
if (g_connman) {
g_connman->Interrupt();
}
}
void Shutdown() {
LogPrintf("%s: In progress...\n", __func__);
static CCriticalSection cs_Shutdown;
TRY_LOCK(cs_Shutdown, lockShutdown);
if (!lockShutdown) {
return;
}
/// Note: Shutdown() must be able to handle cases in which AppInit2() 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.
RenameThread("bitcoin-shutoff");
g_mempool.AddTransactionsUpdated(1);
StopHTTPRPC();
StopREST();
StopRPC();
StopHTTPServer();
g_wallet_init_interface->Flush();
StopMapPort();
// Because these depend on each-other, we make sure that neither can be
// using the other before destroying them.
if (peerLogic) {
UnregisterValidationInterface(peerLogic.get());
}
if (g_connman) {
g_connman->Stop();
}
peerLogic.reset();
g_connman.reset();
StopTorControl();
// After everything has been shut down, but before things get flushed, stop
// the CScheduler/checkqueue threadGroup
threadGroup.interrupt_all();
threadGroup.join_all();
if (fDumpMempoolLater &&
gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
DumpMempool();
}
// FlushStateToDisk generates a SetBestChain callback, which we should avoid
// missing
if (pcoinsTip != nullptr) {
FlushStateToDisk();
}
// After there are no more peers/RPC left to give us new data which may
// generate CValidationInterface callbacks, flush them...
GetMainSignals().FlushBackgroundCallbacks();
// 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.
{
LOCK(cs_main);
if (pcoinsTip != nullptr) {
FlushStateToDisk();
}
pcoinsTip.reset();
pcoinscatcher.reset();
pcoinsdbview.reset();
pblocktree.reset();
}
g_wallet_init_interface->Stop();
#if ENABLE_ZMQ
if (pzmqNotificationInterface) {
UnregisterValidationInterface(pzmqNotificationInterface);
delete pzmqNotificationInterface;
pzmqNotificationInterface = nullptr;
}
#endif
#ifndef WIN32
try {
fs::remove(GetPidFile());
} catch (const fs::filesystem_error &e) {
LogPrintf("%s: Unable to remove pidfile: %s\n", __func__, e.what());
}
#endif
UnregisterAllValidationInterfaces();
GetMainSignals().UnregisterBackgroundSignalScheduler();
GetMainSignals().UnregisterWithMempoolSignals(g_mempool);
g_wallet_init_interface->Close();
globalVerifyHandle.reset();
ECC_Stop();
LogPrintf("%s: done\n", __func__);
}
/**
* Signal handlers are very limited in what they are allowed to do, so:
*/
void HandleSIGTERM(int) {
fRequestShutdown = true;
}
void HandleSIGHUP(int) {
GetLogger().m_reopen_file = true;
}
void OnRPCStarted() {
uiInterface.NotifyBlockTip.connect(&RPCNotifyBlockChange);
}
void OnRPCStopped() {
uiInterface.NotifyBlockTip.disconnect(&RPCNotifyBlockChange);
RPCNotifyBlockChange(false, nullptr);
g_best_block_cv.notify_all();
LogPrint(BCLog::RPC, "RPC stopped.\n");
}
std::string HelpMessage(HelpMessageMode mode) {
const auto defaultBaseParams =
CreateBaseChainParams(CBaseChainParams::MAIN);
const auto testnetBaseParams =
CreateBaseChainParams(CBaseChainParams::TESTNET);
const auto defaultChainParams = CreateChainParams(CBaseChainParams::MAIN);
const auto testnetChainParams =
CreateChainParams(CBaseChainParams::TESTNET);
const bool showDebug = gArgs.GetBoolArg("-help-debug", false);
// 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.
std::string strUsage = HelpMessageGroup(_("Options:"));
strUsage += HelpMessageOpt("-?", _("Print this help message and exit"));
strUsage += HelpMessageOpt("-version", _("Print version and exit"));
strUsage += HelpMessageOpt(
"-alertnotify=",
_("Execute command when a relevant alert is received or we see a "
"really long fork (%s in cmd is replaced by message)"));
strUsage += HelpMessageOpt("-blocknotify=",
_("Execute command when the best block changes "
"(%s in cmd is replaced by block hash)"));
if (showDebug) {
strUsage += HelpMessageOpt(
"-blocksonly",
strprintf(
_("Whether to operate in a blocks only mode (default: %d)"),
DEFAULT_BLOCKSONLY));
}
strUsage += HelpMessageOpt(
"-assumevalid=",
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()));
strUsage += HelpMessageOpt(
"-conf=", strprintf(_("Specify configuration file (default: %s)"),
BITCOIN_CONF_FILENAME));
if (mode == HMM_BITCOIND) {
#if HAVE_DECL_DAEMON
strUsage += HelpMessageOpt(
"-daemon",
_("Run in the background as a daemon and accept commands"));
#endif
}
strUsage += HelpMessageOpt("-datadir=", _("Specify data directory"));
if (showDebug) {
strUsage += HelpMessageOpt(
"-dbbatchsize",
strprintf(
"Maximum database write batch size in bytes (default: %u)",
nDefaultDbBatchSize));
}
strUsage += HelpMessageOpt(
"-dbcache=",
strprintf(
_("Set database cache size in megabytes (%d to %d, default: %d)"),
nMinDbCache, nMaxDbCache, nDefaultDbCache));
if (showDebug) {
strUsage += HelpMessageOpt(
"-feefilter", strprintf("Tell other nodes to filter invs to us by "
"our mempool min fee (default: %d)",
DEFAULT_FEEFILTER));
}
strUsage += HelpMessageOpt(
"-finalizationdelay=",
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));
strUsage += HelpMessageOpt(
"-maxreorgdepth=",
strprintf("Configure at what depth blocks are considered final "
"(default: %d). Use -1 to disable.",
DEFAULT_MAX_REORG_DEPTH));
strUsage += HelpMessageOpt(
"-loadblock=",
_("Imports blocks from external blk000??.dat file on startup"));
strUsage += HelpMessageOpt(
"-debuglogfile=",
strprintf(
_("Specify location of debug log file: this can be an absolute "
"path or a path relative to the data directory (default: %s)"),
DEFAULT_DEBUGLOGFILE));
strUsage += HelpMessageOpt(
"-maxorphantx=", strprintf(_("Keep at most unconnectable "
"transactions in memory (default: %u)"),
DEFAULT_MAX_ORPHAN_TRANSACTIONS));
strUsage += HelpMessageOpt("-maxmempool=",
strprintf(_("Keep the transaction memory pool "
"below megabytes (default: %u)"),
DEFAULT_MAX_MEMPOOL_SIZE));
strUsage +=
HelpMessageOpt("-mempoolexpiry=",
strprintf(_("Do not keep transactions in the mempool "
"longer than hours (default: %u)"),
DEFAULT_MEMPOOL_EXPIRY));
if (showDebug) {
strUsage += HelpMessageOpt(
"-minimumchainwork=",
strprintf(
"Minimum work assumed to exist on a valid chain in hex "
"(default: %s, testnet: %s)",
defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(),
testnetChainParams->GetConsensus().nMinimumChainWork.GetHex()));
}
strUsage +=
HelpMessageOpt("-persistmempool",
strprintf(_("Whether to save the mempool on shutdown "
"and load on restart (default: %u)"),
DEFAULT_PERSIST_MEMPOOL));
strUsage += HelpMessageOpt(
"-blockreconstructionextratxn=",
strprintf(_("Extra transactions to keep in memory for compact block "
"reconstructions (default: %u)"),
DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN));
strUsage += HelpMessageOpt(
"-par=",
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));
#ifndef WIN32
strUsage += HelpMessageOpt(
"-pid=",
strprintf(_("Specify pid file (default: %s)"), BITCOIN_PID_FILENAME));
#endif
strUsage += HelpMessageOpt(
"-prune=",
strprintf(
_("Reduce storage requirements by enabling pruning (deleting) of "
"old blocks. This allows the pruneblockchain RPC to be called to "
"delete specific blocks, and enables automatic pruning of old "
"blocks if a target size in MiB is provided. This mode is "
"incompatible with -txindex and -rescan. "
"Warning: Reverting this setting requires re-downloading the "
"entire blockchain. "
"(default: 0 = disable pruning blocks, 1 = allow manual pruning "
"via RPC, >%u = automatically prune block files to stay under "
"the specified target size in MiB)"),
MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024));
strUsage += HelpMessageOpt(
"-reindex-chainstate",
_("Rebuild chain state from the currently indexed blocks"));
strUsage +=
HelpMessageOpt("-reindex", _("Rebuild chain state and block index from "
"the blk*.dat files on disk"));
#ifndef WIN32
strUsage += HelpMessageOpt(
"-sysperms",
_("Create new files with system default permissions, instead of umask "
"077 (only effective with disabled wallet functionality)"));
#endif
strUsage += HelpMessageOpt(
"-txindex", strprintf(_("Maintain a full transaction index, used by "
"the getrawtransaction rpc call (default: %d)"),
DEFAULT_TXINDEX));
strUsage += HelpMessageOpt(
"-usecashaddr", _("Use Cash Address for destination encoding instead "
"of base58 (activate by default on Jan, 14)"));
strUsage += HelpMessageGroup(_("Connection options:"));
strUsage += HelpMessageOpt(
"-addnode=",
_("Add a node to connect to and attempt to keep the connection open "
"(see the `addnode` RPC command help for more info)"));
strUsage += HelpMessageOpt(
"-banscore=",
strprintf(
_("Threshold for disconnecting misbehaving peers (default: %u)"),
DEFAULT_BANSCORE_THRESHOLD));
strUsage += HelpMessageOpt(
"-bantime=", strprintf(_("Number of seconds to keep misbehaving "
"peers from reconnecting (default: %u)"),
DEFAULT_MISBEHAVING_BANTIME));
strUsage += HelpMessageOpt("-bind=",
_("Bind to given address and always listen on "
"it. Use [host]:port notation for IPv6"));
strUsage += HelpMessageOpt(
"-connect=", _("Connect only to the specified node(s); -connect=0 "
"disables automatic connections (the rules for this "
"peer are the same as for -addnode)"));
strUsage += HelpMessageOpt("-discover",
_("Discover own IP addresses (default: 1 when "
"listening and no -externalip or -proxy)"));
strUsage += HelpMessageOpt(
"-dns", _("Allow DNS lookups for -addnode, -seednode and -connect") +
" " + strprintf(_("(default: %d)"), DEFAULT_NAME_LOOKUP));
strUsage += HelpMessageOpt(
"-dnsseed", _("Query for peer addresses via DNS lookup, if low on "
"addresses (default: 1 unless -connect/-noconnect)"));
strUsage += HelpMessageOpt("-externalip=",
_("Specify your own public address"));
strUsage += HelpMessageOpt(
"-forcednsseed",
strprintf(
_("Always query for peer addresses via DNS lookup (default: %d)"),
DEFAULT_FORCEDNSSEED));
strUsage +=
HelpMessageOpt("-listen", _("Accept connections from outside (default: "
"1 if no -proxy or -connect/-noconnect)"));
strUsage += HelpMessageOpt(
"-listenonion",
strprintf(_("Automatically create Tor hidden service (default: %d)"),
DEFAULT_LISTEN_ONION));
strUsage += HelpMessageOpt(
"-maxconnections=",
strprintf(_("Maintain at most connections to peers (default: %u)"),
DEFAULT_MAX_PEER_CONNECTIONS));
strUsage +=
HelpMessageOpt("-maxreceivebuffer=",
strprintf(_("Maximum per-connection receive buffer, "
"*1000 bytes (default: %u)"),
DEFAULT_MAXRECEIVEBUFFER));
strUsage += HelpMessageOpt(
"-maxsendbuffer=", strprintf(_("Maximum per-connection send buffer, "
"*1000 bytes (default: %u)"),
DEFAULT_MAXSENDBUFFER));
strUsage += HelpMessageOpt(
"-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));
strUsage +=
HelpMessageOpt("-onion=",
strprintf(_("Use separate SOCKS5 proxy to reach peers "
"via Tor hidden services (default: %s)"),
"-proxy"));
strUsage += HelpMessageOpt(
"-onlynet=",
_("Only connect to nodes in network (ipv4, ipv6 or onion)"));
strUsage +=
HelpMessageOpt("-permitbaremultisig",
strprintf(_("Relay non-P2SH multisig (default: %d)"),
DEFAULT_PERMIT_BAREMULTISIG));
strUsage += HelpMessageOpt(
"-peerbloomfilters",
strprintf(_("Support filtering of blocks and transaction with bloom "
"filters (default: %d)"),
DEFAULT_PEERBLOOMFILTERS));
strUsage += HelpMessageOpt(
"-port=",
strprintf(
_("Listen for connections on (default: %u or testnet: %u)"),
defaultChainParams->GetDefaultPort(),
testnetChainParams->GetDefaultPort()));
strUsage +=
HelpMessageOpt("-proxy=", _("Connect through SOCKS5 proxy"));
strUsage += HelpMessageOpt(
"-proxyrandomize",
strprintf(_("Randomize credentials for every proxy connection. This "
"enables Tor stream isolation (default: %d)"),
DEFAULT_PROXYRANDOMIZE));
strUsage += HelpMessageOpt(
"-seednode=",
_("Connect to a node to retrieve peer addresses, and disconnect"));
strUsage += HelpMessageOpt(
"-timeout=", strprintf(_("Specify connection timeout in "
"milliseconds (minimum: 1, default: %d)"),
DEFAULT_CONNECT_TIMEOUT));
strUsage += HelpMessageOpt("-torcontrol=:",
strprintf(_("Tor control port to use if onion "
"listening enabled (default: %s)"),
DEFAULT_TOR_CONTROL));
strUsage += HelpMessageOpt("-torpassword=",
_("Tor control port password (default: empty)"));
#ifdef USE_UPNP
#if USE_UPNP
strUsage +=
HelpMessageOpt("-upnp", _("Use UPnP to map the listening port "
"(default: 1 when listening and no -proxy)"));
#else
strUsage += HelpMessageOpt(
"-upnp",
strprintf(_("Use UPnP to map the listening port (default: %u)"), 0));
#endif
#endif
strUsage +=
HelpMessageOpt("-whitebind=",
_("Bind to given address and whitelist peers connecting "
"to it. Use [host]:port notation for IPv6"));
strUsage += HelpMessageOpt(
"-whitelist=",
_("Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) "
"or CIDR notated network (e.g. 1.2.3.0/24). Can be specified "
"multiple times.") +
" " +
_("Whitelisted peers cannot be DoS banned and their transactions "
"are always relayed, even if they are already in the mempool, "
"useful e.g. for a gateway"));
strUsage += HelpMessageOpt(
"-whitelistrelay",
strprintf(_("Accept relayed transactions received from whitelisted "
"peers even when not relaying transactions (default: %d)"),
DEFAULT_WHITELISTRELAY));
strUsage += HelpMessageOpt(
"-whitelistforcerelay",
strprintf(_("Force relay of transactions from whitelisted peers even "
"if they violate local relay policy (default: %d)"),
DEFAULT_WHITELISTFORCERELAY));
strUsage += HelpMessageOpt(
"-maxuploadtarget=",
strprintf(_("Tries to keep outbound traffic under the given target (in "
"MiB per 24h), 0 = no limit (default: %d)"),
DEFAULT_MAX_UPLOAD_TARGET));
strUsage += g_wallet_init_interface->GetHelpString(showDebug);
#if ENABLE_ZMQ
strUsage += HelpMessageGroup(_("ZeroMQ notification options:"));
strUsage += HelpMessageOpt("-zmqpubhashblock=",
_("Enable publish hash block in "));
strUsage +=
HelpMessageOpt("-zmqpubhashtx=",
_("Enable publish hash transaction in "));
strUsage += HelpMessageOpt("-zmqpubrawblock=",
_("Enable publish raw block in "));
strUsage +=
HelpMessageOpt("-zmqpubrawtx=",
_("Enable publish raw transaction in "));
#endif
strUsage += HelpMessageGroup(_("Debugging/Testing options:"));
strUsage += HelpMessageOpt("-uacomment=",
_("Append comment to the user agent string"));
if (showDebug) {
strUsage += HelpMessageOpt(
"-checkblocks=",
strprintf(
_("How many blocks to check at startup (default: %u, 0 = all)"),
DEFAULT_CHECKBLOCKS));
strUsage +=
HelpMessageOpt("-checklevel=",
strprintf(_("How thorough the block verification of "
"-checkblocks is (0-4, default: %u)"),
DEFAULT_CHECKLEVEL));
strUsage += HelpMessageOpt(
"-checkblockindex",
strprintf("Do a full consistency check for mapBlockIndex, "
"setBlockIndexCandidates, chainActive and "
"mapBlocksUnlinked occasionally. Also sets -checkmempool "
"(default: %u)",
defaultChainParams->DefaultConsistencyChecks()));
strUsage += HelpMessageOpt(
"-checkmempool=",
strprintf("Run checks every transactions (default: %u)",
defaultChainParams->DefaultConsistencyChecks()));
strUsage += HelpMessageOpt(
"-checkpoints", strprintf("Only accept block chain matching "
"built-in checkpoints (default: %d)",
DEFAULT_CHECKPOINTS_ENABLED));
strUsage += HelpMessageOpt(
"-disablesafemode", strprintf("Disable safemode, override a real "
"safe mode event (default: %d)",
DEFAULT_DISABLE_SAFEMODE));
strUsage +=
HelpMessageOpt("-deprecatedrpc=",
"Allows deprecated RPC method(s) to be used");
strUsage += HelpMessageOpt(
"-testsafemode",
strprintf("Force safe mode (default: %d)", DEFAULT_TESTSAFEMODE));
strUsage +=
HelpMessageOpt("-dropmessagestest=",
"Randomly drop 1 of every network messages");
strUsage += HelpMessageOpt(
"-stopafterblockimport",
strprintf(
"Stop running after importing blocks from disk (default: %d)",
DEFAULT_STOPAFTERBLOCKIMPORT));
strUsage += HelpMessageOpt(
"-stopatheight", strprintf("Stop running after reaching the given "
"height in the main chain (default: %u)",
DEFAULT_STOPATHEIGHT));
strUsage += HelpMessageOpt(
"-limitancestorcount=",
strprintf("Do not accept transactions if number of in-mempool "
"ancestors is or more (default: %u)",
DEFAULT_ANCESTOR_LIMIT));
strUsage +=
HelpMessageOpt("-limitancestorsize=",
strprintf("Do not accept transactions whose size "
"with all in-mempool ancestors exceeds "
" kilobytes (default: %u)",
DEFAULT_ANCESTOR_SIZE_LIMIT));
strUsage += HelpMessageOpt(
"-limitdescendantcount=",
strprintf("Do not accept transactions if any ancestor would have "
" or more in-mempool descendants (default: %u)",
DEFAULT_DESCENDANT_LIMIT));
strUsage += HelpMessageOpt(
"-limitdescendantsize=",
strprintf("Do not accept transactions if any ancestor would have "
"more than kilobytes of in-mempool descendants "
"(default: %u).",
DEFAULT_DESCENDANT_SIZE_LIMIT));
strUsage += HelpMessageOpt("-addrmantest",
"Allows to test address relay on localhost");
}
strUsage += HelpMessageOpt(
"-debug=",
strprintf(_("Output debugging information (default: %u, supplying "
" is optional)"),
0) +
". " +
_("If is not supplied or if = 1, output all "
"debugging information.") +
_(" can be:") + " " + ListLogCategories() + ".");
strUsage += HelpMessageOpt(
"-debugexclude=",
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.")));
if (showDebug) {
strUsage += HelpMessageOpt(
"-nodebug", "Turn off debugging messages, same as -debug=0");
}
strUsage += HelpMessageOpt(
"-help-debug",
_("Show all debugging options (usage: --help -help-debug)"));
strUsage += HelpMessageOpt(
"-logips",
strprintf(_("Include IP addresses in debug output (default: %d)"),
DEFAULT_LOGIPS));
strUsage += HelpMessageOpt(
"-logtimestamps",
strprintf(_("Prepend debug output with timestamp (default: %d)"),
DEFAULT_LOGTIMESTAMPS));
if (showDebug) {
strUsage += HelpMessageOpt(
"-logtimemicros",
strprintf(
"Add microsecond precision to debug timestamps (default: %d)",
DEFAULT_LOGTIMEMICROS));
strUsage += HelpMessageOpt(
"-mocktime=",
"Replace actual time with seconds since epoch (default: 0)");
strUsage += HelpMessageOpt(
"-limitfreerelay=",
strprintf("Continuously rate-limit free transactions to *1000 "
"bytes per minute (default: %u)",
DEFAULT_LIMITFREERELAY));
strUsage +=
HelpMessageOpt("-relaypriority",
strprintf("Require high priority for relaying free "
"or low-fee transactions (default: %d)",
DEFAULT_RELAYPRIORITY));
strUsage += HelpMessageOpt(
"-maxsigcachesize=",
strprintf("Limit size of signature cache to MiB (default: %u)",
DEFAULT_MAX_SIG_CACHE_SIZE));
strUsage += HelpMessageOpt(
"-maxscriptcachesize=",
strprintf("Limit size of script cache to MiB (default: %u)",
DEFAULT_MAX_SCRIPT_CACHE_SIZE));
strUsage += HelpMessageOpt(
"-maxtipage=",
strprintf("Maximum tip age in seconds to consider node in initial "
"block download (default: %u)",
DEFAULT_MAX_TIP_AGE));
}
strUsage += HelpMessageOpt(
"-excessutxocharge=",
strprintf(_("Fees (in %s/kB) to charge per utxo created for"
"relaying, and mining (default: %s)"),
CURRENCY_UNIT, FormatMoney(DEFAULT_UTXO_FEE)));
strUsage += HelpMessageOpt(
"-minrelaytxfee=",
strprintf(
_("Fees (in %s/kB) smaller than this are considered zero fee for "
"relaying, mining and transaction creation (default: %s)"),
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE_PER_KB)));
strUsage += HelpMessageOpt(
"-maxtxfee=",
strprintf(_("Maximum total fees (in %s) to use in a single wallet "
"transaction or raw transaction; setting this too low may "
"abort large transactions (default: %s)"),
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)));
strUsage += HelpMessageOpt(
"-printtoconsole",
_("Send trace/debug info to console instead of debug.log file"));
if (showDebug) {
strUsage += HelpMessageOpt(
"-printpriority", strprintf("Log transaction priority and fee per "
"kB when mining blocks (default: %d)",
DEFAULT_PRINTPRIORITY));
}
strUsage += HelpMessageOpt("-shrinkdebugfile",
_("Shrink debug.log file on client startup "
"(default: 1 when no -debug)"));
AppendParamsHelpMessages(strUsage, showDebug);
strUsage += HelpMessageGroup(_("Node relay options:"));
if (showDebug) {
strUsage += HelpMessageOpt(
"-acceptnonstdtxn",
strprintf(
"Relay and mine \"non-standard\" transactions (%sdefault: %u)",
"testnet/regtest only; ",
defaultChainParams->RequireStandard()));
strUsage +=
HelpMessageOpt("-excessiveblocksize=",
strprintf(_("Do not accept blocks larger than this "
"limit, in bytes (default: %d)"),
DEFAULT_MAX_BLOCK_SIZE));
strUsage += HelpMessageOpt(
"-dustrelayfee=",
strprintf("Fee rate (in %s/kB) used to defined 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)",
CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)));
}
strUsage +=
HelpMessageOpt("-bytespersigop",
strprintf(_("Equivalent bytes per sigop in transactions "
"for relay and mining (default: %u)"),
DEFAULT_BYTES_PER_SIGOP));
strUsage += HelpMessageOpt(
"-datacarrier",
strprintf(_("Relay and mine data carrier transactions (default: %d)"),
DEFAULT_ACCEPT_DATACARRIER));
strUsage += HelpMessageOpt(
"-datacarriersize",
strprintf(_("Maximum size of data in data carrier transactions we "
"relay and mine (default: %u)"),
MAX_OP_RETURN_RELAY));
strUsage += HelpMessageGroup(_("Block creation options:"));
strUsage += HelpMessageOpt(
"-blockmaxsize=",
strprintf(_("Set maximum block size in bytes (default: %d)"),
DEFAULT_MAX_GENERATED_BLOCK_SIZE));
strUsage += HelpMessageOpt(
"-blockprioritypercentage=",
strprintf(_("Set maximum percentage of a block reserved to "
"high-priority/low-fee transactions (default: %d)"),
DEFAULT_BLOCK_PRIORITY_PERCENTAGE));
strUsage += HelpMessageOpt(
"-blockmintxfee=",
strprintf(_("Set lowest fee rate (in %s/kB) for transactions to be "
"included in block creation. (default: %s)"),
CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB)));
if (showDebug) {
strUsage +=
HelpMessageOpt("-blockversion=",
"Override block version to test forking scenarios");
}
strUsage += HelpMessageGroup(_("RPC server options:"));
strUsage += HelpMessageOpt("-server",
_("Accept command line and JSON-RPC commands"));
strUsage += HelpMessageOpt(
"-rest", strprintf(_("Accept public REST requests (default: %d)"),
DEFAULT_REST_ENABLE));
strUsage += HelpMessageOpt(
"-rpcbind=",
_("Bind to given address to listen for JSON-RPC connections. Use "
"[host]:port notation for IPv6. This option can be specified "
"multiple times (default: bind to all interfaces)"));
strUsage +=
HelpMessageOpt("-rpccookiefile=",
_("Location of the auth cookie (default: data dir)"));
strUsage += HelpMessageOpt("-rpcuser=",
_("Username for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcpassword=",
_("Password for JSON-RPC connections"));
strUsage += HelpMessageOpt(
"-rpcauth=",
_("Username and hashed password for JSON-RPC connections. The field "
" comes in the format: :$. A canonical "
"python script is included in share/rpcuser. The client then "
"connects normally using the "
"rpcuser=/rpcpassword= pair of arguments. This "
"option can be specified multiple times"));
strUsage += HelpMessageOpt(
"-rpcport=",
strprintf(_("Listen for JSON-RPC connections on (default: %u or "
"testnet: %u)"),
defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()));
strUsage += HelpMessageOpt(
"-rpcallowip=",
_("Allow JSON-RPC connections from specified source. Valid for "
"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"));
strUsage += HelpMessageOpt(
"-rpcthreads=",
strprintf(
_("Set the number of threads to service RPC calls (default: %d)"),
DEFAULT_HTTP_THREADS));
strUsage += HelpMessageOpt(
"-rpccorsdomain=value",
"Domain from which to accept cross origin requests (browser enforced)");
if (showDebug) {
strUsage += HelpMessageOpt(
"-rpcworkqueue=", strprintf("Set the depth of the work queue to "
"service RPC calls (default: %d)",
DEFAULT_HTTP_WORKQUEUE));
strUsage += HelpMessageOpt(
"-rpcservertimeout=",
strprintf("Timeout during HTTP requests (default: %d)",
DEFAULT_HTTP_SERVER_TIMEOUT));
}
return strUsage;
}
std::string LicenseInfo() {
const std::string URL_SOURCE_CODE =
"";
const std::string URL_WEBSITE = "";
return CopyrightHolders(
strprintf(_("Copyright (C) %i-%i"), 2009, COPYRIGHT_YEAR) +
" ") +
"\n" + "\n" +
strprintf(_("Please contribute if you find %s useful. "
"Visit %s for further information about the software."),
PACKAGE_NAME, URL_WEBSITE) +
"\n" +
strprintf(_("The source code is available from %s."),
URL_SOURCE_CODE) +
"\n" + "\n" + _("This is experimental software.") + "\n" +
strprintf(_("Distributed under the MIT software license, see the "
"accompanying file %s or %s"),
"COPYING", "") +
"\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."),
"") +
"\n";
}
static void BlockNotifyCallback(bool initialSync,
const CBlockIndex *pBlockIndex) {
if (initialSync || !pBlockIndex) {
return;
}
std::string strCmd = gArgs.GetArg("-blocknotify", "");
if (!strCmd.empty()) {
boost::replace_all(strCmd, "%s", pBlockIndex->GetBlockHash().GetHex());
std::thread t(runCommand, strCmd);
// thread runs free
t.detach();
}
}
static bool fHaveGenesis = false;
static CWaitableCriticalSection cs_GenesisWait;
static CConditionVariable condvar_GenesisWait;
static void BlockNotifyGenesisWait(bool, const CBlockIndex *pBlockIndex) {
if (pBlockIndex != nullptr) {
{
LOCK(cs_GenesisWait);
fHaveGenesis = true;
}
condvar_GenesisWait.notify_all();
}
}
struct CImportingNow {
CImportingNow() {
assert(fImporting == false);
fImporting = true;
}
~CImportingNow() {
assert(fImporting == true);
fImporting = false;
}
};
// If we're using -prune with -reindex, then delete block files that will be
// ignored by the reindex. Since reindexing works by starting at block file 0
// and looping until a blockfile is missing, do the same here to delete any
// later block files after a gap. Also delete all rev files since they'll be
// rewritten by the reindex anyway. This ensures that vinfoBlockFile is in sync
// with what's actually on disk by the time we start downloading, so that
// pruning works correctly.
void CleanupBlockRevFiles() {
std::map mapBlockFiles;
// Glob all blk?????.dat and rev?????.dat files from the blocks directory.
// Remove the rev files immediately and insert the blk file paths into an
// ordered map keyed by block file index.
LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for "
"-reindex with -prune\n");
fs::path blocksdir = GetDataDir() / "blocks";
for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator();
it++) {
if (is_regular_file(*it) &&
it->path().filename().string().length() == 12 &&
it->path().filename().string().substr(8, 4) == ".dat") {
if (it->path().filename().string().substr(0, 3) == "blk") {
mapBlockFiles[it->path().filename().string().substr(3, 5)] =
it->path();
} else if (it->path().filename().string().substr(0, 3) == "rev") {
remove(it->path());
}
}
}
// Remove all block files that aren't part of a contiguous set starting at
// zero by walking the ordered map (keys are block file indices) by keeping
// a separate counter. Once we hit a gap (or if 0 doesn't exist) start
// removing block files.
int nContigCounter = 0;
for (const std::pair &item : mapBlockFiles) {
if (atoi(item.first) == nContigCounter) {
nContigCounter++;
continue;
}
remove(item.second);
}
}
void ThreadImport(const Config &config, std::vector vImportFiles) {
RenameThread("bitcoin-loadblk");
{
CImportingNow imp;
// -reindex
if (fReindex) {
int nFile = 0;
while (true) {
CDiskBlockPos pos(nFile, 0);
if (!fs::exists(GetBlockPosFilename(pos, "blk"))) {
// No block files left to reindex
break;
}
FILE *file = OpenBlockFile(pos, true);
if (!file) {
// This error is logged in OpenBlockFile
break;
}
LogPrintf("Reindexing block file blk%05u.dat...\n",
(unsigned int)nFile);
LoadExternalBlockFile(config, file, &pos);
nFile++;
}
pblocktree->WriteReindexing(false);
fReindex = false;
LogPrintf("Reindexing finished\n");
// To avoid ending up in a situation without genesis block, re-try
// initializing (no-op if reindexing worked):
LoadGenesisBlock(config.GetChainParams());
}
// hardcoded $DATADIR/bootstrap.dat
fs::path pathBootstrap = GetDataDir() / "bootstrap.dat";
if (fs::exists(pathBootstrap)) {
FILE *file = fsbridge::fopen(pathBootstrap, "rb");
if (file) {
fs::path pathBootstrapOld = GetDataDir() / "bootstrap.dat.old";
LogPrintf("Importing bootstrap.dat...\n");
LoadExternalBlockFile(config, file);
RenameOver(pathBootstrap, pathBootstrapOld);
} else {
LogPrintf("Warning: Could not open bootstrap file %s\n",
pathBootstrap.string());
}
}
// -loadblock=
for (const fs::path &path : vImportFiles) {
FILE *file = fsbridge::fopen(path, "rb");
if (file) {
LogPrintf("Importing blocks file %s...\n", path.string());
LoadExternalBlockFile(config, file);
} else {
LogPrintf("Warning: Could not open blocks file %s\n",
path.string());
}
}
// scan for better chains in the block chain database, that are not yet
// connected in the active best chain
CValidationState state;
if (!ActivateBestChain(config, state)) {
LogPrintf("Failed to connect best block");
StartShutdown();
}
if (gArgs.GetBoolArg("-stopafterblockimport",
DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
StartShutdown();
}
} // End scope of CImportingNow
if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
LoadMempool(config);
fDumpMempoolLater = !fRequestShutdown;
}
}
/** Sanity checks
* Ensure that Bitcoin is running in a usable environment with all
* necessary library support.
*/
bool InitSanityCheck(void) {
if (!ECC_InitSanityCheck()) {
InitError(
"Elliptic curve cryptography sanity check failure. Aborting.");
return false;
}
if (!glibc_sanity_test() || !glibcxx_sanity_test()) {
return false;
}
if (!Random_SanityCheck()) {
InitError("OS cryptographic RNG sanity check failure. Aborting.");
return false;
}
return true;
}
static bool AppInitServers(Config &config,
HTTPRPCRequestProcessor &httpRPCRequestProcessor) {
RPCServerSignals::OnStarted(&OnRPCStarted);
RPCServerSignals::OnStopped(&OnRPCStopped);
if (!InitHTTPServer(config)) {
return false;
}
if (!StartRPC()) {
return false;
}
if (!StartHTTPRPC(config, httpRPCRequestProcessor)) {
return false;
}
if (gArgs.GetBoolArg("-rest", DEFAULT_REST_ENABLE) && !StartREST()) {
return false;
}
if (!StartHTTPServer()) {
return false;
}
return true;
}
// Parameter interaction based on rules
void InitParameterInteraction() {
// when specifying an explicit binding address, you want to listen on it
// even when -connect or -proxy is specified.
if (gArgs.IsArgSet("-bind")) {
if (gArgs.SoftSetBoolArg("-listen", true)) {
LogPrintf(
"%s: parameter interaction: -bind set -> setting -listen=1\n",
__func__);
}
}
if (gArgs.IsArgSet("-whitebind")) {
if (gArgs.SoftSetBoolArg("-listen", true)) {
LogPrintf("%s: parameter interaction: -whitebind set -> setting "
"-listen=1\n",
__func__);
}
}
if (gArgs.IsArgSet("-connect")) {
// when only connecting to trusted nodes, do not seed via DNS, or listen
// by default.
if (gArgs.SoftSetBoolArg("-dnsseed", false)) {
LogPrintf("%s: parameter interaction: -connect set -> setting "
"-dnsseed=0\n",
__func__);
}
if (gArgs.SoftSetBoolArg("-listen", false)) {
LogPrintf("%s: parameter interaction: -connect set -> setting "
"-listen=0\n",
__func__);
}
}
if (gArgs.IsArgSet("-proxy")) {
// to protect privacy, do not listen by default if a default proxy
// server is specified.
if (gArgs.SoftSetBoolArg("-listen", false)) {
LogPrintf(
"%s: parameter interaction: -proxy set -> setting -listen=0\n",
__func__);
}
// to protect privacy, do not use UPNP when a proxy is set. The user may
// still specify -listen=1 to listen locally, so don't rely on this
// happening through -listen below.
if (gArgs.SoftSetBoolArg("-upnp", false)) {
LogPrintf(
"%s: parameter interaction: -proxy set -> setting -upnp=0\n",
__func__);
}
// to protect privacy, do not discover addresses by default
if (gArgs.SoftSetBoolArg("-discover", false)) {
LogPrintf("%s: parameter interaction: -proxy set -> setting "
"-discover=0\n",
__func__);
}
}
if (!gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) {
// do not map ports or try to retrieve public IP when not listening
// (pointless)
if (gArgs.SoftSetBoolArg("-upnp", false)) {
LogPrintf(
"%s: parameter interaction: -listen=0 -> setting -upnp=0\n",
__func__);
}
if (gArgs.SoftSetBoolArg("-discover", false)) {
LogPrintf(
"%s: parameter interaction: -listen=0 -> setting -discover=0\n",
__func__);
}
if (gArgs.SoftSetBoolArg("-listenonion", false)) {
LogPrintf("%s: parameter interaction: -listen=0 -> setting "
"-listenonion=0\n",
__func__);
}
}
if (gArgs.IsArgSet("-externalip")) {
// if an explicit public IP is specified, do not try to find others
if (gArgs.SoftSetBoolArg("-discover", false)) {
LogPrintf("%s: parameter interaction: -externalip set -> setting "
"-discover=0\n",
__func__);
}
}
// disable whitelistrelay in blocksonly mode
if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)) {
if (gArgs.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 (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
if (gArgs.SoftSetBoolArg("-whitelistrelay", true)) {
LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> "
"setting -whitelistrelay=1\n",
__func__);
}
}
}
static std::string ResolveErrMsg(const char *const optname,
const std::string &strBind) {
return strprintf(_("Cannot resolve -%s address: '%s'"), optname, strBind);
}
void InitLogging() {
BCLog::Logger &logger = GetLogger();
logger.m_print_to_console = gArgs.GetBoolArg("-printtoconsole", false);
logger.m_log_timestamps =
gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
logger.m_log_time_micros =
gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
fLogIPs = gArgs.GetBoolArg("-logips", DEFAULT_LOGIPS);
LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
LogPrintf("%s version %s\n", CLIENT_NAME, FormatFullVersion());
}
namespace { // Variables internal to initialization process only
int nMaxConnections;
int nUserMaxConnections;
int nFD;
ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED);
} // 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() {
// 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));
#endif
#if _MSC_VER >= 1400
// 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)
// Minimum supported OS versions: WinXP SP3, WinVista >= SP1, Win Server 2008
// A failure is non-critical and needs no further attention!
#ifndef PROCESS_DEP_ENABLE
// We define this here, because GCCs winbase.h limits this to _WIN32_WINNT >=
// 0x0601 (Windows 7), which is not correct. Can be removed, when GCCs winbase.h
// is fixed!
#define PROCESS_DEP_ENABLE 0x00000001
#endif
typedef BOOL(WINAPI * PSETPROCDEPPOL)(DWORD);
PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(
GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy");
if (setProcDEPPol != nullptr) {
setProcDEPPol(PROCESS_DEP_ENABLE);
}
#endif
if (!SetupNetworking()) {
return InitError("Initializing networking failed");
}
#ifndef WIN32
if (!gArgs.GetBoolArg("-sysperms", false)) {
umask(077);
}
// Clean shutdown on SIGTERM
struct sigaction sa;
sa.sa_handler = HandleSIGTERM;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGINT, &sa, nullptr);
// Reopen debug.log on SIGHUP
struct sigaction sa_hup;
sa_hup.sa_handler = HandleSIGHUP;
sigemptyset(&sa_hup.sa_mask);
sa_hup.sa_flags = 0;
sigaction(SIGHUP, &sa_hup, nullptr);
// Ignore SIGPIPE, otherwise it will bring the daemon down if the client
// closes unexpectedly
signal(SIGPIPE, SIG_IGN);
#endif
std::set_new_handler(new_handler_terminate);
return true;
}
bool AppInitParameterInteraction(Config &config, RPCServer &rpcServer) {
const CChainParams &chainparams = config.GetChainParams();
// Step 2: parameter interactions
// also see: InitParameterInteraction()
// if using block pruning, then disallow txindex
if (gArgs.GetArg("-prune", 0)) {
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
return InitError(_("Prune mode is incompatible with -txindex."));
}
}
// if space reserved for high priority transactions is misconfigured
// stop program execution and warn the user with a proper error message
const int64_t blkprio = gArgs.GetArg("-blockprioritypercentage",
DEFAULT_BLOCK_PRIORITY_PERCENTAGE);
if (!config.SetBlockPriorityPercentage(blkprio)) {
return InitError(_("Block priority percentage has to belong to the "
"[0..100] interval."));
}
// -bind and -whitebind can't be set when not listening
size_t nUserBind =
gArgs.GetArgs("-bind").size() + gArgs.GetArgs("-whitebind").size();
if (nUserBind != 0 && !gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) {
return InitError(
"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 =
gArgs.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
nMaxConnections = std::max(nUserMaxConnections, 0);
// Trim requested connection counts, to fit into system limitations
nMaxConnections =
std::max(std::min(nMaxConnections,
(int)(FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS -
MAX_ADDNODE_CONNECTIONS)),
0);
nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS +
MAX_ADDNODE_CONNECTIONS);
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) {
InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, "
"because of system limitations."),
nUserMaxConnections, nMaxConnections));
}
// Step 3: parameter-to-internal-flags
if (gArgs.IsArgSet("-debug")) {
// Special-case: if -debug=0/-nodebug is set, turn off debugging
// messages
const std::vector &categories = gArgs.GetArgs("-debug");
if (find(categories.begin(), categories.end(), std::string("0")) ==
categories.end()) {
for (const auto &cat : categories) {
BCLog::LogFlags flag;
if (!GetLogCategory(flag, cat)) {
InitWarning(
strprintf(_("Unsupported logging category %s=%s."),
"-debug", cat));
}
GetLogger().EnableCategory(flag);
}
}
}
// Now remove the logging categories which were explicitly excluded
for (const std::string &cat : gArgs.GetArgs("-debugexclude")) {
BCLog::LogFlags flag;
if (!GetLogCategory(flag, cat)) {
InitWarning(strprintf(_("Unsupported logging category %s=%s."),
"-debugexclude", cat));
}
GetLogger().DisableCategory(flag);
}
// Check for -debugnet
if (gArgs.GetBoolArg("-debugnet", false)) {
InitWarning(
_("Unsupported argument -debugnet ignored, use -debug=net."));
}
// Check for -socks - as this is a privacy risk to continue, exit here
if (gArgs.IsArgSet("-socks")) {
return InitError(
_("Unsupported argument -socks found. Setting SOCKS version isn't "
"possible anymore, only SOCKS5 proxies are supported."));
}
// Check for -tor - as this is a privacy risk to continue, exit here
if (gArgs.GetBoolArg("-tor", false)) {
return InitError(_("Unsupported argument -tor found, use -onion."));
}
if (gArgs.GetBoolArg("-benchmark", false)) {
InitWarning(
_("Unsupported argument -benchmark ignored, use -debug=bench."));
}
if (gArgs.GetBoolArg("-whitelistalwaysrelay", false)) {
InitWarning(_("Unsupported argument -whitelistalwaysrelay ignored, use "
"-whitelistrelay and/or -whitelistforcerelay."));
}
if (gArgs.IsArgSet("-blockminsize")) {
InitWarning("Unsupported argument -blockminsize ignored.");
}
// Checkmempool and checkblockindex default to true in regtest mode
int ratio = std::min(
std::max(
gArgs.GetArg("-checkmempool",
chainparams.DefaultConsistencyChecks() ? 1 : 0),
0),
1000000);
if (ratio != 0) {
g_mempool.setSanityCheck(1.0 / ratio);
}
fCheckBlockIndex = gArgs.GetBoolArg("-checkblockindex",
chainparams.DefaultConsistencyChecks());
fCheckpointsEnabled =
gArgs.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);
hashAssumeValid = uint256S(
gArgs.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 (gArgs.IsArgSet("-minimumchainwork")) {
const std::string minChainWorkStr =
gArgs.GetArg("-minimumchainwork", "");
if (!IsHexNumber(minChainWorkStr)) {
return InitError(strprintf(
"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 =
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
int64_t nMempoolSizeMin =
gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) *
1000 * 40;
if (nMempoolSizeMax < 0 || nMempoolSizeMax < nMempoolSizeMin) {
return InitError(strprintf(_("-maxmempool must be at least %d MB"),
std::ceil(nMempoolSizeMin / 1000000.0)));
}
// -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency
nScriptCheckThreads = gArgs.GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS);
if (nScriptCheckThreads <= 0) {
nScriptCheckThreads += GetNumCores();
}
if (nScriptCheckThreads <= 1) {
nScriptCheckThreads = 0;
} else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS) {
nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
}
// Configure excessive block size.
const uint64_t nProposedExcessiveBlockSize =
gArgs.GetArg("-excessiveblocksize", DEFAULT_MAX_BLOCK_SIZE);
if (!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 uint64_t nProposedMaxGeneratedBlockSize =
gArgs.GetArg("-blockmaxsize", DEFAULT_MAX_GENERATED_BLOCK_SIZE);
if (nProposedMaxGeneratedBlockSize > config.GetMaxBlockSize()) {
auto msg = _("Max generated block size (blockmaxsize) cannot exceed "
"the excessive block size (excessiveblocksize)");
return InitError(msg);
}
// block pruning; get the amount of disk space (in MiB) to allot for block &
// undo files
int64_t nPruneArg = gArgs.GetArg("-prune", 0);
if (nPruneArg < 0) {
return InitError(
_("Prune cannot be configured with a negative value."));
}
nPruneTarget = (uint64_t)nPruneArg * 1024 * 1024;
if (nPruneArg == 1) {
// manual pruning: -prune=1
LogPrintf("Block pruning enabled. Use RPC call "
"pruneblockchain(height) to manually prune block and undo "
"files.\n");
nPruneTarget = std::numeric_limits::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 %uMiB on disk for block and undo "
"files.\n",
nPruneTarget / 1024 / 1024);
fPruneMode = true;
}
RegisterAllRPCCommands(config, rpcServer, tableRPC);
g_wallet_init_interface->RegisterRPC(tableRPC);
#ifdef ENABLE_WALLET
RegisterDumpRPCCommands(tableRPC);
#endif
nConnectTimeout = gArgs.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
if (nConnectTimeout <= 0) {
nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
}
// Obtain the amount to charge excess UTXO
if (gArgs.IsArgSet("-excessutxocharge")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(gArgs.GetArg("-excessutxocharge", ""), n);
if (!parsed || Amount::zero() > n) {
return InitError(AmountErrMsg(
"excessutxocharge", gArgs.GetArg("-excessutxocharge", "")));
}
config.SetExcessUTXOCharge(n);
} else {
config.SetExcessUTXOCharge(DEFAULT_UTXO_FEE);
}
// Fee-per-kilobyte amount considered the same as "free". If you are mining,
// be careful setting this: if you set it to zero then a transaction spammer
// can cheaply fill blocks using 1-satoshi-fee transactions. It should be
// set above the real cost to you of processing a transaction.
if (gArgs.IsArgSet("-minrelaytxfee")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n);
if (!parsed || Amount::zero() == n) {
return InitError(AmountErrMsg("minrelaytxfee",
gArgs.GetArg("-minrelaytxfee", "")));
}
// High fee check is done afterward in WalletParameterInteraction()
config.SetMinFeePerKB(CFeeRate(n));
} else {
config.SetMinFeePerKB(CFeeRate(DEFAULT_MIN_RELAY_TX_FEE_PER_KB));
}
// Sanity check argument for min fee for including tx in block
// TODO: Harmonize which arguments need sanity checking and where that
// happens.
if (gArgs.IsArgSet("-blockmintxfee")) {
Amount n = Amount::zero();
if (!ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n)) {
return InitError(AmountErrMsg("blockmintxfee",
gArgs.GetArg("-blockmintxfee", "")));
}
}
// Feerate used to define dust. Shouldn't be changed lightly as old
// implementations may inadvertently create non-standard transactions.
if (gArgs.IsArgSet("-dustrelayfee")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n);
if (!parsed || Amount::zero() == n) {
return InitError(AmountErrMsg("dustrelayfee",
gArgs.GetArg("-dustrelayfee", "")));
}
dustRelayFee = CFeeRate(n);
}
fRequireStandard =
!gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
if (chainparams.RequireStandard() && !fRequireStandard) {
return InitError(
strprintf("acceptnonstdtxn is not currently supported for %s chain",
chainparams.NetworkIDString()));
}
nBytesPerSigOp = gArgs.GetArg("-bytespersigop", nBytesPerSigOp);
if (!g_wallet_init_interface->ParameterInteraction()) {
return false;
}
fIsBareMultisigStd =
gArgs.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
fAcceptDatacarrier =
gArgs.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER);
// Option to startup with mocktime set (used for regression testing):
SetMockTime(gArgs.GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op
if (gArgs.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS)) {
nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);
}
// Signal Bitcoin Cash support.
// TODO: remove some time after the hardfork when no longer needed
// to differentiate the network nodes.
nLocalServices = ServiceFlags(nLocalServices | NODE_BITCOIN_CASH);
nMaxTipAge = gArgs.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE);
return true;
}
static bool LockDataDirectory(bool probeOnly) {
std::string strDataDir = GetDataDir().string();
// Make sure only a single Bitcoin process is using the data directory.
fs::path pathLockFile = GetDataDir() / ".lock";
// empty lock file; created if it doesn't exist.
FILE *file = fsbridge::fopen(pathLockFile, "a");
if (file) {
fclose(file);
}
try {
static boost::interprocess::file_lock lock(
pathLockFile.string().c_str());
if (!lock.try_lock()) {
return InitError(
strprintf(_("Cannot obtain a lock on data directory %s. %s is "
"probably already running."),
strDataDir, _(PACKAGE_NAME)));
}
if (probeOnly) {
lock.unlock();
}
} catch (const boost::interprocess::interprocess_exception &e) {
return InitError(strprintf(_("Cannot obtain a lock on data directory "
"%s. %s is probably already running.") +
" %s.",
strDataDir, _(PACKAGE_NAME), e.what()));
}
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 AppInitMain(Config &config,
HTTPRPCRequestProcessor &httpRPCRequestProcessor) {
// Step 4a: application initialization
const CChainParams &chainparams = config.GetChainParams();
#ifndef WIN32
CreatePidFile(GetPidFile(), getpid());
#endif
BCLog::Logger &logger = GetLogger();
bool default_shrinkdebugfile = logger.DefaultShrinkDebugFile();
if (gArgs.GetBoolArg("-shrinkdebugfile", default_shrinkdebugfile)) {
// 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.m_print_to_file) {
if (!logger.OpenDebugLog()) {
return InitError(strprintf("Could not open debug log file %s",
logger.GetDebugLogPath().string()));
}
}
if (!logger.m_log_timestamps) {
- LogPrintf("Startup time: %s\n",
- DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime()));
+ LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime()));
}
LogPrintf("Default data directory %s\n", GetDefaultDataDir().string());
LogPrintf("Using data directory %s\n", GetDataDir().string());
LogPrintf(
"Using config file %s\n",
GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string());
LogPrintf("Using at most %i automatic connections (%i file descriptors "
"available)\n",
nMaxConnections, nFD);
// Warn about relative -datadir path.
if (gArgs.IsArgSet("-datadir") &&
!fs::path(gArgs.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",
gArgs.GetArg("-datadir", ""), fs::current_path().string());
}
InitSignatureCache();
InitScriptExecutionCache();
LogPrintf("Using %u threads for script verification\n",
nScriptCheckThreads);
if (nScriptCheckThreads) {
for (int i = 0; i < nScriptCheckThreads - 1; i++) {
threadGroup.create_thread(&ThreadScriptCheck);
}
}
// Start the lightweight task scheduler thread
CScheduler::Function serviceLoop =
boost::bind(&CScheduler::serviceQueue, &scheduler);
threadGroup.create_thread(boost::bind(&TraceThread,
"scheduler", serviceLoop));
GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
GetMainSignals().RegisterWithMempoolSignals(g_mempool);
/**
* 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 (gArgs.GetBoolArg("-server", false)) {
uiInterface.InitMessage.connect(SetRPCWarmupStatus);
if (!AppInitServers(config, httpRPCRequestProcessor)) {
return InitError(
_("Unable to start HTTP server. See debug log for details."));
}
}
// Step 5: verify wallet database integrity
if (!g_wallet_init_interface->Verify(chainparams)) {
return false;
}
// Step 6: network initialization
// Note that we absolutely cannot open any actual connections
// until the very end ("start node") as the UTXO/block state
// is not yet setup and may end up being set up twice if we
// need to reindex later.
assert(!g_connman);
g_connman = std::unique_ptr(
new CConnman(config, GetRand(std::numeric_limits::max()),
GetRand(std::numeric_limits::max())));
CConnman &connman = *g_connman;
peerLogic.reset(new PeerLogicValidation(&connman, scheduler));
RegisterValidationInterface(peerLogic.get());
// sanitize comments per BIP-0014, format user agent and check total size
std::vector uacomments;
for (const std::string &cmt : gArgs.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 strSubVersion =
FormatSubVersion(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 (gArgs.IsArgSet("-onlynet")) {
std::set nets;
for (const std::string &snet : gArgs.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)) SetLimited(net);
}
}
// Check for host lookup allowed before parsing any network related
// parameters
fNameLookup = gArgs.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
bool proxyRandomize =
gArgs.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 = gArgs.GetArg("-proxy", "");
SetLimited(NET_TOR);
if (proxyArg != "" && proxyArg != "0") {
CService proxyAddr;
if (!Lookup(proxyArg.c_str(), 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_TOR, addrProxy);
SetNameProxy(addrProxy);
// by default, -proxy sets onion as reachable, unless -noonion later
SetLimited(NET_TOR, false);
}
// -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 = gArgs.GetArg("-onion", "");
if (onionArg != "") {
if (onionArg == "0") { // Handle -noonion/-onion=0
SetLimited(NET_TOR); // set onions as unreachable
} else {
CService onionProxy;
if (!Lookup(onionArg.c_str(), 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_TOR, addrOnion);
SetLimited(NET_TOR, false);
}
}
// see Step 2: parameter interactions for more information about these
fListen = gArgs.GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = gArgs.GetBoolArg("-discover", true);
fRelayTxes = !gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY);
for (const std::string &strAddr : gArgs.GetArgs("-externalip")) {
CService addrLocal;
if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) &&
addrLocal.IsValid()) {
AddLocal(addrLocal, LOCAL_MANUAL);
} else {
return InitError(ResolveErrMsg("externalip", strAddr));
}
}
#if ENABLE_ZMQ
pzmqNotificationInterface = CZMQNotificationInterface::Create();
if (pzmqNotificationInterface) {
RegisterValidationInterface(pzmqNotificationInterface);
}
#endif
// unlimited unless -maxuploadtarget is set
uint64_t nMaxOutboundLimit = 0;
uint64_t nMaxOutboundTimeframe = MAX_UPLOAD_TIMEFRAME;
if (gArgs.IsArgSet("-maxuploadtarget")) {
nMaxOutboundLimit =
gArgs.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET) * 1024 *
1024;
}
// Step 7: load block chain
fReindex = gArgs.GetBoolArg("-reindex", false);
bool fReindexChainState = gArgs.GetBoolArg("-reindex-chainstate", false);
// cache size calculations
int64_t nTotalCache = (gArgs.GetArg("-dbcache", nDefaultDbCache) << 20);
// total cache cannot be less than nMinDbCache
nTotalCache = std::max(nTotalCache, nMinDbCache << 20);
// total cache cannot be greater than nMaxDbcache
nTotalCache = std::min(nTotalCache, nMaxDbCache << 20);
int64_t nBlockTreeDBCache = nTotalCache / 8;
nBlockTreeDBCache = std::min(nBlockTreeDBCache,
(gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)
? nMaxBlockDBAndTxIndexCache
: nMaxBlockDBCache)
<< 20);
nTotalCache -= nBlockTreeDBCache;
// 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, nMaxCoinsDBCache << 20);
nTotalCache -= nCoinDBCache;
// the rest goes to in-memory cache
nCoinCacheUsage = nTotalCache;
int64_t nMempoolSizeMax =
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
LogPrintf("Cache configuration:\n");
LogPrintf("* Using %.1fMiB for block index database\n",
nBlockTreeDBCache * (1.0 / 1024 / 1024));
LogPrintf("* Using %.1fMiB for chain state database\n",
nCoinDBCache * (1.0 / 1024 / 1024));
LogPrintf("* Using %.1fMiB for in-memory UTXO set (plus up to %.1fMiB of "
"unused mempool space)\n",
nCoinCacheUsage * (1.0 / 1024 / 1024),
nMempoolSizeMax * (1.0 / 1024 / 1024));
int64_t nStart = 0;
bool fLoaded = false;
while (!fLoaded && !fRequestShutdown) {
bool fReset = fReindex;
std::string strLoadError;
uiInterface.InitMessage(_("Loading block index..."));
nStart = GetTimeMillis();
do {
try {
UnloadBlockIndex();
pcoinsTip.reset();
pcoinsdbview.reset();
pcoinscatcher.reset();
pblocktree.reset(
new CBlockTreeDB(nBlockTreeDBCache, false, fReset));
if (fReindex) {
pblocktree->WriteReindexing(true);
// If we're reindexing in prune mode, wipe away unusable
// block files and all undo data files
if (fPruneMode) {
CleanupBlockRevFiles();
}
}
if (fRequestShutdown) {
break;
}
// LoadBlockIndex will load fTxIndex from the db, or set it if
// we're reindexing. It will also load fHavePruned if we've
// ever removed a block file from disk.
if (!LoadBlockIndex(config)) {
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 (!mapBlockIndex.empty() &&
mapBlockIndex.count(
chainparams.GetConsensus().hashGenesisBlock) == 0) {
return InitError(_("Incorrect or no genesis block found. "
"Wrong datadir for network?"));
}
// Check for changed -txindex state
if (fTxIndex != gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
strLoadError = _("You need to rebuild the database using "
"-reindex-chainstate to change -txindex");
break;
}
// 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. This is called again in ThreadImport
// if the reindex completes.
if (!fReindex && !LoadGenesisBlock(chainparams)) {
strLoadError = _("Error initializing block database");
break;
}
// At this point we're either in reindex or we've loaded a
// useful block tree into mapBlockIndex!
pcoinsdbview.reset(new CCoinsViewDB(
nCoinDBCache, false, fReset || fReindexChainState));
pcoinscatcher.reset(
new CCoinsViewErrorCatcher(pcoinsdbview.get()));
// If necessary, upgrade from older database format.
// This is a no-op if we cleared the coinsviewdb with -reindex
// or -reindex-chainstate
if (!pcoinsdbview->Upgrade()) {
strLoadError = _("Error upgrading chainstate database");
break;
}
// ReplayBlocks is a no-op if we cleared the coinsviewdb with
// -reindex or -reindex-chainstate
if (!ReplayBlocks(config, pcoinsdbview.get())) {
strLoadError =
_("Unable to replay blocks. You will need to rebuild "
"the database using -reindex-chainstate.");
break;
}
// The on-disk coinsdb is now in a good state, create the cache
pcoinsTip.reset(new CCoinsViewCache(pcoinscatcher.get()));
if (!fReindex && !fReindexChainState) {
// LoadChainTip sets chainActive based on pcoinsTip's best
// block
if (!LoadChainTip(config)) {
strLoadError = _("Error initializing block database");
break;
}
assert(chainActive.Tip() != nullptr);
}
if (!fReindex) {
// Note that RewindBlockIndex MUST run even if we're about
// to -reindex-chainstate. It both disconnects blocks based
// on chainActive, and drops block data in mapBlockIndex
// based on lack of available witness data.
uiInterface.InitMessage(_("Rewinding blocks..."));
if (!RewindBlockIndex(config)) {
strLoadError = _("Unable to rewind the database to a "
"pre-fork state. You will need to "
"redownload the blockchain");
break;
}
}
if (!fReindex && !fReindexChainState) {
uiInterface.InitMessage(_("Verifying blocks..."));
if (fHavePruned &&
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) >
MIN_BLOCKS_TO_KEEP) {
LogPrintf("Prune: pruned datadir may not have more "
"than %d blocks; only checking available "
"blocks",
MIN_BLOCKS_TO_KEEP);
}
{
LOCK(cs_main);
CBlockIndex *tip = chainActive.Tip();
RPCNotifyBlockChange(true, tip);
if (tip && tip->nTime > GetAdjustedTime() +
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");
break;
}
}
if (!CVerifyDB().VerifyDB(
config, pcoinsdbview.get(),
gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
gArgs.GetArg("-checkblocks",
DEFAULT_CHECKBLOCKS))) {
strLoadError = _("Corrupted block database detected");
break;
}
}
} catch (const std::exception &e) {
LogPrintf("%s\n", e.what());
strLoadError = _("Error opening block database");
break;
}
fLoaded = true;
} while (false);
if (!fLoaded && !fRequestShutdown) {
// first suggest a reindex
if (!fReset) {
bool fRet = uiInterface.ThreadSafeQuestion(
strLoadError + ".\n\n" +
_("Do you want to rebuild the block database now?"),
strLoadError + ".\nPlease restart with -reindex or "
"-reindex-chainstate to recover.",
"",
CClientUIInterface::MSG_ERROR |
CClientUIInterface::BTN_ABORT);
if (fRet) {
fReindex = true;
fRequestShutdown = false;
} 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 (fRequestShutdown) {
LogPrintf("Shutdown requested. Exiting.\n");
return false;
}
LogPrintf(" block index %15dms\n", GetTimeMillis() - nStart);
// Encoded addresses using cashaddr instead of base58
// Activates by default on Jan, 14
config.SetCashAddrEncoding(
gArgs.GetBoolArg("-usecashaddr", GetAdjustedTime() > 1515900000));
// Step 8: load wallet
if (!g_wallet_init_interface->Open(chainparams)) {
return false;
}
// Step 9: 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) {
uiInterface.InitMessage(_("Pruning blockstore..."));
PruneAndFlush();
}
}
// Step 10: import blocks
if (!CheckDiskSpace()) {
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.
if (chainActive.Tip() == nullptr) {
uiInterface.NotifyBlockTip.connect(BlockNotifyGenesisWait);
} else {
fHaveGenesis = true;
}
if (gArgs.IsArgSet("-blocknotify")) {
uiInterface.NotifyBlockTip.connect(BlockNotifyCallback);
}
std::vector vImportFiles;
for (const std::string &strFile : gArgs.GetArgs("-loadblock")) {
vImportFiles.push_back(strFile);
}
threadGroup.create_thread(
boost::bind(&ThreadImport, std::ref(config), vImportFiles));
// Wait for genesis block to be processed
{
WAIT_LOCK(cs_GenesisWait, 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()) {
condvar_GenesisWait.wait_for(lock, std::chrono::milliseconds(500));
}
uiInterface.NotifyBlockTip.disconnect(BlockNotifyGenesisWait);
}
// Step 11: start node
int chain_active_height;
//// debug print
{
LOCK(cs_main);
LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size());
chain_active_height = chainActive.Height();
}
LogPrintf("nBestHeight = %d\n", chain_active_height);
if (gArgs.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
StartTorControl();
}
Discover();
// Map ports with UPnP
if (gArgs.GetBoolArg("-upnp", DEFAULT_UPNP)) {
StartMapPort();
}
CConnman::Options connOptions;
connOptions.nLocalServices = nLocalServices;
connOptions.nMaxConnections = nMaxConnections;
connOptions.nMaxOutbound =
std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections);
connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
connOptions.nMaxFeeler = 1;
connOptions.nBestHeight = chain_active_height;
connOptions.uiInterface = &uiInterface;
connOptions.m_msgproc = peerLogic.get();
connOptions.nSendBufferMaxSize =
1000 * gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
connOptions.nReceiveFloodSize =
1000 * gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
connOptions.m_added_nodes = gArgs.GetArgs("-addnode");
connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe;
connOptions.nMaxOutboundLimit = nMaxOutboundLimit;
for (const std::string &strBind : gArgs.GetArgs("-bind")) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) {
return InitError(ResolveErrMsg("bind", strBind));
}
connOptions.vBinds.push_back(addrBind);
}
for (const std::string &strBind : gArgs.GetArgs("-whitebind")) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, 0, false)) {
return InitError(ResolveErrMsg("whitebind", strBind));
}
if (addrBind.GetPort() == 0) {
return InitError(strprintf(
_("Need to specify a port with -whitebind: '%s'"), strBind));
}
connOptions.vWhiteBinds.push_back(addrBind);
}
for (const auto &net : gArgs.GetArgs("-whitelist")) {
CSubNet subnet;
LookupSubNet(net.c_str(), subnet);
if (!subnet.IsValid()) {
return InitError(strprintf(
_("Invalid netmask specified in -whitelist: '%s'"), net));
}
connOptions.vWhitelistedRange.push_back(subnet);
}
connOptions.vSeedNodes = gArgs.GetArgs("-seednode");
// Initiate outbound connections unless connect=0
connOptions.m_use_addrman_outgoing = !gArgs.IsArgSet("-connect");
if (!connOptions.m_use_addrman_outgoing) {
const auto connect = gArgs.GetArgs("-connect");
if (connect.size() != 1 || connect[0] != "0") {
connOptions.m_specified_outgoing = connect;
}
}
if (!connman.Start(scheduler, connOptions)) {
return false;
}
// Step 12: finished
SetRPCWarmupFinished();
uiInterface.InitMessage(_("Done loading"));
g_wallet_init_interface->Start(scheduler);
return !fRequestShutdown;
}
diff --git a/src/logging.cpp b/src/logging.cpp
index b3dcd72625..2943680a8d 100644
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -1,271 +1,269 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017-2018 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "logging.h"
#include "util.h"
#include "utiltime.h"
bool fLogIPs = DEFAULT_LOGIPS;
const char *const DEFAULT_DEBUGLOGFILE = "debug.log";
/**
* NOTE: the logger instance is leaked on exit. This is ugly, but will be
* cleaned up by the OS/libc. Defining a logger as a global object doesn't work
* since the order of destruction of static/global objects is undefined.
* Consider if the logger gets destroyed, and then some later destructor calls
* LogPrintf, maybe indirectly, and you get a core dump at shutdown trying to
* access the logger. When the shutdown sequence is fully audited and tested,
* explicit destruction of these objects can be implemented by changing this
* from a raw pointer to a std::unique_ptr.
*
* This method of initialization was originally introduced in
* ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c.
*/
BCLog::Logger &GetLogger() {
static BCLog::Logger *const logger = new BCLog::Logger();
return *logger;
}
static int FileWriteStr(const std::string &str, FILE *fp) {
return fwrite(str.data(), 1, str.size(), fp);
}
fs::path BCLog::Logger::GetDebugLogPath() {
fs::path logfile(gArgs.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE));
if (logfile.is_absolute()) {
return logfile;
} else {
return GetDataDir() / logfile;
}
}
bool BCLog::Logger::OpenDebugLog() {
std::lock_guard scoped_lock(m_file_mutex);
assert(m_fileout == nullptr);
fs::path pathDebug = GetDebugLogPath();
m_fileout = fsbridge::fopen(pathDebug, "a");
if (!m_fileout) {
return false;
}
// Unbuffered.
setbuf(m_fileout, nullptr);
// Dump buffered messages from before we opened the log.
while (!m_msgs_before_open.empty()) {
FileWriteStr(m_msgs_before_open.front(), m_fileout);
m_msgs_before_open.pop_front();
}
return true;
}
struct CLogCategoryDesc {
BCLog::LogFlags flag;
std::string category;
};
const CLogCategoryDesc LogCategories[] = {
{BCLog::NONE, "0"},
{BCLog::NONE, "none"},
{BCLog::NET, "net"},
{BCLog::TOR, "tor"},
{BCLog::MEMPOOL, "mempool"},
{BCLog::HTTP, "http"},
{BCLog::BENCH, "bench"},
{BCLog::ZMQ, "zmq"},
{BCLog::DB, "db"},
{BCLog::RPC, "rpc"},
{BCLog::ESTIMATEFEE, "estimatefee"},
{BCLog::ADDRMAN, "addrman"},
{BCLog::SELECTCOINS, "selectcoins"},
{BCLog::REINDEX, "reindex"},
{BCLog::CMPCTBLOCK, "cmpctblock"},
{BCLog::RAND, "rand"},
{BCLog::PRUNE, "prune"},
{BCLog::PROXY, "proxy"},
{BCLog::MEMPOOLREJ, "mempoolrej"},
{BCLog::LIBEVENT, "libevent"},
{BCLog::COINDB, "coindb"},
{BCLog::QT, "qt"},
{BCLog::LEVELDB, "leveldb"},
{BCLog::ALL, "1"},
{BCLog::ALL, "all"},
};
bool GetLogCategory(BCLog::LogFlags &flag, const std::string &str) {
if (str == "") {
flag = BCLog::ALL;
return true;
}
for (const CLogCategoryDesc &category_desc : LogCategories) {
if (category_desc.category == str) {
flag = category_desc.flag;
return true;
}
}
return false;
}
std::string ListLogCategories() {
std::string ret;
int outcount = 0;
for (const CLogCategoryDesc &category_desc : LogCategories) {
// Omit the special cases.
if (category_desc.flag != BCLog::NONE &&
category_desc.flag != BCLog::ALL) {
if (outcount != 0) {
ret += ", ";
}
ret += category_desc.category;
outcount++;
}
}
return ret;
}
BCLog::Logger::~Logger() {
if (m_fileout) {
fclose(m_fileout);
}
}
std::string BCLog::Logger::LogTimestampStr(const std::string &str) {
std::string strStamped;
if (!m_log_timestamps) {
return str;
}
if (m_started_new_line) {
int64_t nTimeMicros = GetTimeMicros();
- strStamped =
- DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nTimeMicros / 1000000);
+ strStamped = FormatISO8601DateTime(nTimeMicros / 1000000);
if (m_log_time_micros) {
strStamped += strprintf(".%06d", nTimeMicros % 1000000);
}
int64_t mocktime = GetMockTime();
if (mocktime) {
- strStamped += " (mocktime: " +
- DateTimeStrFormat("%Y-%m-%d %H:%M:%S", mocktime) +
- ")";
+ strStamped +=
+ " (mocktime: " + FormatISO8601DateTime(mocktime) + ")";
}
strStamped += ' ' + str;
} else {
strStamped = str;
}
if (!str.empty() && str[str.size() - 1] == '\n') {
m_started_new_line = true;
} else {
m_started_new_line = false;
}
return strStamped;
}
int BCLog::Logger::LogPrintStr(const std::string &str) {
// Returns total number of characters written.
int ret = 0;
std::string strTimestamped = LogTimestampStr(str);
if (m_print_to_console) {
// Print to console.
ret = fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout);
fflush(stdout);
} else if (m_print_to_file) {
std::lock_guard scoped_lock(m_file_mutex);
// Buffer if we haven't opened the log yet.
if (m_fileout == nullptr) {
ret = strTimestamped.length();
m_msgs_before_open.push_back(strTimestamped);
} else {
// Reopen the log file, if requested.
if (m_reopen_file) {
m_reopen_file = false;
fs::path pathDebug = GetDebugLogPath();
if (fsbridge::freopen(pathDebug, "a", m_fileout) != nullptr) {
// unbuffered.
setbuf(m_fileout, nullptr);
}
}
ret = FileWriteStr(strTimestamped, m_fileout);
}
}
return ret;
}
void BCLog::Logger::ShrinkDebugFile() {
// Amount of debug.log to save at end when shrinking (must fit in memory)
constexpr size_t RECENT_DEBUG_HISTORY_SIZE = 10 * 1000000;
// Scroll debug.log if it's getting too big.
fs::path pathLog = GetDebugLogPath();
FILE *file = fsbridge::fopen(pathLog, "r");
// If debug.log file is more than 10% bigger the RECENT_DEBUG_HISTORY_SIZE
// trim it down by saving only the last RECENT_DEBUG_HISTORY_SIZE bytes.
if (file &&
fs::file_size(pathLog) > 11 * (RECENT_DEBUG_HISTORY_SIZE / 10)) {
// Restart the file with some of the end.
std::vector vch(RECENT_DEBUG_HISTORY_SIZE, 0);
fseek(file, -((long)vch.size()), SEEK_END);
int nBytes = fread(vch.data(), 1, vch.size(), file);
fclose(file);
file = fsbridge::fopen(pathLog, "w");
if (file) {
fwrite(vch.data(), 1, nBytes, file);
fclose(file);
}
} else if (file != nullptr) {
fclose(file);
}
}
void BCLog::Logger::EnableCategory(LogFlags category) {
m_categories |= category;
}
bool BCLog::Logger::EnableCategory(const std::string &str) {
BCLog::LogFlags flag;
if (!GetLogCategory(flag, str)) {
return false;
}
EnableCategory(flag);
return true;
}
void BCLog::Logger::DisableCategory(LogFlags category) {
m_categories &= ~category;
}
bool BCLog::Logger::DisableCategory(const std::string &str) {
BCLog::LogFlags flag;
if (!GetLogCategory(flag, str)) {
return false;
}
DisableCategory(flag);
return true;
}
bool BCLog::Logger::WillLogCategory(LogFlags category) const {
// ALL is not meant to be used as a logging category, but only as a mask
// representing all categories.
if (category == BCLog::NONE || category == BCLog::ALL) {
LogPrintf("Error trying to log using a category mask instead of an "
"explicit category.\n");
return true;
}
return (m_categories.load(std::memory_order_relaxed) & category) != 0;
}
bool BCLog::Logger::DefaultShrinkDebugFile() const {
return m_categories != BCLog::NONE;
}
diff --git a/src/net.cpp b/src/net.cpp
index 3a599be7a2..a5a53533d9 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1,3096 +1,3095 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-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.
#if defined(HAVE_CONFIG_H)
#include "config/bitcoin-config.h"
#endif
#include "net.h"
#include "addrman.h"
#include "chainparams.h"
#include "clientversion.h"
#include "config.h"
#include "consensus/consensus.h"
#include "crypto/common.h"
#include "crypto/sha256.h"
#include "hash.h"
#include "netbase.h"
#include "primitives/transaction.h"
#include "scheduler.h"
#include "ui_interface.h"
#include "utilstrencodings.h"
#ifdef WIN32
#include
#else
#include
#endif
#ifdef USE_UPNP
#include
#include
#include
#include
#endif
#include
// Dump addresses to peers.dat and banlist.dat every 15 minutes (900s)
#define DUMP_ADDRESSES_INTERVAL 900
// We add a random period time (0 to 1 seconds) to feeler connections to prevent
// synchronization.
#define FEELER_SLEEP_WINDOW 1
// MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define
// it as 0
#if !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
#endif
// MSG_DONTWAIT is not available on some platforms, if it doesn't exist define
// it as 0
#if !defined(MSG_DONTWAIT)
#define MSG_DONTWAIT 0
#endif
// Fix for ancient MinGW versions, that don't have defined these in ws2tcpip.h.
// Todo: Can be removed when our pull-tester is upgraded to a modern MinGW
// version.
#ifdef WIN32
#ifndef PROTECTION_LEVEL_UNRESTRICTED
#define PROTECTION_LEVEL_UNRESTRICTED 10
#endif
#ifndef IPV6_PROTECTION_LEVEL
#define IPV6_PROTECTION_LEVEL 23
#endif
#endif
/** Used to pass flags to the Bind() function */
enum BindFlags {
BF_NONE = 0,
BF_EXPLICIT = (1U << 0),
BF_REPORT_ERROR = (1U << 1),
BF_WHITELIST = (1U << 2),
};
const static 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;
//
// Global state variables
//
bool fDiscover = true;
bool fListen = true;
bool fRelayTxes = true;
CCriticalSection cs_mapLocalHost;
std::map mapLocalHost;
static bool vfLimited[NET_MAX] = {};
limitedmap mapAlreadyAskedFor(MAX_INV_SZ);
void CConnman::AddOneShot(const std::string &strDest) {
LOCK(cs_vOneShots);
vOneShots.push_back(strDest);
}
unsigned short GetListenPort() {
return (unsigned short)(gArgs.GetArg("-port", Params().GetDefaultPort()));
}
// find 'best' local address for a particular peer
bool GetLocal(CService &addr, const CNetAddr *paddrPeer) {
if (!fListen) {
return false;
}
int nBestScore = -1;
int nBestReachability = -1;
{
LOCK(cs_mapLocalHost);
for (const auto &entry : mapLocalHost) {
int nScore = entry.second.nScore;
int nReachability = entry.first.GetReachabilityFrom(paddrPeer);
if (nReachability > nBestReachability ||
(nReachability == nBestReachability && nScore > nBestScore)) {
addr = CService(entry.first, entry.second.nPort);
nBestReachability = nReachability;
nBestScore = nScore;
}
}
}
return nBestScore >= 0;
}
//! Convert the pnSeeds6 array into usable address objects.
static std::vector
convertSeed6(const std::vector &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 vSeedsOut;
vSeedsOut.reserve(vSeedsIn.size());
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() - GetRand(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) == LOCAL_NONE) {
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() &&
!IsLimited(addrLocal.GetNetwork());
}
// Pushes our own address to a peer.
void AdvertiseLocal(CNode *pnode) {
if (fListen && pnode->fSuccessfullyConnected) {
CAddress addrLocal =
GetLocalAddress(&pnode->addr, pnode->GetLocalServices());
if (gArgs.GetBoolArg("-addrmantest", false)) {
// use IPv4 loopback during addrmantest
addrLocal =
CAddress(CService(LookupNumeric("127.0.0.1", GetListenPort())),
pnode->GetLocalServices());
}
// If discovery is enabled, sometimes give our peer the address it tells
// us that it sees us as in case it has a better idea of our address
// than we do.
if (IsPeerAddrLocalGood(pnode) &&
(!addrLocal.IsRoutable() ||
GetRand((GetnScore(addrLocal) > LOCAL_MANUAL) ? 8 : 2) == 0)) {
addrLocal.SetIP(pnode->GetAddrLocal());
}
if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false)) {
LogPrint(BCLog::NET, "AdvertiseLocal: advertising address %s\n",
addrLocal.ToString());
FastRandomContext insecure_rand;
pnode->PushAddress(addrLocal, insecure_rand);
}
}
}
// 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 (IsLimited(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);
}
/**
* Make a particular network entirely off-limits (no automatic connects to it).
*/
void SetLimited(enum Network net, bool fLimited) {
if (net == NET_UNROUTABLE || net == NET_INTERNAL) {
return;
}
LOCK(cs_mapLocalHost);
vfLimited[net] = fLimited;
}
bool IsLimited(enum Network net) {
LOCK(cs_mapLocalHost);
return vfLimited[net];
}
bool IsLimited(const CNetAddr &addr) {
return IsLimited(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;
}
/** check whether a given network is one we can probably connect to */
bool IsReachable(enum Network net) {
LOCK(cs_mapLocalHost);
return !vfLimited[net];
}
/** check whether a given address is in a network we can probably connect to */
bool IsReachable(const CNetAddr &addr) {
enum Network net = addr.GetNetwork();
return IsReachable(net);
}
CNode *CConnman::FindNode(const CNetAddr &ip) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (static_cast(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(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(pnode->addr) == addr) {
return pnode;
}
}
return nullptr;
}
bool CConnman::CheckIncomingNonce(uint64_t nonce) {
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (!pnode->fSuccessfullyConnected && !pnode->fInbound &&
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) {
if (pszDest == nullptr) {
if (IsLocal(addrConnect)) {
return nullptr;
}
// Look for an existing connection
CNode *pnode = FindNode(static_cast(addrConnect));
if (pnode) {
LogPrintf("Failed to open new connection, already connected\n");
return nullptr;
}
}
/// debug print
LogPrint(BCLog::NET, "trying connection %s lastseen=%.1fhrs\n",
pszDest ? pszDest : addrConnect.ToString(),
pszDest
? 0.0
: (double)(GetAdjustedTime() - addrConnect.nTime) / 3600.0);
// Resolve
const int default_port = Params().GetDefaultPort();
if (pszDest) {
std::vector 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(addrConnect));
if (pnode) {
pnode->MaybeSetAddrName(std::string(pszDest));
LogPrintf("Failed to open new connection, already connected\n");
return nullptr;
}
}
}
// Connect
bool connected = false;
SOCKET hSocket = INVALID_SOCKET;
proxyType proxy;
if (addrConnect.IsValid()) {
bool proxyConnectionFailed = false;
if (GetProxy(addrConnect.GetNetwork(), proxy)) {
hSocket = CreateSocket(proxy.proxy);
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
connected = ConnectThroughProxy(
proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), hSocket,
nConnectTimeout, &proxyConnectionFailed);
} else {
// no proxy needed (none set for target network)
hSocket = CreateSocket(addrConnect);
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
connected =
ConnectSocketDirectly(addrConnect, hSocket, nConnectTimeout);
}
if (!proxyConnectionFailed) {
// If a connection to the node was attempted, and failure (if any)
// is not caused by a problem connecting to the proxy, mark this as
// an attempt.
addrman.Attempt(addrConnect, fCountFailure);
}
} else if (pszDest && GetNameProxy(proxy)) {
hSocket = CreateSocket(proxy.proxy);
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
std::string host;
int port = default_port;
SplitHostPort(std::string(pszDest), port, host);
connected = ConnectThroughProxy(proxy, host, port, hSocket,
nConnectTimeout, nullptr);
}
if (!connected) {
CloseSocket(hSocket);
return nullptr;
}
// Add node
NodeId id = GetNewNodeId();
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE)
.Write(id)
.Finalize();
CAddress addr_bind = GetBindAddress(hSocket);
CNode *pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket,
addrConnect, CalculateKeyedNetGroup(addrConnect),
nonce, addr_bind, pszDest ? pszDest : "", false);
pnode->AddRef();
return pnode;
}
void CConnman::DumpBanlist() {
// Clean unused entries (if bantime has expired)
SweepBanned();
if (!BannedSetIsDirty()) {
return;
}
int64_t nStart = GetTimeMillis();
CBanDB bandb(config->GetChainParams());
banmap_t banmap;
GetBanned(banmap);
if (bandb.Write(banmap)) {
SetBannedSetDirty(false);
}
LogPrint(BCLog::NET,
"Flushed %d banned node ips/subnets to banlist.dat %dms\n",
banmap.size(), GetTimeMillis() - nStart);
}
void CNode::CloseSocketDisconnect() {
fDisconnect = true;
LOCK(cs_hSocket);
if (hSocket != INVALID_SOCKET) {
LogPrint(BCLog::NET, "disconnecting peer=%d\n", id);
CloseSocket(hSocket);
}
}
void CConnman::ClearBanned() {
{
LOCK(cs_setBanned);
setBanned.clear();
setBannedIsDirty = true;
}
// Store banlist to disk.
DumpBanlist();
if (clientInterface) {
clientInterface->BannedListChanged();
}
}
bool CConnman::IsBanned(CNetAddr ip) {
LOCK(cs_setBanned);
for (const auto &it : setBanned) {
CSubNet subNet = it.first;
CBanEntry banEntry = it.second;
if (subNet.Match(ip) && GetTime() < banEntry.nBanUntil) {
return true;
}
}
return false;
}
bool CConnman::IsBanned(CSubNet subnet) {
LOCK(cs_setBanned);
banmap_t::iterator i = setBanned.find(subnet);
if (i != setBanned.end()) {
CBanEntry banEntry = (*i).second;
if (GetTime() < banEntry.nBanUntil) {
return true;
}
}
return false;
}
void CConnman::Ban(const CNetAddr &addr, const BanReason &banReason,
int64_t bantimeoffset, bool sinceUnixEpoch) {
CSubNet subNet(addr);
Ban(subNet, banReason, bantimeoffset, sinceUnixEpoch);
}
void CConnman::Ban(const CSubNet &subNet, const BanReason &banReason,
int64_t bantimeoffset, bool sinceUnixEpoch) {
CBanEntry banEntry(GetTime());
banEntry.banReason = banReason;
if (bantimeoffset <= 0) {
bantimeoffset = gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME);
sinceUnixEpoch = false;
}
banEntry.nBanUntil = (sinceUnixEpoch ? 0 : GetTime()) + bantimeoffset;
{
LOCK(cs_setBanned);
if (setBanned[subNet].nBanUntil < banEntry.nBanUntil) {
setBanned[subNet] = banEntry;
setBannedIsDirty = true;
} else {
return;
}
}
if (clientInterface) {
clientInterface->BannedListChanged();
}
{
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (subNet.Match(static_cast(pnode->addr))) {
pnode->fDisconnect = true;
}
}
}
if (banReason == BanReasonManuallyAdded) {
// Store banlist to disk immediately if user requested ban.
DumpBanlist();
}
}
bool CConnman::Unban(const CNetAddr &addr) {
CSubNet subNet(addr);
return Unban(subNet);
}
bool CConnman::Unban(const CSubNet &subNet) {
{
LOCK(cs_setBanned);
if (!setBanned.erase(subNet)) {
return false;
}
setBannedIsDirty = true;
}
if (clientInterface) {
clientInterface->BannedListChanged();
}
// Store banlist to disk immediately.
DumpBanlist();
return true;
}
void CConnman::GetBanned(banmap_t &banMap) {
LOCK(cs_setBanned);
// Sweep the banlist so expired bans are not returned
SweepBanned();
// Create a thread safe copy.
banMap = setBanned;
}
void CConnman::SetBanned(const banmap_t &banMap) {
LOCK(cs_setBanned);
setBanned = banMap;
setBannedIsDirty = true;
}
void CConnman::SweepBanned() {
int64_t now = GetTime();
bool notifyUI = false;
{
LOCK(cs_setBanned);
banmap_t::iterator it = setBanned.begin();
while (it != setBanned.end()) {
CSubNet subNet = (*it).first;
CBanEntry banEntry = (*it).second;
if (now > banEntry.nBanUntil) {
setBanned.erase(it++);
setBannedIsDirty = true;
notifyUI = true;
LogPrint(
BCLog::NET,
"%s: Removed banned node ip/subnet from banlist.dat: %s\n",
__func__, subNet.ToString());
} else {
++it;
}
}
}
// update UI
if (notifyUI && clientInterface) {
clientInterface->BannedListChanged();
}
}
bool CConnman::BannedSetIsDirty() {
LOCK(cs_setBanned);
return setBannedIsDirty;
}
void CConnman::SetBannedSetDirty(bool dirty) {
// Reuse setBanned lock for the isDirty flag.
LOCK(cs_setBanned);
setBannedIsDirty = dirty;
}
bool CConnman::IsWhitelistedRange(const CNetAddr &addr) {
for (const CSubNet &subnet : vWhitelistedRange) {
if (subnet.Match(addr)) {
return true;
}
}
return false;
}
std::string CNode::GetAddrName() const {
LOCK(cs_addrName);
return addrName;
}
void CNode::MaybeSetAddrName(const std::string &addrNameIn) {
LOCK(cs_addrName);
if (addrName.empty()) {
addrName = addrNameIn;
}
}
CService CNode::GetAddrLocal() const {
LOCK(cs_addrLocal);
return addrLocal;
}
void CNode::SetAddrLocal(const CService &addrLocalIn) {
LOCK(cs_addrLocal);
if (addrLocal.IsValid()) {
error("Addr local already set for node: %i. Refusing to change from %s "
"to %s",
id, addrLocal.ToString(), addrLocalIn.ToString());
} else {
addrLocal = addrLocalIn;
}
}
void CNode::copyStats(CNodeStats &stats) {
stats.nodeid = this->GetId();
stats.nServices = nServices;
stats.addr = addr;
stats.addrBind = addrBind;
{
LOCK(cs_filter);
stats.fRelayTxes = fRelayTxes;
}
stats.nLastSend = nLastSend;
stats.nLastRecv = nLastRecv;
stats.nTimeConnected = nTimeConnected;
stats.nTimeOffset = nTimeOffset;
stats.addrName = GetAddrName();
stats.nVersion = nVersion;
{
LOCK(cs_SubVer);
stats.cleanSubVer = cleanSubVer;
}
stats.fInbound = fInbound;
stats.m_manual_connection = m_manual_connection;
stats.nStartingHeight = nStartingHeight;
{
LOCK(cs_vSend);
stats.mapSendBytesPerMsgCmd = mapSendBytesPerMsgCmd;
stats.nSendBytes = nSendBytes;
}
{
LOCK(cs_vRecv);
stats.mapRecvBytesPerMsgCmd = mapRecvBytesPerMsgCmd;
stats.nRecvBytes = nRecvBytes;
}
stats.fWhitelisted = fWhitelisted;
// It is common for nodes with good ping times to suddenly become lagged,
// due to a new block arriving or other large transfer. Merely reporting
// pingtime might fool the caller into thinking the node was still
// responsive, since pingtime does not update until the ping is complete,
// which might take a while. So, if a ping is taking an unusually long time
// in flight, the caller can immediately detect that this is happening.
int64_t nPingUsecWait = 0;
if ((0 != nPingNonceSent) && (0 != nPingUsecStart)) {
nPingUsecWait = GetTimeMicros() - nPingUsecStart;
}
// Raw ping time is in microseconds, but show it to user as whole seconds
// (Bitcoin users should be well used to small numbers with many decimal
// places by now :)
stats.dPingTime = ((double(nPingUsecTime)) / 1e6);
stats.dMinPing = ((double(nMinPingUsecTime)) / 1e6);
stats.dPingWait = ((double(nPingUsecWait)) / 1e6);
// Leave string empty if addrLocal invalid (not filled in yet)
CService addrLocalUnlocked = GetAddrLocal();
stats.addrLocal =
addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : "";
}
static bool IsOversizedMessage(const Config &config, const CNetMessage &msg) {
if (!msg.in_data) {
// Header only, cannot be oversized.
return false;
}
return msg.hdr.IsOversized(config);
}
bool CNode::ReceiveMsgBytes(const Config &config, const char *pch,
uint32_t nBytes, bool &complete) {
complete = false;
int64_t nTimeMicros = GetTimeMicros();
LOCK(cs_vRecv);
nLastRecv = nTimeMicros / 1000000;
nRecvBytes += nBytes;
while (nBytes > 0) {
// Get current incomplete message, or create a new one.
if (vRecvMsg.empty() || vRecvMsg.back().complete()) {
vRecvMsg.push_back(CNetMessage(config.GetChainParams().NetMagic(),
SER_NETWORK, INIT_PROTO_VERSION));
}
CNetMessage &msg = vRecvMsg.back();
// Absorb network data.
int handled;
if (!msg.in_data) {
handled = msg.readHeader(config, pch, nBytes);
} else {
handled = msg.readData(pch, nBytes);
}
if (handled < 0) {
return false;
}
if (IsOversizedMessage(config, msg)) {
LogPrint(BCLog::NET,
"Oversized message from peer=%i, disconnecting\n",
GetId());
return false;
}
pch += handled;
nBytes -= handled;
if (msg.complete()) {
// Store received bytes per message command to prevent a memory DOS,
// only allow valid commands.
mapMsgCmdSize::iterator i =
mapRecvBytesPerMsgCmd.find(msg.hdr.pchCommand.data());
if (i == mapRecvBytesPerMsgCmd.end()) {
i = mapRecvBytesPerMsgCmd.find(NET_MESSAGE_COMMAND_OTHER);
}
assert(i != mapRecvBytesPerMsgCmd.end());
i->second += msg.hdr.nMessageSize + CMessageHeader::HEADER_SIZE;
msg.nTime = nTimeMicros;
complete = true;
}
}
return true;
}
void CNode::SetSendVersion(int nVersionIn) {
// Send version may only be changed in the version message, and only one
// version message is allowed per session. We can therefore treat this value
// as const and even atomic as long as it's only used once a version message
// has been successfully processed. Any attempt to set this twice is an
// error.
if (nSendVersion != 0) {
error("Send version already set for node: %i. Refusing to change from "
"%i to %i",
id, nSendVersion, nVersionIn);
} else {
nSendVersion = nVersionIn;
}
}
int CNode::GetSendVersion() const {
// The send version should always be explicitly set to INIT_PROTO_VERSION
// rather than using this value until SetSendVersion has been called.
if (nSendVersion == 0) {
error("Requesting unset send version for node: %i. Using %i", id,
INIT_PROTO_VERSION);
return INIT_PROTO_VERSION;
}
return nSendVersion;
}
int CNetMessage::readHeader(const Config &config, const char *pch,
uint32_t nBytes) {
// copy data to temporary parsing buffer
uint32_t nRemaining = 24 - nHdrPos;
uint32_t nCopy = std::min(nRemaining, nBytes);
memcpy(&hdrbuf[nHdrPos], pch, nCopy);
nHdrPos += nCopy;
// if header incomplete, exit
if (nHdrPos < 24) {
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 CNetMessage::readData(const char *pch, uint32_t nBytes) {
unsigned int nRemaining = hdr.nMessageSize - nDataPos;
unsigned int nCopy = std::min(nRemaining, nBytes);
if (vRecv.size() < nDataPos + nCopy) {
// Allocate up to 256 KiB ahead, but never more than the total message
// size.
vRecv.resize(std::min(hdr.nMessageSize, nDataPos + nCopy + 256 * 1024));
}
hasher.Write((const uint8_t *)pch, nCopy);
memcpy(&vRecv[nDataPos], pch, nCopy);
nDataPos += nCopy;
return nCopy;
}
const uint256 &CNetMessage::GetMessageHash() const {
assert(complete());
if (data_hash.IsNull()) {
hasher.Finalize(data_hash.begin());
}
return data_hash;
}
// requires LOCK(cs_vSend)
size_t CConnman::SocketSendData(CNode *pnode) const {
AssertLockHeld(pnode->cs_vSend);
size_t nSentSize = 0;
size_t nMsgCount = 0;
for (const auto &data : pnode->vSendMsg) {
assert(data.size() > pnode->nSendOffset);
int nBytes = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
break;
}
nBytes = send(pnode->hSocket,
reinterpret_cast(data.data()) +
pnode->nSendOffset,
data.size() - pnode->nSendOffset,
MSG_NOSIGNAL | MSG_DONTWAIT);
}
if (nBytes == 0) {
// couldn't send anything at all
break;
}
if (nBytes < 0) {
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE &&
nErr != WSAEINTR && nErr != WSAEINPROGRESS) {
LogPrintf("socket send error %s\n", NetworkErrorString(nErr));
pnode->CloseSocketDisconnect();
}
break;
}
assert(nBytes > 0);
pnode->nLastSend = GetSystemTimeInSeconds();
pnode->nSendBytes += nBytes;
pnode->nSendOffset += nBytes;
nSentSize += nBytes;
if (pnode->nSendOffset != data.size()) {
// could not send full message; stop sending more
break;
}
pnode->nSendOffset = 0;
pnode->nSendSize -= data.size();
pnode->fPauseSend = pnode->nSendSize > nSendBufferMaxSize;
nMsgCount++;
}
pnode->vSendMsg.erase(pnode->vSendMsg.begin(),
pnode->vSendMsg.begin() + nMsgCount);
if (pnode->vSendMsg.empty()) {
assert(pnode->nSendOffset == 0);
assert(pnode->nSendSize == 0);
}
return nSentSize;
}
struct NodeEvictionCandidate {
NodeId id;
int64_t nTimeConnected;
int64_t nMinPingUsecTime;
int64_t nLastBlockTime;
int64_t nLastTXTime;
bool fRelevantServices;
bool fRelayTxes;
bool fBloomFilter;
CAddress addr;
uint64_t nKeyedNetGroup;
};
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
return a.nMinPingUsecTime > b.nMinPingUsecTime;
}
static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
return a.nTimeConnected > b.nTimeConnected;
}
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
return a.nKeyedNetGroup < b.nKeyedNetGroup;
}
static bool CompareNodeBlockTime(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
// There is a fall-through here because it is common for a node to have many
// peers which have not yet relayed a block.
if (a.nLastBlockTime != b.nLastBlockTime) {
return a.nLastBlockTime < b.nLastBlockTime;
}
if (a.fRelevantServices != b.fRelevantServices) {
return b.fRelevantServices;
}
return a.nTimeConnected > b.nTimeConnected;
}
static bool CompareNodeTXTime(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
// There is a fall-through here because it is common for a node to have more
// than a few peers that have not yet relayed txn.
if (a.nLastTXTime != b.nLastTXTime) {
return a.nLastTXTime < b.nLastTXTime;
}
if (a.fRelayTxes != b.fRelayTxes) {
return b.fRelayTxes;
}
if (a.fBloomFilter != b.fBloomFilter) {
return a.fBloomFilter;
}
return a.nTimeConnected > b.nTimeConnected;
}
//! Sort an array by the specified comparator, then erase the last K elements.
template
static void EraseLastKElements(std::vector &elements, Comparator comparator,
size_t k) {
std::sort(elements.begin(), elements.end(), comparator);
size_t eraseSize = std::min(k, elements.size());
elements.erase(elements.end() - eraseSize, elements.end());
}
/**
* 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 vEvictionCandidates;
{
LOCK(cs_vNodes);
for (CNode *node : vNodes) {
if (node->fWhitelisted || !node->fInbound || node->fDisconnect) {
continue;
}
NodeEvictionCandidate candidate = {
node->GetId(),
node->nTimeConnected,
node->nMinPingUsecTime,
node->nLastBlockTime,
node->nLastTXTime,
HasAllDesirableServiceFlags(node->nServices),
node->fRelayTxes,
node->pfilter != nullptr,
node->addr,
node->nKeyedNetGroup};
vEvictionCandidates.push_back(candidate);
}
}
// 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 transactions.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
// Protect 4 nodes that most recently sent us blocks.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
// 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.
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected,
vEvictionCandidates.size() / 2);
if (vEvictionCandidates.empty()) {
return false;
}
// Identify the network group with the most connections and youngest member.
// (vEvictionCandidates is already sorted by reverse connect time)
uint64_t naMostConnections;
unsigned int nMostConnections = 0;
int64_t nMostConnectionsTime = 0;
std::map> mapNetGroupNodes;
for (const NodeEvictionCandidate &node : vEvictionCandidates) {
std::vector &group =
mapNetGroupNodes[node.nKeyedNetGroup];
group.push_back(node);
int64_t grouptime = group[0].nTimeConnected;
size_t group_size = group.size();
if (group_size > nMostConnections ||
(group_size == nMostConnections &&
grouptime > nMostConnectionsTime)) {
nMostConnections = group_size;
nMostConnectionsTime = grouptime;
naMostConnections = node.nKeyedNetGroup;
}
}
// Reduce to the network group with the most connections
vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]);
// Disconnect from the network group with the most connections
NodeId evicted = vEvictionCandidates.front().id;
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (pnode->GetId() == evicted) {
pnode->fDisconnect = true;
return true;
}
}
return false;
}
void CConnman::AcceptConnection(const ListenSocket &hListenSocket) {
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
SOCKET hSocket =
accept(hListenSocket.socket, (struct sockaddr *)&sockaddr, &len);
CAddress addr;
int nInbound = 0;
int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler);
if (hSocket != INVALID_SOCKET) {
if (!addr.SetSockAddr((const struct sockaddr *)&sockaddr)) {
LogPrintf("Warning: Unknown socket family\n");
}
}
bool whitelisted = hListenSocket.whitelisted || IsWhitelistedRange(addr);
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (pnode->fInbound) {
nInbound++;
}
}
}
if (hSocket == INVALID_SOCKET) {
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK) {
LogPrintf("socket error accept failed: %s\n",
NetworkErrorString(nErr));
}
return;
}
if (!fNetworkActive) {
LogPrintf("connection from %s dropped: not accepting new connections\n",
addr.ToString());
CloseSocket(hSocket);
return;
}
if (!IsSelectableSocket(hSocket)) {
LogPrintf("connection from %s dropped: non-selectable socket\n",
addr.ToString());
CloseSocket(hSocket);
return;
}
// According to the internet TCP_NODELAY is not carried into accepted
// sockets on all platforms. Set it again here just to be sure.
SetSocketNoDelay(hSocket);
if (IsBanned(addr) && !whitelisted) {
LogPrint(BCLog::NET, "connection from %s dropped (banned)\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();
CAddress addr_bind = GetBindAddress(hSocket);
CNode *pnode =
new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr,
CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true);
pnode->AddRef();
pnode->fWhitelisted = whitelisted;
m_msgproc->InitializeNode(*config, pnode);
LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString());
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
}
void CConnman::ThreadSocketHandler() {
unsigned int nPrevNodeCount = 0;
while (!interruptNet) {
//
// Disconnect nodes
//
{
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 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 vNodesDisconnectedCopy = vNodesDisconnected;
for (CNode *pnode : vNodesDisconnectedCopy) {
// wait until threads are done using it
if (pnode->GetRefCount() <= 0) {
bool fDelete = false;
{
TRY_LOCK(pnode->cs_inventory, lockInv);
if (lockInv) {
TRY_LOCK(pnode->cs_vSend, lockSend);
if (lockSend) {
fDelete = true;
}
}
}
if (fDelete) {
vNodesDisconnected.remove(pnode);
DeleteNode(pnode);
}
}
}
}
size_t vNodesSize;
{
LOCK(cs_vNodes);
vNodesSize = vNodes.size();
}
if (vNodesSize != nPrevNodeCount) {
nPrevNodeCount = vNodesSize;
if (clientInterface) {
clientInterface->NotifyNumConnectionsChanged(nPrevNodeCount);
}
}
//
// Find which sockets have data to receive
//
struct timeval timeout;
timeout.tv_sec = 0;
// Frequency to poll pnode->vSend
timeout.tv_usec = 50000;
fd_set fdsetRecv;
fd_set fdsetSend;
fd_set fdsetError;
FD_ZERO(&fdsetRecv);
FD_ZERO(&fdsetSend);
FD_ZERO(&fdsetError);
SOCKET hSocketMax = 0;
bool have_fds = false;
for (const ListenSocket &hListenSocket : vhListenSocket) {
FD_SET(hListenSocket.socket, &fdsetRecv);
hSocketMax = std::max(hSocketMax, hListenSocket.socket);
have_fds = true;
}
{
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;
}
FD_SET(pnode->hSocket, &fdsetError);
hSocketMax = std::max(hSocketMax, pnode->hSocket);
have_fds = true;
if (select_send) {
FD_SET(pnode->hSocket, &fdsetSend);
continue;
}
if (select_recv) {
FD_SET(pnode->hSocket, &fdsetRecv);
}
}
}
int nSelect = select(have_fds ? hSocketMax + 1 : 0, &fdsetRecv,
&fdsetSend, &fdsetError, &timeout);
if (interruptNet) {
return;
}
if (nSelect == SOCKET_ERROR) {
if (have_fds) {
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(timeout.tv_usec / 1000))) {
return;
}
}
//
// Accept new connections
//
for (const ListenSocket &hListenSocket : vhListenSocket) {
if (hListenSocket.socket != INVALID_SOCKET &&
FD_ISSET(hListenSocket.socket, &fdsetRecv)) {
AcceptConnection(hListenSocket);
}
}
//
// Service each socket
//
std::vector 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 = FD_ISSET(pnode->hSocket, &fdsetRecv);
sendSet = FD_ISSET(pnode->hSocket, &fdsetSend);
errorSet = FD_ISSET(pnode->hSocket, &fdsetError);
}
if (recvSet || errorSet) {
// typical socket buffer is 8K-64K
char pchBuf[0x10000];
int32_t nBytes = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
continue;
}
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf),
MSG_DONTWAIT);
}
if (nBytes > 0) {
bool notify = false;
if (!pnode->ReceiveMsgBytes(*config, pchBuf, nBytes,
notify)) {
pnode->CloseSocketDisconnect();
}
RecordBytesRecv(nBytes);
if (notify) {
size_t nSizeAdded = 0;
auto it(pnode->vRecvMsg.begin());
for (; it != pnode->vRecvMsg.end(); ++it) {
if (!it->complete()) {
break;
}
nSizeAdded +=
it->vRecv.size() + CMessageHeader::HEADER_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\n");
}
pnode->CloseSocketDisconnect();
} else if (nBytes < 0) {
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE &&
nErr != WSAEINTR && nErr != WSAEINPROGRESS) {
if (!pnode->fDisconnect) {
LogPrintf("socket recv error %s\n",
NetworkErrorString(nErr));
}
pnode->CloseSocketDisconnect();
}
}
}
//
// Send
//
if (sendSet) {
LOCK(pnode->cs_vSend);
size_t nBytes = SocketSendData(pnode);
if (nBytes) {
RecordBytesSent(nBytes);
}
}
//
// Inactivity checking
//
int64_t nTime = GetSystemTimeInSeconds();
if (nTime - pnode->nTimeConnected > 60) {
if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) {
LogPrint(BCLog::NET,
"socket no message in first 60 seconds, %d %d "
"from %d\n",
pnode->nLastRecv != 0, pnode->nLastSend != 0,
pnode->GetId());
pnode->fDisconnect = true;
} else if (nTime - pnode->nLastSend > TIMEOUT_INTERVAL) {
LogPrintf("socket sending timeout: %is\n",
nTime - pnode->nLastSend);
pnode->fDisconnect = true;
} else if (nTime - pnode->nLastRecv >
(pnode->nVersion > BIP0031_VERSION ? TIMEOUT_INTERVAL
: 90 * 60)) {
LogPrintf("socket receive timeout: %is\n",
nTime - pnode->nLastRecv);
pnode->fDisconnect = true;
} else if (pnode->nPingNonceSent &&
pnode->nPingUsecStart + TIMEOUT_INTERVAL * 1000000 <
GetTimeMicros()) {
LogPrintf("ping timeout: %fs\n",
0.000001 *
(GetTimeMicros() - pnode->nPingUsecStart));
pnode->fDisconnect = true;
} else if (!pnode->fSuccessfullyConnected) {
LogPrint(BCLog::NET, "version handshake timeout from %d\n",
pnode->GetId());
pnode->fDisconnect = true;
}
}
}
{
LOCK(cs_vNodes);
for (CNode *pnode : vNodesCopy) {
pnode->Release();
}
}
}
}
void CConnman::WakeMessageHandler() {
{
std::lock_guard lock(mutexMsgProc);
fMsgProcWake = true;
}
condMsgProc.notify_one();
}
#ifdef USE_UPNP
static CThreadInterrupt g_upnp_interrupt;
static std::thread g_upnp_thread;
static void ThreadMapPort() {
std::string port = strprintf("%u", GetListenPort());
const char *multicastif = nullptr;
const char *minissdpdpath = nullptr;
struct UPNPDev *devlist = nullptr;
char lanaddr[64];
#ifndef UPNPDISCOVER_SUCCESS
/* miniupnpc 1.5 */
devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0);
#elif MINIUPNPC_API_VERSION < 14
/* miniupnpc 1.6 */
int error = 0;
devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error);
#else
/* miniupnpc 1.9.20150730 */
int error = 0;
devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error);
#endif
struct UPNPUrls urls;
struct IGDdatas data;
int r;
r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
if (r == 1) {
if (fDiscover) {
char externalIPAddress[40];
r = UPNP_GetExternalIPAddress(
urls.controlURL, data.first.servicetype, externalIPAddress);
if (r != UPNPCOMMAND_SUCCESS) {
LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r);
} else {
if (externalIPAddress[0]) {
CNetAddr resolved;
if (LookupHost(externalIPAddress, resolved, false)) {
LogPrintf("UPnP: ExternalIPAddress = %s\n",
resolved.ToString().c_str());
AddLocal(resolved, LOCAL_UPNP);
}
} else {
LogPrintf("UPnP: GetExternalIPAddress failed.\n");
}
}
}
std::string strDesc = "Bitcoin " + FormatFullVersion();
do {
#ifndef UPNPDISCOVER_SUCCESS
/* miniupnpc 1.5 */
r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
port.c_str(), port.c_str(), lanaddr,
strDesc.c_str(), "TCP", 0);
#else
/* miniupnpc 1.6 */
r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
port.c_str(), port.c_str(), lanaddr,
strDesc.c_str(), "TCP", 0, "0");
#endif
if (r != UPNPCOMMAND_SUCCESS) {
LogPrintf(
"AddPortMapping(%s, %s, %s) failed with code %d (%s)\n",
port, port, lanaddr, r, strupnperror(r));
} else {
LogPrintf("UPnP Port Mapping successful.\n");
}
} while (g_upnp_interrupt.sleep_for(std::chrono::minutes(20)));
r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype,
port.c_str(), "TCP", 0);
LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r);
freeUPNPDevlist(devlist);
devlist = nullptr;
FreeUPNPUrls(&urls);
} else {
LogPrintf("No valid UPnP IGDs found\n");
freeUPNPDevlist(devlist);
devlist = nullptr;
if (r != 0) {
FreeUPNPUrls(&urls);
}
}
}
void StartMapPort() {
if (!g_upnp_thread.joinable()) {
assert(!g_upnp_interrupt);
g_upnp_thread = std::thread(
(std::bind(&TraceThread, "upnp", &ThreadMapPort)));
}
}
void InterruptMapPort() {
if (g_upnp_thread.joinable()) {
g_upnp_interrupt();
}
}
void StopMapPort() {
if (g_upnp_thread.joinable()) {
g_upnp_thread.join();
g_upnp_interrupt.reset();
}
}
#else
void StartMapPort() {
// Intentionally left blank.
}
void InterruptMapPort() {
// Intentionally left blank.
}
void StopMapPort() {
// Intentionally left blank.
}
#endif
void CConnman::ThreadDNSAddressSeed() {
// goal: only query DNS seeds if address need is acute.
// Avoiding DNS seeds when we don't need them 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.
if ((addrman.size() > 0) &&
(!gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED))) {
if (!interruptNet.sleep_for(std::chrono::seconds(11))) {
return;
}
LOCK(cs_vNodes);
int nRelevant = 0;
for (const CNode *pnode : vNodes) {
nRelevant += pnode->fSuccessfullyConnected && !pnode->fFeeler &&
!pnode->fOneShot && !pnode->m_manual_connection &&
!pnode->fInbound;
}
if (nRelevant >= 2) {
LogPrintf("P2P peers available. Skipped DNS seeding.\n");
return;
}
}
const std::vector &vSeeds =
config->GetChainParams().DNSSeeds();
int found = 0;
LogPrintf("Loading addresses from DNS seeds (could take a while)\n");
for (const std::string &seed : vSeeds) {
if (interruptNet) {
return;
}
if (HaveNameProxy()) {
AddOneShot(seed);
} else {
std::vector vIPs;
std::vector 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.c_str(), 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 - GetRand(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
// oneshot to get nodes with our desired service bits.
AddOneShot(seed);
}
}
}
LogPrintf("%d addresses found from DNS seeds\n", found);
}
void CConnman::DumpAddresses() {
int64_t nStart = GetTimeMillis();
CAddrDB adb(config->GetChainParams());
adb.Write(addrman);
LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n",
addrman.size(), GetTimeMillis() - nStart);
}
void CConnman::DumpData() {
DumpAddresses();
DumpBanlist();
}
void CConnman::ProcessOneShot() {
std::string strDest;
{
LOCK(cs_vOneShots);
if (vOneShots.empty()) {
return;
}
strDest = vOneShots.front();
vOneShots.pop_front();
}
CAddress addr;
CSemaphoreGrant grant(*semOutbound, true);
if (grant) {
OpenNetworkConnection(addr, false, &grant, strDest.c_str(), true);
}
}
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 one-shots and feelers).
// Also exclude peers that haven't finished initial connection handshake yet (so
// that we don't decide we're over our desired connection limit, and then evict
// some peer that has finished the handshake).
int CConnman::GetExtraOutboundCount() {
int nOutbound = 0;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (!pnode->fInbound && !pnode->m_manual_connection &&
!pnode->fFeeler && !pnode->fDisconnect && !pnode->fOneShot &&
pnode->fSuccessfullyConnected) {
++nOutbound;
}
}
}
return std::max(nOutbound - nMaxOutbound, 0);
}
void CConnman::ThreadOpenConnections(const std::vector connect) {
// Connect to specific addresses
if (!connect.empty()) {
for (int64_t nLoop = 0;; nLoop++) {
ProcessOneShot();
for (const std::string &strAddr : connect) {
CAddress addr(CService(), NODE_NONE);
OpenNetworkConnection(addr, false, nullptr, strAddr.c_str(),
false, false, true);
for (int i = 0; i < 10 && i < nLoop; i++) {
if (!interruptNet.sleep_for(
std::chrono::milliseconds(500))) {
return;
}
}
}
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) {
return;
}
}
}
// Initiate network connections
int64_t nStart = GetTime();
// Minimum time before next feeler connection (in microseconds).
int64_t nNextFeeler =
PoissonNextSend(nStart * 1000 * 1000, FEELER_INTERVAL);
while (!interruptNet) {
ProcessOneShot();
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) {
return;
}
CSemaphoreGrant grant(*semOutbound);
if (interruptNet) {
return;
}
// Add seed nodes if DNS seeds are all down (an infrastructure attack?).
if (addrman.size() == 0 && (GetTime() - nStart > 60)) {
static bool done = false;
if (!done) {
LogPrintf("Adding fixed seed nodes as DNS doesn't seem to be "
"available.\n");
CNetAddr local;
local.SetInternal("fixedseeds");
addrman.Add(convertSeed6(config->GetChainParams().FixedSeeds()),
local);
done = true;
}
}
//
// Choose an address to connect to based on most recently seen
//
CAddress addrConnect;
// Only connect out to one peer per network group (/16 for IPv4). Do
// this here so we don't have to critsect vNodes inside mapAddresses
// critsect.
int nOutbound = 0;
std::set> setConnected;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (!pnode->fInbound && !pnode->m_manual_connection) {
// Netgroups for inbound and addnode 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 addnode peers do not use our outbound slots. Inbound
// peers also have the added issue that they're attacker
// controlled and could be used to prevent us from
// connecting to particular hosts if we used them here.
setConnected.insert(pnode->addr.GetGroup());
nOutbound++;
}
}
}
// Feeler Connections
//
// Design goals:
// * Increase the number of connectable addresses in the tried table.
//
// Method:
// * Choose a random address from new and attempt to connect to it if
// we can connect successfully it is added to tried.
// * Start attempting feeler connections only after node finishes
// making outbound connections.
// * Only make a feeler connection once every few minutes.
//
bool fFeeler = false;
if (nOutbound >= nMaxOutbound && !GetTryNewOutboundPeer()) {
// The current time right now (in microseconds).
int64_t nTime = GetTimeMicros();
if (nTime > nNextFeeler) {
nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
fFeeler = true;
} else {
continue;
}
}
addrman.ResolveCollisions();
int64_t nANow = GetAdjustedTime();
int nTries = 0;
while (!interruptNet) {
CAddrInfo addr = addrman.SelectTriedCollision();
// SelectTriedCollision returns an invalid address if it is empty.
if (!fFeeler || !addr.IsValid()) {
addr = addrman.Select(fFeeler);
}
// if we selected an invalid address, restart
if (!addr.IsValid() || setConnected.count(addr.GetGroup()) ||
IsLocal(addr)) {
break;
}
// If we didn't find an appropriate destination after trying 100
// addresses fetched from addrman, stop this loop, and let the outer
// loop run again (which sleeps, adds seed nodes, recalculates
// already-connected network ranges, ...) before trying new addrman
// addresses.
nTries++;
if (nTries > 100) {
break;
}
if (IsLimited(addr)) {
continue;
}
// only consider very recently tried nodes after 30 failed attempts
if (nANow - addr.nLastTry < 600 && nTries < 30) {
continue;
}
// for non-feelers, require all the services we'll want,
// for feelers, only require they be a full node (only because most
// SPV clients don't have a good address DB available)
if (!fFeeler && !HasAllDesirableServiceFlags(addr.nServices)) {
continue;
}
if (fFeeler && !MayHaveUsefulAddressDB(addr.nServices)) {
continue;
}
// do not allow non-default ports, unless after 50 invalid addresses
// selected already.
if (addr.GetPort() != config->GetChainParams().GetDefaultPort() &&
nTries < 50) {
continue;
}
addrConnect = addr;
break;
}
if (addrConnect.IsValid()) {
if (fFeeler) {
// Add small amount of random noise before connection to avoid
// synchronization.
int randsleep = GetRandInt(FEELER_SLEEP_WINDOW * 1000);
if (!interruptNet.sleep_for(
std::chrono::milliseconds(randsleep))) {
return;
}
LogPrint(BCLog::NET, "Making feeler connection to %s\n",
addrConnect.ToString());
}
OpenNetworkConnection(addrConnect,
(int)setConnected.size() >=
std::min(nMaxConnections - 1, 2),
&grant, nullptr, false, fFeeler);
}
}
}
std::vector CConnman::GetAddedNodeInfo() {
std::vector ret;
std::list 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 mapConnected;
std::map> mapConnectedByName;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (pnode->addr.IsValid()) {
mapConnected[pnode->addr] = pnode->fInbound;
}
std::string addrName = pnode->GetAddrName();
if (!addrName.empty()) {
mapConnectedByName[std::move(addrName)] =
std::make_pair(pnode->fInbound,
static_cast(pnode->addr));
}
}
}
for (const std::string &strAddNode : lAddresses) {
CService service(
LookupNumeric(strAddNode.c_str(), Params().GetDefaultPort()));
AddedNodeInfo addedNode{strAddNode, CService(), false, false};
if (service.IsValid()) {
// strAddNode is an IP:port
auto it = mapConnected.find(service);
if (it != mapConnected.end()) {
addedNode.resolvedAddress = service;
addedNode.fConnected = true;
addedNode.fInbound = it->second;
}
} else {
// strAddNode is a name
auto it = mapConnectedByName.find(strAddNode);
if (it != mapConnectedByName.end()) {
addedNode.resolvedAddress = it->second.second;
addedNode.fConnected = true;
addedNode.fInbound = it->second.first;
}
}
ret.emplace_back(std::move(addedNode));
}
return ret;
}
void CConnman::ThreadOpenAddedConnections() {
while (true) {
CSemaphoreGrant grant(*semAddnode);
std::vector 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, lets
// 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(), false, false,
true);
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, bool fOneShot,
bool fFeeler, bool manual_connection) {
//
// Initiate outbound network connection
//
if (interruptNet) {
return;
}
if (!fNetworkActive) {
return;
}
if (!pszDest) {
if (IsLocal(addrConnect) || FindNode((CNetAddr)addrConnect) ||
IsBanned(addrConnect) || FindNode(addrConnect.ToStringIPPort())) {
return;
}
} else if (FindNode(std::string(pszDest))) {
return;
}
CNode *pnode = ConnectNode(addrConnect, pszDest, fCountFailure);
if (!pnode) {
return;
}
if (grantOutbound) {
grantOutbound->MoveTo(pnode->grantOutbound);
}
if (fOneShot) {
pnode->fOneShot = true;
}
if (fFeeler) {
pnode->fFeeler = true;
}
if (manual_connection) {
pnode->m_manual_connection = true;
}
m_msgproc->InitializeNode(*config, pnode);
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
}
void CConnman::ThreadMessageHandler() {
while (!flagInterruptMsgProc) {
std::vector vNodesCopy;
{
LOCK(cs_vNodes);
vNodesCopy = vNodes;
for (CNode *pnode : vNodesCopy) {
pnode->AddRef();
}
}
bool fMoreWork = false;
for (CNode *pnode : vNodesCopy) {
if (pnode->fDisconnect) {
continue;
}
// Receive messages
bool fMoreNodeWork = m_msgproc->ProcessMessages(
*config, pnode, flagInterruptMsgProc);
fMoreWork |= (fMoreNodeWork && !pnode->fPauseSend);
if (flagInterruptMsgProc) {
return;
}
// Send messages
{
LOCK(pnode->cs_sendProcessing);
m_msgproc->SendMessages(*config, pnode, flagInterruptMsgProc);
}
if (flagInterruptMsgProc) {
return;
}
}
{
LOCK(cs_vNodes);
for (CNode *pnode : vNodesCopy) {
pnode->Release();
}
}
WAIT_LOCK(mutexMsgProc, lock);
if (!fMoreWork) {
condMsgProc.wait_until(lock,
std::chrono::steady_clock::now() +
std::chrono::milliseconds(100),
[this] { return fMsgProcWake; });
}
fMsgProcWake = false;
}
}
bool CConnman::BindListenPort(const CService &addrBind, std::string &strError,
bool fWhitelisted) {
strError = "";
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("Error: Bind address family for %s not supported",
addrBind.ToString());
LogPrintf("%s\n", strError);
return false;
}
SOCKET hListenSocket = CreateSocket(addrBind);
if (hListenSocket == INVALID_SOCKET) {
strError = strprintf("Error: Couldn't open socket for incoming "
"connections (socket returned error %s)",
NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError);
return false;
}
// Allow binding if the port is still in TIME_WAIT state after
// the program was closed and restarted.
setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne,
sizeof(int));
// Some systems don't have IPV6_V6ONLY but are always v6only; others do have
// the option and enable it by default or not. Try to enable it, if
// possible.
if (addrBind.IsIPv6()) {
#ifdef IPV6_V6ONLY
setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY,
(sockopt_arg_type)&nOne, sizeof(int));
#endif
#ifdef WIN32
int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED;
setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL,
(sockopt_arg_type)&nProtLevel, sizeof(int));
#endif
}
if (::bind(hListenSocket, (struct sockaddr *)&sockaddr, len) ==
SOCKET_ERROR) {
int nErr = WSAGetLastError();
if (nErr == WSAEADDRINUSE) {
strError = strprintf(_("Unable to bind to %s on this computer. %s "
"is probably already running."),
addrBind.ToString(), _(PACKAGE_NAME));
} else {
strError = strprintf(_("Unable to bind to %s on this computer "
"(bind returned error %s)"),
addrBind.ToString(), NetworkErrorString(nErr));
}
LogPrintf("%s\n", strError);
CloseSocket(hListenSocket);
return false;
}
LogPrintf("Bound to %s\n", addrBind.ToString());
// Listen for incoming connections
if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) {
strError = strprintf(_("Error: Listening for incoming connections "
"failed (listen returned error %s)"),
NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError);
CloseSocket(hListenSocket);
return false;
}
vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted));
if (addrBind.IsRoutable() && fDiscover && !fWhitelisted) {
AddLocal(addrBind, LOCAL_BIND);
}
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 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());
}
}
}
}
#else
// 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(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(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) {
LogPrint(BCLog::NET, "SetNetworkActive: %s\n", active);
if (fNetworkActive == active) {
return;
}
fNetworkActive = active;
uiInterface.NotifyNetworkActiveChanged(fNetworkActive);
}
CConnman::CConnman(const Config &configIn, uint64_t nSeed0In, uint64_t nSeed1In)
: config(&configIn), nSeed0(nSeed0In), nSeed1(nSeed1In) {
fNetworkActive = true;
setBannedIsDirty = false;
fAddressesInitialized = false;
nLastNodeId = 0;
nSendBufferMaxSize = 0;
nReceiveFloodSize = 0;
flagInterruptMsgProc = false;
SetTryNewOutboundPeer(false);
Options connOptions;
Init(connOptions);
}
NodeId CConnman::GetNewNodeId() {
return nLastNodeId.fetch_add(1, std::memory_order_relaxed);
}
bool CConnman::Bind(const CService &addr, unsigned int flags) {
if (!(flags & BF_EXPLICIT) && IsLimited(addr)) {
return false;
}
std::string strError;
if (!BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) {
if ((flags & BF_REPORT_ERROR) && clientInterface) {
clientInterface->ThreadSafeMessageBox(
strError, "", CClientUIInterface::MSG_ERROR);
}
return false;
}
return true;
}
bool CConnman::InitBinds(const std::vector &binds,
const std::vector &whiteBinds) {
bool fBound = false;
for (const auto &addrBind : binds) {
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR));
}
for (const auto &addrBind : whiteBinds) {
fBound |=
Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST));
}
if (binds.empty() && whiteBinds.empty()) {
struct in_addr inaddr_any;
inaddr_any.s_addr = INADDR_ANY;
struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT;
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE);
fBound |= Bind(CService(inaddr_any, GetListenPort()),
!fBound ? BF_REPORT_ERROR : BF_NONE);
}
return fBound;
}
bool CConnman::Start(CScheduler &scheduler, const Options &connOptions) {
Init(connOptions);
nTotalBytesRecv = 0;
nTotalBytesSent = 0;
nMaxOutboundTotalBytesSentInCycle = 0;
nMaxOutboundCycleStartTime = 0;
if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) {
if (clientInterface) {
clientInterface->ThreadSafeMessageBox(
_("Failed to listen on any port. Use -listen=0 if you want "
"this."),
"", CClientUIInterface::MSG_ERROR);
}
return false;
}
for (const auto &strDest : connOptions.vSeedNodes) {
AddOneShot(strDest);
}
if (clientInterface) {
clientInterface->InitMessage(_("Loading P2P addresses..."));
}
// Load addresses from peers.dat
int64_t nStart = GetTimeMillis();
{
CAddrDB adb(config->GetChainParams());
if (adb.Read(addrman)) {
LogPrintf("Loaded %i addresses from peers.dat %dms\n",
addrman.size(), GetTimeMillis() - nStart);
} else {
// Addrman can be in an inconsistent state after failure, reset it
addrman.Clear();
LogPrintf("Invalid or missing peers.dat; recreating\n");
DumpAddresses();
}
}
if (clientInterface) {
clientInterface->InitMessage(_("Loading banlist..."));
}
// Load addresses from banlist.dat
nStart = GetTimeMillis();
CBanDB bandb(config->GetChainParams());
banmap_t banmap;
if (bandb.Read(banmap)) {
// thread save setter
SetBanned(banmap);
// no need to write down, just read data
SetBannedSetDirty(false);
// sweep out unused entries
SweepBanned();
LogPrint(BCLog::NET,
"Loaded %d banned node ips/subnets from banlist.dat %dms\n",
banmap.size(), GetTimeMillis() - nStart);
} else {
LogPrintf("Invalid or missing banlist.dat; recreating\n");
// force write
SetBannedSetDirty(true);
DumpBanlist();
}
uiInterface.InitMessage(_("Starting network threads..."));
fAddressesInitialized = true;
if (semOutbound == nullptr) {
// initialize semaphore
semOutbound = MakeUnique(
std::min((nMaxOutbound + nMaxFeeler), nMaxConnections));
}
if (semAddnode == nullptr) {
// initialize semaphore
semAddnode = MakeUnique(nMaxAddnode);
}
//
// Start threads
//
assert(m_msgproc);
InterruptSocks5(false);
interruptNet.reset();
flagInterruptMsgProc = false;
{
LOCK(mutexMsgProc);
fMsgProcWake = false;
}
// Send and receive from sockets, accept connections
threadSocketHandler = std::thread(
&TraceThread>, "net",
std::function(std::bind(&CConnman::ThreadSocketHandler, this)));
if (!gArgs.GetBoolArg("-dnsseed", true)) {
LogPrintf("DNS seeding disabled\n");
} else {
threadDNSAddressSeed =
std::thread(&TraceThread>, "dnsseed",
std::function(
std::bind(&CConnman::ThreadDNSAddressSeed, this)));
}
// Initiate outbound connections from -addnode
threadOpenAddedConnections =
std::thread(&TraceThread>, "addcon",
std::function(std::bind(
&CConnman::ThreadOpenAddedConnections, this)));
if (connOptions.m_use_addrman_outgoing &&
!connOptions.m_specified_outgoing.empty()) {
if (clientInterface) {
clientInterface->ThreadSafeMessageBox(
_("Cannot provide specific connections and have addrman find "
"outgoing connections at the same."),
"", CClientUIInterface::MSG_ERROR);
}
return false;
}
if (connOptions.m_use_addrman_outgoing ||
!connOptions.m_specified_outgoing.empty()) {
threadOpenConnections =
std::thread(&TraceThread>, "opencon",
std::function(
std::bind(&CConnman::ThreadOpenConnections, this,
connOptions.m_specified_outgoing)));
}
// Process messages
threadMessageHandler =
std::thread(&TraceThread>, "msghand",
std::function(
std::bind(&CConnman::ThreadMessageHandler, this)));
// Dump network addresses
scheduler.scheduleEvery(
[this]() {
this->DumpData();
return true;
},
DUMP_ADDRESSES_INTERVAL * 1000);
return true;
}
class CNetCleanup {
public:
CNetCleanup() {}
~CNetCleanup() {
#ifdef WIN32
// Shutdown Windows Sockets
WSACleanup();
#endif
}
} instance_of_cnetcleanup;
void CConnman::Interrupt() {
{
std::lock_guard lock(mutexMsgProc);
flagInterruptMsgProc = true;
}
condMsgProc.notify_all();
interruptNet();
InterruptSocks5(true);
if (semOutbound) {
for (int i = 0; i < (nMaxOutbound + nMaxFeeler); i++) {
semOutbound->post();
}
}
if (semAddnode) {
for (int i = 0; i < nMaxAddnode; i++) {
semAddnode->post();
}
}
}
void CConnman::Stop() {
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();
}
if (fAddressesInitialized) {
DumpData();
fAddressesInitialized = false;
}
// Close sockets
for (CNode *pnode : vNodes) {
pnode->CloseSocketDisconnect();
}
for (ListenSocket &hListenSocket : vhListenSocket) {
if (hListenSocket.socket != INVALID_SOCKET) {
if (!CloseSocket(hListenSocket.socket)) {
LogPrintf("CloseSocket(hListenSocket) failed with error %s\n",
NetworkErrorString(WSAGetLastError()));
}
}
}
// clean up some globals (to help leak detection)
for (CNode *pnode : vNodes) {
DeleteNode(pnode);
}
for (CNode *pnode : vNodesDisconnected) {
DeleteNode(pnode);
}
vNodes.clear();
vNodesDisconnected.clear();
vhListenSocket.clear();
semOutbound.reset();
semAddnode.reset();
}
void CConnman::DeleteNode(CNode *pnode) {
assert(pnode);
bool fUpdateConnectionTime = false;
m_msgproc->FinalizeNode(*config, pnode->GetId(), fUpdateConnectionTime);
if (fUpdateConnectionTime) {
addrman.Connected(pnode->addr);
}
delete pnode;
}
CConnman::~CConnman() {
Interrupt();
Stop();
}
size_t CConnman::GetAddressCount() const {
return addrman.size();
}
void CConnman::SetServices(const CService &addr, ServiceFlags nServices) {
addrman.SetServices(addr, nServices);
}
void CConnman::MarkAddressGood(const CAddress &addr) {
addrman.Good(addr);
}
void CConnman::AddNewAddresses(const std::vector &vAddr,
const CAddress &addrFrom, int64_t nTimePenalty) {
addrman.Add(vAddr, addrFrom, nTimePenalty);
}
std::vector CConnman::GetAddresses() {
return addrman.GetAddr();
}
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::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->fInbound ? CONNECTIONS_IN : CONNECTIONS_OUT)) {
nNum++;
}
}
return nNum;
}
void CConnman::GetNodeStats(std::vector &vstats) {
vstats.clear();
LOCK(cs_vNodes);
vstats.reserve(vNodes.size());
for (CNode *pnode : vNodes) {
vstats.emplace_back();
pnode->copyStats(vstats.back());
}
}
bool CConnman::DisconnectNode(const std::string &strNode) {
LOCK(cs_vNodes);
if (CNode *pnode = FindNode(strNode)) {
pnode->fDisconnect = true;
return true;
}
return false;
}
bool CConnman::DisconnectNode(NodeId id) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (id == pnode->GetId()) {
pnode->fDisconnect = true;
return true;
}
}
return false;
}
void CConnman::RecordBytesRecv(uint64_t bytes) {
LOCK(cs_totalBytesRecv);
nTotalBytesRecv += bytes;
}
void CConnman::RecordBytesSent(uint64_t bytes) {
LOCK(cs_totalBytesSent);
nTotalBytesSent += bytes;
uint64_t now = GetTime();
if (nMaxOutboundCycleStartTime + nMaxOutboundTimeframe < now) {
// timeframe expired, reset cycle
nMaxOutboundCycleStartTime = now;
nMaxOutboundTotalBytesSentInCycle = 0;
}
// TODO, exclude whitebind peers
nMaxOutboundTotalBytesSentInCycle += bytes;
}
void CConnman::SetMaxOutboundTarget(uint64_t limit) {
LOCK(cs_totalBytesSent);
nMaxOutboundLimit = limit;
}
uint64_t CConnman::GetMaxOutboundTarget() {
LOCK(cs_totalBytesSent);
return nMaxOutboundLimit;
}
uint64_t CConnman::GetMaxOutboundTimeframe() {
LOCK(cs_totalBytesSent);
return nMaxOutboundTimeframe;
}
uint64_t CConnman::GetMaxOutboundTimeLeftInCycle() {
LOCK(cs_totalBytesSent);
if (nMaxOutboundLimit == 0) {
return 0;
}
if (nMaxOutboundCycleStartTime == 0) {
return nMaxOutboundTimeframe;
}
uint64_t cycleEndTime = nMaxOutboundCycleStartTime + nMaxOutboundTimeframe;
uint64_t now = GetTime();
return (cycleEndTime < now) ? 0 : cycleEndTime - GetTime();
}
void CConnman::SetMaxOutboundTimeframe(uint64_t timeframe) {
LOCK(cs_totalBytesSent);
if (nMaxOutboundTimeframe != timeframe) {
// reset measure-cycle in case of changing the timeframe.
nMaxOutboundCycleStartTime = GetTime();
}
nMaxOutboundTimeframe = timeframe;
}
bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) {
LOCK(cs_totalBytesSent);
if (nMaxOutboundLimit == 0) {
return false;
}
if (historicalBlockServingLimit) {
// keep a large enough buffer to at least relay each block once.
uint64_t timeLeftInCycle = GetMaxOutboundTimeLeftInCycle();
uint64_t buffer = timeLeftInCycle / 600 * ONE_MEGABYTE;
if (buffer >= nMaxOutboundLimit ||
nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit - buffer) {
return true;
}
} else if (nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit) {
return true;
}
return false;
}
uint64_t CConnman::GetOutboundTargetBytesLeft() {
LOCK(cs_totalBytesSent);
if (nMaxOutboundLimit == 0) {
return 0;
}
return (nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit)
? 0
: nMaxOutboundLimit - nMaxOutboundTotalBytesSentInCycle;
}
uint64_t CConnman::GetTotalBytesRecv() {
LOCK(cs_totalBytesRecv);
return nTotalBytesRecv;
}
uint64_t CConnman::GetTotalBytesSent() {
LOCK(cs_totalBytesSent);
return nTotalBytesSent;
}
ServiceFlags CConnman::GetLocalServices() const {
return nLocalServices;
}
void CConnman::SetBestHeight(int height) {
nBestHeight.store(height, std::memory_order_release);
}
int CConnman::GetBestHeight() const {
return nBestHeight.load(std::memory_order_acquire);
}
unsigned int CConnman::GetReceiveFloodSize() const {
return nReceiveFloodSize;
}
CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn,
int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn,
uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn,
const CAddress &addrBindIn, const std::string &addrNameIn,
bool fInboundIn)
: nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn),
addrBind(addrBindIn), fInbound(fInboundIn),
nKeyedNetGroup(nKeyedNetGroupIn), addrKnown(5000, 0.001),
filterInventoryKnown(50000, 0.000001), id(idIn),
nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn),
nMyStartingHeight(nMyStartingHeightIn), nSendVersion(0) {
nServices = NODE_NONE;
hSocket = hSocketIn;
nRecvVersion = INIT_PROTO_VERSION;
nLastSend = 0;
nLastRecv = 0;
nSendBytes = 0;
nRecvBytes = 0;
nTimeOffset = 0;
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
nVersion = 0;
strSubVer = "";
fWhitelisted = false;
fOneShot = false;
m_manual_connection = false;
// set by version message
fClient = false;
// set by version message
m_limited_node = false;
fFeeler = false;
fSuccessfullyConnected = false;
fDisconnect = false;
nRefCount = 0;
nSendSize = 0;
nSendOffset = 0;
hashContinue = uint256();
nStartingHeight = -1;
filterInventoryKnown.reset();
fSendMempool = false;
fGetAddr = false;
nNextLocalAddrSend = 0;
nNextAddrSend = 0;
nNextInvSend = 0;
fRelayTxes = false;
fSentAddr = false;
pfilter = MakeUnique();
timeLastMempoolReq = 0;
nLastBlockTime = 0;
nLastTXTime = 0;
nPingNonceSent = 0;
nPingUsecStart = 0;
nPingUsecTime = 0;
fPingQueued = false;
nMinPingUsecTime = std::numeric_limits::max();
minFeeFilter = Amount::zero();
lastSentFeeFilter = Amount::zero();
nextSendTimeFeeFilter = 0;
fPauseRecv = false;
fPauseSend = false;
nProcessQueueSize = 0;
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);
}
}
CNode::~CNode() {
CloseSocket(hSocket);
}
void CNode::AskFor(const CInv &inv) {
if (mapAskFor.size() > MAPASKFOR_MAX_SZ ||
setAskFor.size() > SETASKFOR_MAX_SZ) {
return;
}
// a peer may not have multiple non-responded queue positions for a single
// inv item.
if (!setAskFor.insert(inv.hash).second) {
return;
}
// We're using mapAskFor as a priority queue, the key is the earliest time
// the request can be sent.
int64_t nRequestTime;
limitedmap::const_iterator it =
mapAlreadyAskedFor.find(inv.hash);
if (it != mapAlreadyAskedFor.end()) {
nRequestTime = it->second;
} else {
nRequestTime = 0;
}
LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(),
- nRequestTime,
- DateTimeStrFormat("%H:%M:%S", nRequestTime / 1000000), id);
+ nRequestTime, FormatISO8601DateTime(nRequestTime / 1000000), id);
// Make sure not to reuse time indexes to keep things in the same order
int64_t nNow = GetTimeMicros() - 1000000;
static int64_t nLastTime;
++nLastTime;
nNow = std::max(nNow, nLastTime);
nLastTime = nNow;
// Each retry is 2 minutes after the last
nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow);
if (it != mapAlreadyAskedFor.end()) {
mapAlreadyAskedFor.update(it, nRequestTime);
} else {
mapAlreadyAskedFor.insert(std::make_pair(inv.hash, nRequestTime));
}
mapAskFor.insert(std::make_pair(nRequestTime, inv));
}
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();
size_t nTotalSize = nMessageSize + CMessageHeader::HEADER_SIZE;
LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n",
SanitizeString(msg.command.c_str()), nMessageSize, pnode->GetId());
std::vector serializedHeader;
serializedHeader.reserve(CMessageHeader::HEADER_SIZE);
uint256 hash = Hash(msg.data.data(), msg.data.data() + nMessageSize);
CMessageHeader hdr(config->GetChainParams().NetMagic(), msg.command.c_str(),
nMessageSize);
memcpy(hdr.pchChecksum, hash.begin(), CMessageHeader::CHECKSUM_SIZE);
CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, serializedHeader, 0, hdr};
size_t nBytesSent = 0;
{
LOCK(pnode->cs_vSend);
bool optimisticSend(pnode->vSendMsg.empty());
// log total amount of bytes per command
pnode->mapSendBytesPerMsgCmd[msg.command] += 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 func) {
CNode *found = nullptr;
LOCK(cs_vNodes);
for (auto &&pnode : vNodes) {
if (pnode->GetId() == id) {
found = pnode;
break;
}
}
return found != nullptr && NodeFullyConnected(found) && func(found);
}
int64_t PoissonNextSend(int64_t nNow, int average_interval_seconds) {
return nNow + int64_t(log1p(GetRand(1ULL << 48) *
-0.0000000000000035527136788 /* -1/2^48 */) *
average_interval_seconds * -1000000.0 +
0.5);
}
CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const {
return CSipHasher(nSeed0, nSeed1).Write(id);
}
uint64_t CConnman::CalculateKeyedNetGroup(const CAddress &ad) const {
std::vector vchNetGroup(ad.GetGroup());
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 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);
}
// Size compliance is checked at startup, it is safe to not check it again
std::string subversion =
FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
return subversion;
}
diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp
index 93457e79f5..e951154c96 100644
--- a/src/qt/paymentserver.cpp
+++ b/src/qt/paymentserver.cpp
@@ -1,880 +1,880 @@
// Copyright (c) 2011-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.
#include "paymentserver.h"
#include "bitcoinunits.h"
#include "guiutil.h"
#include "optionsmodel.h"
#include "chainparams.h"
#include "config.h"
#include "dstencode.h"
#include "policy/policy.h"
#include "ui_interface.h"
#include "util.h"
#include "wallet/wallet.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
// BIP70 payment protocol messages
const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
// BIP71 payment protocol media types
const char *BIP71_MIMETYPE_PAYMENT = "application/bitcoincash-payment";
const char *BIP71_MIMETYPE_PAYMENTACK = "application/bitcoincash-paymentack";
const char *BIP71_MIMETYPE_PAYMENTREQUEST =
"application/bitcoincash-paymentrequest";
struct X509StoreDeleter {
void operator()(X509_STORE *b) { X509_STORE_free(b); }
};
struct X509Deleter {
void operator()(X509 *b) { X509_free(b); }
};
namespace // Anon namespace
{
std::unique_ptr certStore;
}
//
// Create a name that is unique for:
// testnet / non-testnet
// data directory
//
static QString ipcServerName() {
QString name("BitcoinQt");
// Append a simple hash of the datadir
// Note that GetDataDir(true) returns a different path for -testnet versus
// main net
QString ddir(GUIUtil::boostPathToQString(GetDataDir(true)));
name.append(QString::number(qHash(ddir)));
return name;
}
//
// We store payment URIs and requests received before the main GUI window is up
// and ready to ask the user to send payment.
//
static QList savedPaymentRequests;
static void ReportInvalidCertificate(const QSslCertificate &cert) {
qDebug() << QString("%1: Payment server found an invalid certificate: ")
.arg(__func__)
<< cert.serialNumber()
<< cert.subjectInfo(QSslCertificate::CommonName)
<< cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier)
<< cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
}
//
// Load OpenSSL's list of root certificate authorities
//
void PaymentServer::LoadRootCAs(X509_STORE *_store) {
// Unit tests mostly use this, to pass in fake root CAs:
if (_store) {
certStore.reset(_store);
return;
}
// Normal execution, use either -rootcertificates or system certs:
certStore.reset(X509_STORE_new());
// Note: use "-system-" default here so that users can pass
// -rootcertificates="" and get 'I don't like X.509 certificates, don't
// trust anybody' behavior:
QString certFile =
QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-"));
// Empty store
if (certFile.isEmpty()) {
qDebug() << QString("PaymentServer::%1: Payment request authentication "
"via X.509 certificates disabled.")
.arg(__func__);
return;
}
QList certList;
if (certFile != "-system-") {
qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root "
"certificate.")
.arg(__func__)
.arg(certFile);
certList = QSslCertificate::fromPath(certFile);
// Use those certificates when fetching payment requests, too:
QSslSocket::setDefaultCaCertificates(certList);
} else
certList = QSslSocket::systemCaCertificates();
int nRootCerts = 0;
const QDateTime currentTime = QDateTime::currentDateTime();
for (const QSslCertificate &cert : certList) {
// Don't log nullptr certificates
if (cert.isNull()) {
continue;
}
// Not yet active/valid, or expired certificate
if (currentTime < cert.effectiveDate() ||
currentTime > cert.expiryDate()) {
ReportInvalidCertificate(cert);
continue;
}
// Blacklisted certificate
if (cert.isBlacklisted()) {
ReportInvalidCertificate(cert);
continue;
}
QByteArray certData = cert.toDer();
const uint8_t *data = (const uint8_t *)certData.data();
std::unique_ptr x509(
d2i_X509(0, &data, certData.size()));
if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) {
// Note: X509_STORE increases the reference count to the X509
// object, we still have to release our reference to it.
++nRootCerts;
} else {
ReportInvalidCertificate(cert);
continue;
}
}
qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts
<< " root certificates";
// Project for another day:
// Fetch certificate revocation lists, and add them to certStore.
// Issues to consider:
// performance (start a thread to fetch in background?)
// privacy (fetch through tor/proxy so IP address isn't revealed)
// would it be easier to just use a compiled-in blacklist?
// or use Qt's blacklist?
// "certificate stapling" with server-side caching is more efficient
}
static std::string ipcParseURI(const QString &arg, const CChainParams ¶ms,
bool useCashAddr) {
const QString scheme = GUIUtil::bitcoinURIScheme(params, useCashAddr);
if (!arg.startsWith(scheme + ":", Qt::CaseInsensitive)) {
return {};
}
SendCoinsRecipient r;
if (!GUIUtil::parseBitcoinURI(scheme, arg, &r)) {
return {};
}
return r.address.toStdString();
}
static bool ipcCanParseCashAddrURI(const QString &arg,
const std::string &network) {
auto tempChainParams = CreateChainParams(network);
std::string addr = ipcParseURI(arg, *tempChainParams, true);
return IsValidDestinationString(addr, *tempChainParams);
}
static bool ipcCanParseLegacyURI(const QString &arg,
const std::string &network) {
auto tempChainParams = CreateChainParams(network);
std::string addr = ipcParseURI(arg, *tempChainParams, false);
return IsValidDestinationString(addr, *tempChainParams);
}
//
// Sending to the server is done synchronously, at startup.
// If the server isn't already running, startup continues, and the items in
// savedPaymentRequest will be handled when uiReady() is called.
//
// Warning: ipcSendCommandLine() is called early in init, so don't use "Q_EMIT
// message()", but "QMessageBox::"!
//
void PaymentServer::ipcParseCommandLine(int argc, char *argv[]) {
std::array networks = {
{&CBaseChainParams::MAIN, &CBaseChainParams::TESTNET,
&CBaseChainParams::REGTEST}};
const std::string *chosenNetwork = nullptr;
for (int i = 1; i < argc; i++) {
QString arg(argv[i]);
if (arg.startsWith("-")) {
continue;
}
const std::string *itemNetwork = nullptr;
// Try to parse as a URI
for (auto net : networks) {
if (ipcCanParseCashAddrURI(arg, *net)) {
itemNetwork = net;
break;
}
if (ipcCanParseLegacyURI(arg, *net)) {
itemNetwork = net;
break;
}
}
if (!itemNetwork && QFile::exists(arg)) {
// Filename
PaymentRequestPlus request;
if (readPaymentRequestFromFile(arg, request)) {
for (auto net : networks) {
if (*net == request.getDetails().network()) {
itemNetwork = net;
}
}
}
}
if (itemNetwork == nullptr) {
// Printing to debug.log is about the best we can do here, the GUI
// hasn't started yet so we can't pop up a message box.
qWarning() << "PaymentServer::ipcSendCommandLine: Payment request "
"file or URI does not exist or is invalid: "
<< arg;
continue;
}
if (chosenNetwork && chosenNetwork != itemNetwork) {
qWarning() << "PaymentServer::ipcSendCommandLine: Payment request "
"from network "
<< QString(itemNetwork->c_str())
<< " does not match already chosen network "
<< QString(chosenNetwork->c_str());
continue;
}
savedPaymentRequests.append(arg);
chosenNetwork = itemNetwork;
}
if (chosenNetwork) {
SelectParams(*chosenNetwork);
}
}
//
// Sending to the server is done synchronously, at startup.
// If the server isn't already running, startup continues, and the items in
// savedPaymentRequest will be handled when uiReady() is called.
//
bool PaymentServer::ipcSendCommandLine() {
bool fResult = false;
for (const QString &r : savedPaymentRequests) {
QLocalSocket *socket = new QLocalSocket();
socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT)) {
delete socket;
socket = nullptr;
return false;
}
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << r;
out.device()->seek(0);
socket->write(block);
socket->flush();
socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
socket->disconnectFromServer();
delete socket;
socket = nullptr;
fResult = true;
}
return fResult;
}
PaymentServer::PaymentServer(QObject *parent, bool startLocalServer)
: QObject(parent), saveURIs(true), uriServer(0), netManager(0),
optionsModel(0) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
// Install global event filter to catch QFileOpenEvents
// on Mac: sent when you click bitcoincash: links
// other OSes: helpful when dealing with payment request files
if (parent) {
parent->installEventFilter(this);
}
QString name = ipcServerName();
// Clean up old socket leftover from a crash:
QLocalServer::removeServer(name);
if (startLocalServer) {
uriServer = new QLocalServer(this);
if (!uriServer->listen(name)) {
// constructor is called early in init, so don't use "Q_EMIT
// message()" here
QMessageBox::critical(0, tr("Payment request error"),
tr("Cannot start click-to-pay handler"));
} else {
connect(uriServer, SIGNAL(newConnection()), this,
SLOT(handleURIConnection()));
connect(this, SIGNAL(receivedPaymentACK(QString)), this,
SLOT(handlePaymentACK(QString)));
}
}
}
PaymentServer::~PaymentServer() {
google::protobuf::ShutdownProtobufLibrary();
}
//
// OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types.
// Also used by paymentservertests.cpp and when opening a payment request file
// via "Open URI..." menu entry.
//
bool PaymentServer::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent *fileEvent = static_cast(event);
if (!fileEvent->file().isEmpty()) {
handleURIOrFile(fileEvent->file());
} else if (!fileEvent->url().isEmpty()) {
handleURIOrFile(fileEvent->url().toString());
}
return true;
}
return QObject::eventFilter(object, event);
}
void PaymentServer::initNetManager() {
if (!optionsModel) {
return;
}
if (netManager != nullptr) {
delete netManager;
}
// netManager is used to fetch paymentrequests given in bitcoincash: URIs
netManager = new QNetworkAccessManager(this);
QNetworkProxy proxy;
// Query active SOCKS5 proxy
if (optionsModel->getProxySettings(proxy)) {
netManager->setProxy(proxy);
qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy"
<< proxy.hostName() << ":" << proxy.port();
} else {
qDebug()
<< "PaymentServer::initNetManager: No active proxy server found.";
}
connect(netManager, SIGNAL(finished(QNetworkReply *)), this,
SLOT(netRequestFinished(QNetworkReply *)));
connect(netManager,
SIGNAL(sslErrors(QNetworkReply *, const QList &)), this,
SLOT(reportSslErrors(QNetworkReply *, const QList &)));
}
void PaymentServer::uiReady() {
initNetManager();
saveURIs = false;
for (const QString &s : savedPaymentRequests) {
handleURIOrFile(s);
}
savedPaymentRequests.clear();
}
bool PaymentServer::handleURI(const QString &scheme, const QString &s) {
if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) {
return false;
}
QUrlQuery uri((QUrl(s)));
if (uri.hasQueryItem("r")) {
// payment request URI
QByteArray temp;
temp.append(uri.queryItemValue("r"));
QString decoded = QUrl::fromPercentEncoding(temp);
QUrl fetchUrl(decoded, QUrl::StrictMode);
if (fetchUrl.isValid()) {
qDebug() << "PaymentServer::handleURIOrFile: fetchRequest("
<< fetchUrl << ")";
fetchRequest(fetchUrl);
} else {
qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: "
<< fetchUrl;
Q_EMIT message(tr("URI handling"),
tr("Payment request fetch URL is invalid: %1")
.arg(fetchUrl.toString()),
CClientUIInterface::ICON_WARNING);
}
return true;
}
// normal URI
SendCoinsRecipient recipient;
if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) {
if (!IsValidDestinationString(recipient.address.toStdString(),
GetConfig().GetChainParams())) {
Q_EMIT message(
tr("URI handling"),
tr("Invalid payment address %1").arg(recipient.address),
CClientUIInterface::MSG_ERROR);
} else {
Q_EMIT receivedPaymentRequest(recipient);
}
} else {
Q_EMIT message(
tr("URI handling"),
tr("URI cannot be parsed! This can be caused by an invalid "
"Bitcoin address or malformed URI parameters."),
CClientUIInterface::ICON_WARNING);
}
return true;
}
void PaymentServer::handleURIOrFile(const QString &s) {
if (saveURIs) {
savedPaymentRequests.append(s);
return;
}
// bitcoincash: CashAddr URI
QString schemeCash = GUIUtil::bitcoinURIScheme(Params(), true);
if (handleURI(schemeCash, s)) {
return;
}
// bitcoincash: Legacy URI
QString schemeLegacy = GUIUtil::bitcoinURIScheme(Params(), false);
if (handleURI(schemeLegacy, s)) {
return;
}
// payment request file
if (QFile::exists(s)) {
PaymentRequestPlus request;
SendCoinsRecipient recipient;
if (!readPaymentRequestFromFile(s, request)) {
Q_EMIT message(tr("Payment request file handling"),
tr("Payment request file cannot be read! This can "
"be caused by an invalid payment request file."),
CClientUIInterface::ICON_WARNING);
} else if (processPaymentRequest(request, recipient)) {
Q_EMIT receivedPaymentRequest(recipient);
}
return;
}
}
void PaymentServer::handleURIConnection() {
QLocalSocket *clientConnection = uriServer->nextPendingConnection();
while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) {
clientConnection->waitForReadyRead();
}
connect(clientConnection, SIGNAL(disconnected()), clientConnection,
SLOT(deleteLater()));
QDataStream in(clientConnection);
in.setVersion(QDataStream::Qt_4_0);
if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
return;
}
QString msg;
in >> msg;
handleURIOrFile(msg);
}
//
// Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
// so don't use "Q_EMIT message()", but "QMessageBox::"!
//
bool PaymentServer::readPaymentRequestFromFile(const QString &filename,
PaymentRequestPlus &request) {
QFile f(filename);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << QString("PaymentServer::%1: Failed to open %2")
.arg(__func__)
.arg(filename);
return false;
}
// BIP70 DoS protection
if (!verifySize(f.size())) {
return false;
}
QByteArray data = f.readAll();
return request.parse(data);
}
bool PaymentServer::processPaymentRequest(const PaymentRequestPlus &request,
SendCoinsRecipient &recipient) {
if (!optionsModel) {
return false;
}
if (request.IsInitialized()) {
// Payment request network matches client network?
if (!verifyNetwork(request.getDetails())) {
Q_EMIT message(
tr("Payment request rejected"),
tr("Payment request network doesn't match client network."),
CClientUIInterface::MSG_ERROR);
return false;
}
// Make sure any payment requests involved are still valid.
// This is re-checked just before sending coins in
// WalletModel::sendCoins().
if (verifyExpired(request.getDetails())) {
Q_EMIT message(tr("Payment request rejected"),
tr("Payment request expired."),
CClientUIInterface::MSG_ERROR);
return false;
}
} else {
Q_EMIT message(tr("Payment request error"),
tr("Payment request is not initialized."),
CClientUIInterface::MSG_ERROR);
return false;
}
recipient.paymentRequest = request;
recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
request.getMerchant(certStore.get(), recipient.authenticatedMerchant);
QList> sendingTos = request.getPayTo();
QStringList addresses;
for (const std::pair