diff --git a/doc/release-notes.md b/doc/release-notes.md
index 35b602834..738c5d370 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,8 +1,10 @@
Bitcoin ABC version 0.16.2 is now available from:
This release includes the following features and fixes:
- Remove the newdaaactivationtime configuration.
- Do not use the NODE_BITCOIN_CASH service bit for preferencial peering anymore.
+ - Only connect to node using the cash magic.
+ - Remove indicator mentionning if a node uses the cash magic getpeerinfo RPC.
diff --git a/src/net.cpp b/src/net.cpp
index 1db5c7bcc..527a1a226 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1,3056 +1,3052 @@
// 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
#if !defined(HAVE_MSG_NOSIGNAL) && !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 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
static const std::string NET_MESSAGE_COMMAND_OTHER = "*other*";
// SHA256("netgroup")[0:8]
static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL;
// SHA256("localhostnonce")[0:8]
static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL;
//
// 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);
// Signals for message handling
static CNodeSignals g_signals;
CNodeSignals &GetNodeSignals() {
return g_signals;
}
void CConnman::AddOneShot(const std::string &strDest) {
LOCK(cs_vOneShots);
vOneShots.push_back(strDest);
}
unsigned short GetListenPort() {
return (unsigned short)(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 (std::map::iterator it =
mapLocalHost.begin();
it != mapLocalHost.end(); it++) {
int nScore = (*it).second.nScore;
int nReachability = (*it).first.GetReachabilityFrom(paddrPeer);
if (nReachability > nBestReachability ||
(nReachability == nBestReachability && nScore > nBestScore)) {
addr = CService((*it).first, (*it).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 (std::vector::const_iterator i(vSeedsIn.begin());
i != vSeedsIn.end(); ++i) {
struct in6_addr ip;
memcpy(&ip, i->addr, sizeof(ip));
CAddress addr(CService(ip, i->port), NODE_NETWORK);
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()), NODE_NONE);
CService addr;
if (GetLocal(addr, paddrPeer)) {
ret = CAddress(addr, nLocalServices);
}
ret.nTime = GetAdjustedTime();
return ret;
}
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 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()) {
LogPrint("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);
}
bool RemoveLocal(const CService &addr) {
LOCK(cs_mapLocalHost);
LogPrintf("RemoveLocal(%s)\n", addr.ToString());
mapLocalHost.erase(addr);
return true;
}
/** Make a particular network entirely off-limits (no automatic connects to it)
*/
void SetLimited(enum Network net, bool fLimited) {
if (net == NET_UNROUTABLE) {
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 ((CNetAddr)pnode->addr == ip) {
return pnode;
}
}
return nullptr;
}
CNode *CConnman::FindNode(const CSubNet &subNet) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (subNet.Match((CNetAddr)pnode->addr)) {
return pnode;
}
}
return nullptr;
}
CNode *CConnman::FindNode(const std::string &addrName) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (pnode->GetAddrName() == addrName) {
return pnode;
}
}
return nullptr;
}
CNode *CConnman::FindNode(const CService &addr) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if ((CService)pnode->addr == addr) {
return pnode;
}
}
return nullptr;
}
bool CConnman::CheckIncomingNonce(uint64_t nonce) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (!pnode->fSuccessfullyConnected && !pnode->fInbound &&
pnode->GetLocalNonce() == nonce)
return false;
}
return true;
}
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((CService)addrConnect);
if (pnode) {
LogPrintf("Failed to open new connection, already connected\n");
return nullptr;
}
}
/// debug print
LogPrint("net", "trying connection %s lastseen=%.1fhrs\n",
pszDest ? pszDest : addrConnect.ToString(),
pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime) /
3600.0);
// Connect
SOCKET hSocket;
bool proxyConnectionFailed = false;
if (pszDest ? ConnectSocketByName(addrConnect, hSocket, pszDest,
Params().GetDefaultPort(),
nConnectTimeout, &proxyConnectionFailed)
: ConnectSocket(addrConnect, hSocket, nConnectTimeout,
&proxyConnectionFailed)) {
if (!IsSelectableSocket(hSocket)) {
LogPrintf("Cannot create connection: non-selectable socket created "
"(fd >= FD_SETSIZE ?)\n");
CloseSocket(hSocket);
return nullptr;
}
if (pszDest && addrConnect.IsValid()) {
// 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((CService)addrConnect);
if (pnode) {
pnode->MaybeSetAddrName(std::string(pszDest));
CloseSocket(hSocket);
LogPrintf("Failed to open new connection, already connected\n");
return nullptr;
}
}
addrman.Attempt(addrConnect, fCountFailure);
// Add node
NodeId id = GetNewNodeId();
uint64_t nonce =
GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE)
.Write(id)
.Finalize();
CNode *pnode =
new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect,
CalculateKeyedNetGroup(addrConnect), nonce,
pszDest ? pszDest : "", false);
pnode->nServicesExpected =
ServiceFlags(addrConnect.nServices & nRelevantServices);
pnode->AddRef();
return pnode;
} else if (!proxyConnectionFailed) {
// If connecting to the node failed, and failure is not caused by a
// problem connecting to the proxy, mark this as an attempt.
addrman.Attempt(addrConnect, fCountFailure);
}
return nullptr;
}
void CConnman::DumpBanlist() {
// Clean unused entries (if bantime has expired)
SweepBanned();
if (!BannedSetIsDirty()) {
return;
}
int64_t nStart = GetTimeMillis();
CBanDB bandb;
banmap_t banmap;
GetBanned(banmap);
if (bandb.Write(banmap)) {
SetBannedSetDirty(false);
}
LogPrint("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("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);
bool fResult = false;
for (banmap_t::iterator it = setBanned.begin(); it != setBanned.end();
it++) {
CSubNet subNet = (*it).first;
CBanEntry banEntry = (*it).second;
if (subNet.Match(ip) && GetTime() < banEntry.nBanUntil) {
fResult = true;
}
}
return fResult;
}
bool CConnman::IsBanned(CSubNet subnet) {
LOCK(cs_setBanned);
bool fResult = false;
banmap_t::iterator i = setBanned.find(subnet);
if (i != setBanned.end()) {
CBanEntry banEntry = (*i).second;
if (GetTime() < banEntry.nBanUntil) {
fResult = true;
}
}
return fResult;
}
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 = 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((CNetAddr)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();
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;
LogPrint("net",
"%s: Removed banned node ip/subnet from banlist.dat: %s\n",
__func__, subNet.ToString());
} else {
++it;
}
}
}
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) {
LOCK(cs_vWhitelistedRange);
for (const CSubNet &subnet : vWhitelistedRange) {
if (subnet.Match(addr)) {
return true;
}
}
return false;
}
void CConnman::AddWhitelistedRange(const CSubNet &subnet) {
LOCK(cs_vWhitelistedRange);
vWhitelistedRange.push_back(subnet);
}
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;
}
}
#undef X
#define X(name) stats.name = name
void CNode::copyStats(CNodeStats &stats) {
stats.nodeid = this->GetId();
X(nServices);
X(addr);
{
LOCK(cs_filter);
X(fRelayTxes);
}
X(nLastSend);
X(nLastRecv);
X(nTimeConnected);
X(nTimeOffset);
stats.addrName = GetAddrName();
X(nVersion);
{
LOCK(cs_SubVer);
X(cleanSubVer);
}
X(fInbound);
X(fAddnode);
X(nStartingHeight);
{
LOCK(cs_vSend);
X(mapSendBytesPerMsgCmd);
X(nSendBytes);
}
{
LOCK(cs_vRecv);
X(mapRecvBytesPerMsgCmd);
X(nRecvBytes);
}
X(fWhitelisted);
- X(fUsesCashMagic);
// 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() : "";
}
#undef X
bool CNode::ReceiveMsgBytes(const char *pch, unsigned int 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(GetMagic(Params()), SER_NETWORK,
+ vRecvMsg.push_back(CNetMessage(Params().NetMagic(), SER_NETWORK,
INIT_PROTO_VERSION));
}
CNetMessage &msg = vRecvMsg.back();
// Absorb network data.
int handled;
if (!msg.in_data) {
handled = msg.readHeader(pch, nBytes);
} else {
handled = msg.readData(pch, nBytes);
}
if (handled < 0) {
return false;
}
if (msg.in_data && msg.hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) {
LogPrint("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);
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 char *pch, unsigned int nBytes) {
// copy data to temporary parsing buffer
unsigned int nRemaining = 24 - nHdrPos;
unsigned int 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 messages larger than MAX_SIZE
if (hdr.nMessageSize > MAX_SIZE) {
return -1;
}
// switch state to reading message data
in_data = true;
return nCopy;
}
int CNetMessage::readData(const char *pch, unsigned int 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;
}
/**
* 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->id,
node->nTimeConnected,
node->nMinPingUsecTime,
node->nLastBlockTime,
node->nLastTXTime,
(node->nServices & nRelevantServices) == nRelevantServices,
node->fRelayTxes,
node->pfilter != nullptr,
node->addr,
node->nKeyedNetGroup};
vEvictionCandidates.push_back(candidate);
}
}
if (vEvictionCandidates.empty()) {
return false;
}
// Protect connections with certain characteristics
// Deterministically select 4 peers to protect by netgroup. An attacker
// cannot predict which netgroups will be protected.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(),
CompareNetGroupKeyed);
vEvictionCandidates.erase(
vEvictionCandidates.end() -
std::min(4, static_cast(vEvictionCandidates.size())),
vEvictionCandidates.end());
if (vEvictionCandidates.empty()) {
return false;
}
// Protect the 8 nodes with the lowest minimum ping time. An attacker cannot
// manipulate this metric without physically moving nodes closer to the
// target.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(),
ReverseCompareNodeMinPingTime);
vEvictionCandidates.erase(
vEvictionCandidates.end() -
std::min(8, static_cast(vEvictionCandidates.size())),
vEvictionCandidates.end());
if (vEvictionCandidates.empty()) {
return false;
}
// Protect 4 nodes that most recently sent us transactions. An attacker
// cannot manipulate this metric without performing useful work.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(),
CompareNodeTXTime);
vEvictionCandidates.erase(
vEvictionCandidates.end() -
std::min(4, static_cast(vEvictionCandidates.size())),
vEvictionCandidates.end());
if (vEvictionCandidates.empty()) {
return false;
}
// Protect 4 nodes that most recently sent us blocks. An attacker cannot
// manipulate this metric without performing useful work.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(),
CompareNodeBlockTime);
vEvictionCandidates.erase(
vEvictionCandidates.end() -
std::min(4, static_cast(vEvictionCandidates.size())),
vEvictionCandidates.end());
if (vEvictionCandidates.empty()) {
return false;
}
// 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.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(),
ReverseCompareNodeTimeConnected);
vEvictionCandidates.erase(
vEvictionCandidates.end() -
static_cast(vEvictionCandidates.size() / 2),
vEvictionCandidates.end());
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) {
mapNetGroupNodes[node.nKeyedNetGroup].push_back(node);
int64_t grouptime =
mapNetGroupNodes[node.nKeyedNetGroup][0].nTimeConnected;
size_t groupsize = mapNetGroupNodes[node.nKeyedNetGroup].size();
if (groupsize > nMostConnections ||
(groupsize == nMostConnections &&
grouptime > nMostConnectionsTime)) {
nMostConnections = groupsize;
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 (std::vector::const_iterator it(vNodes.begin());
it != vNodes.end(); ++it) {
if ((*it)->GetId() == evicted) {
(*it)->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 (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.
int set = 1;
#ifdef WIN32
setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&set,
sizeof(int));
#else
setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (void *)&set, sizeof(int));
#endif
if (IsBanned(addr) && !whitelisted) {
LogPrintf("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("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();
CNode *pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr,
CalculateKeyedNetGroup(addr), nonce, "", true);
pnode->AddRef();
pnode->fWhitelisted = whitelisted;
GetNodeSignals().InitializeNode(*config, pnode, *this);
LogPrint("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);
// 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];
int 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(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("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("net", "socket no message in first 60 seconds, %d "
"%d from %d\n",
pnode->nLastRecv != 0, pnode->nLastSend != 0,
pnode->id);
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) {
LogPrintf("version handshake timeout from %d\n", pnode->id);
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
void ThreadMapPort() {
std::string port = strprintf("%u", GetListenPort());
const char *multicastif = 0;
const char *minissdpdpath = 0;
struct UPNPDev *devlist = 0;
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();
try {
while (true) {
#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");
}
// Refresh every 20 minutes
MilliSleep(20 * 60 * 1000);
}
} catch (const boost::thread_interrupted &) {
r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype,
port.c_str(), "TCP", 0);
LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r);
freeUPNPDevlist(devlist);
devlist = 0;
FreeUPNPUrls(&urls);
throw;
}
} else {
LogPrintf("No valid UPnP IGDs found\n");
freeUPNPDevlist(devlist);
devlist = 0;
if (r != 0) {
FreeUPNPUrls(&urls);
}
}
}
void MapPort(bool fUseUPnP) {
static boost::thread *upnp_thread = nullptr;
if (fUseUPnP) {
if (upnp_thread) {
upnp_thread->interrupt();
upnp_thread->join();
delete upnp_thread;
}
upnp_thread = new boost::thread(
boost::bind(&TraceThread, "upnp", &ThreadMapPort));
} else if (upnp_thread) {
upnp_thread->interrupt();
upnp_thread->join();
delete upnp_thread;
upnp_thread = nullptr;
}
}
#else
void MapPort(bool) {
// Intentionally left blank.
}
#endif
static std::string GetDNSHost(const CDNSSeedData &data,
ServiceFlags *requiredServiceBits) {
// use default host for non-filter-capable seeds or if we use the default
// service bits (NODE_NETWORK)
if (!data.supportsServiceBitsFiltering ||
*requiredServiceBits == NODE_NETWORK) {
*requiredServiceBits = NODE_NETWORK;
return data.host;
}
// See chainparams.cpp, most dnsseeds only support one or two possible
// servicebits hostnames
return strprintf("x%x.%s", *requiredServiceBits, data.host);
}
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) &&
(!GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED))) {
if (!interruptNet.sleep_for(std::chrono::seconds(11))) {
return;
}
LOCK(cs_vNodes);
int nRelevant = 0;
for (auto pnode : vNodes) {
nRelevant +=
pnode->fSuccessfullyConnected &&
((pnode->nServices & nRelevantServices) == nRelevantServices);
}
if (nRelevant >= 2) {
LogPrintf("P2P peers available. Skipped DNS seeding.\n");
return;
}
}
const std::vector &vSeeds = Params().DNSSeeds();
int found = 0;
LogPrintf("Loading addresses from DNS seeds (could take a while)\n");
for (const CDNSSeedData &seed : vSeeds) {
if (HaveNameProxy()) {
AddOneShot(seed.host);
} else {
std::vector vIPs;
std::vector vAdd;
ServiceFlags requiredServiceBits = nRelevantServices;
if (LookupHost(GetDNSHost(seed, &requiredServiceBits).c_str(), vIPs,
0, true)) {
for (const CNetAddr &ip : vIPs) {
int nOneDay = 24 * 3600;
CAddress addr =
CAddress(CService(ip, Params().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++;
}
}
// TODO: The seed name resolve may fail, yielding an IP of [::],
// which results in addrman assigning the same source to results
// from different seeds. This should switch to a hard-coded stable
// dummy IP for each seed name, so that the resolve is not required
// at all.
if (!vIPs.empty()) {
CService seedSource;
Lookup(seed.name.c_str(), seedSource, 0, true);
addrman.Add(vAdd, seedSource);
}
}
}
LogPrintf("%d addresses found from DNS seeds\n", found);
}
void CConnman::DumpAddresses() {
int64_t nStart = GetTimeMillis();
CAddrDB adb;
adb.Write(addrman);
LogPrint("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) {
if (!OpenNetworkConnection(addr, false, &grant, strDest.c_str(),
true)) {
AddOneShot(strDest);
}
}
}
void CConnman::ThreadOpenConnections() {
// Connect to specific addresses
if (mapMultiArgs.count("-connect") &&
mapMultiArgs.at("-connect").size() > 0) {
for (int64_t nLoop = 0;; nLoop++) {
ProcessOneShot();
for (const std::string &strAddr : mapMultiArgs.at("-connect")) {
CAddress addr(CService(), NODE_NONE);
OpenNetworkConnection(addr, false, nullptr, strAddr.c_str());
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;
LookupHost("127.0.0.1", local, false);
addrman.Add(convertSeed6(Params().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 (CNode *pnode : vNodes) {
if (!pnode->fInbound && !pnode->fAddnode) {
// 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) {
// The current time right now (in microseconds).
int64_t nTime = GetTimeMicros();
if (nTime > nNextFeeler) {
nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
fFeeler = true;
} else {
continue;
}
}
int64_t nANow = GetAdjustedTime();
int nTries = 0;
while (!interruptNet) {
CAddrInfo 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 connect to full nodes
if ((addr.nServices & REQUIRED_SERVICES) != REQUIRED_SERVICES) {
continue;
}
// only consider very recently tried nodes after 30 failed attempts
if (nANow - addr.nLastTry < 600 && nTries < 30) {
continue;
}
// only consider nodes missing relevant services after 40 failed
// attempts and only if less than half the outbound are up.
if ((addr.nServices & nRelevantServices) != nRelevantServices &&
(nTries < 40 || nOutbound >= (nMaxOutbound >> 1))) {
continue;
}
// do not allow non-default ports, unless after 50 invalid addresses
// selected already.
if (addr.GetPort() != Params().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("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());
for (const std::string &strAddNode : vAddedNodes) {
lAddresses.push_back(strAddNode);
}
}
// 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()));
if (service.IsValid()) {
// strAddNode is an IP:port
auto it = mapConnected.find(service);
if (it != mapConnected.end()) {
ret.push_back(
AddedNodeInfo{strAddNode, service, true, it->second});
} else {
ret.push_back(
AddedNodeInfo{strAddNode, CService(), false, false});
}
} else {
// strAddNode is a name
auto it = mapConnectedByName.find(strAddNode);
if (it != mapConnectedByName.end()) {
ret.push_back(AddedNodeInfo{strAddNode, it->second.second, true,
it->second.first});
} else {
ret.push_back(
AddedNodeInfo{strAddNode, CService(), false, false});
}
}
}
return ret;
}
void CConnman::ThreadOpenAddedConnections() {
{
LOCK(cs_vAddedNodes);
if (mapMultiArgs.count("-addnode")) {
vAddedNodes = mapMultiArgs.at("-addnode");
}
}
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;
}
// If strAddedNode is an IP/port, decode it immediately, so
// OpenNetworkConnection can detect existing connections to that
// IP/port.
tried = true;
CService service(LookupNumeric(info.strAddedNode.c_str(),
Params().GetDefaultPort()));
OpenNetworkConnection(CAddress(service, NODE_NONE), 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.
bool CConnman::OpenNetworkConnection(const CAddress &addrConnect,
bool fCountFailure,
CSemaphoreGrant *grantOutbound,
const char *pszDest, bool fOneShot,
bool fFeeler, bool fAddnode) {
//
// Initiate outbound network connection
//
if (interruptNet) {
return false;
}
if (!fNetworkActive) {
return false;
}
if (!pszDest) {
if (IsLocal(addrConnect) || FindNode((CNetAddr)addrConnect) ||
IsBanned(addrConnect) || FindNode(addrConnect.ToStringIPPort())) {
return false;
}
} else if (FindNode(std::string(pszDest))) {
return false;
}
CNode *pnode = ConnectNode(addrConnect, pszDest, fCountFailure);
if (!pnode) {
return false;
}
if (grantOutbound) {
grantOutbound->MoveTo(pnode->grantOutbound);
}
if (fOneShot) {
pnode->fOneShot = true;
}
if (fFeeler) {
pnode->fFeeler = true;
}
if (fAddnode) {
pnode->fAddnode = true;
}
GetNodeSignals().InitializeNode(*config, pnode, *this);
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
return true;
}
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 = GetNodeSignals().ProcessMessages(
*config, pnode, *this, flagInterruptMsgProc);
fMoreWork |= (fMoreNodeWork && !pnode->fPauseSend);
if (flagInterruptMsgProc) {
return;
}
// Send messages
{
LOCK(pnode->cs_sendProcessing);
GetNodeSignals().SendMessages(*config, pnode, *this,
flagInterruptMsgProc);
}
if (flagInterruptMsgProc) {
return;
}
}
{
LOCK(cs_vNodes);
for (CNode *pnode : vNodesCopy) {
pnode->Release();
}
}
std::unique_lock lock(mutexMsgProc);
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 = socket(((struct sockaddr *)&sockaddr)->sa_family,
SOCK_STREAM, IPPROTO_TCP);
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;
}
if (!IsSelectableSocket(hListenSocket)) {
strError = "Error: Couldn't create a listenable socket for incoming "
"connections";
LogPrintf("%s\n", strError);
return false;
}
#ifndef WIN32
#ifdef SO_NOSIGPIPE
// Different way of disabling SIGPIPE on BSD
setsockopt(hListenSocket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&nOne,
sizeof(int));
#endif
// Allow binding if the port is still in TIME_WAIT state after
// the program was closed and restarted.
setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (void *)&nOne,
sizeof(int));
// Disable Nagle's algorithm
setsockopt(hListenSocket, IPPROTO_TCP, TCP_NODELAY, (void *)&nOne,
sizeof(int));
#else
setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&nOne,
sizeof(int));
setsockopt(hListenSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&nOne,
sizeof(int));
#endif
// Set to non-blocking, incoming connections will also inherit this
if (!SetSocketNonBlocking(hListenSocket, true)) {
strError = strprintf("BindListenPort: Setting listening socket to "
"non-blocking failed, error %s\n",
NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError);
return false;
}
// 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
#ifdef WIN32
setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY,
(const char *)&nOne, sizeof(int));
#else
setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&nOne,
sizeof(int));
#endif
#endif
#ifdef WIN32
int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED;
setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL,
(const char *)&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(boost::thread_group &threadGroup) {
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 = (struct sockaddr_in *)(ifa->ifa_addr);
CNetAddr addr(s4->sin_addr);
if (AddLocal(addr, LOCAL_IF)) {
LogPrintf("%s: IPv4 %s: %s\n", __func__, ifa->ifa_name,
addr.ToString());
}
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
struct sockaddr_in6 *s6 =
(struct sockaddr_in6 *)(ifa->ifa_addr);
CNetAddr addr(s6->sin6_addr);
if (AddLocal(addr, LOCAL_IF)) {
LogPrintf("%s: IPv6 %s: %s\n", __func__, ifa->ifa_name,
addr.ToString());
}
}
}
freeifaddrs(myaddrs);
}
#endif
}
void CConnman::SetNetworkActive(bool active) {
if (fDebug) {
LogPrint("net", "SetNetworkActive: %s\n", active);
}
if (!active) {
fNetworkActive = false;
LOCK(cs_vNodes);
// Close sockets to all nodes
for (CNode *pnode : vNodes) {
pnode->CloseSocketDisconnect();
}
} else {
fNetworkActive = true;
}
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;
semOutbound = nullptr;
semAddnode = nullptr;
nMaxConnections = 0;
nMaxOutbound = 0;
nMaxAddnode = 0;
nBestHeight = 0;
clientInterface = nullptr;
flagInterruptMsgProc = false;
}
NodeId CConnman::GetNewNodeId() {
return nLastNodeId.fetch_add(1, std::memory_order_relaxed);
}
bool CConnman::Start(CScheduler &scheduler, std::string &strNodeError,
Options connOptions) {
nTotalBytesRecv = 0;
nTotalBytesSent = 0;
nMaxOutboundTotalBytesSentInCycle = 0;
nMaxOutboundCycleStartTime = 0;
nRelevantServices = connOptions.nRelevantServices;
nLocalServices = connOptions.nLocalServices;
nMaxConnections = connOptions.nMaxConnections;
nMaxOutbound = std::min((connOptions.nMaxOutbound), nMaxConnections);
nMaxAddnode = connOptions.nMaxAddnode;
nMaxFeeler = connOptions.nMaxFeeler;
nSendBufferMaxSize = connOptions.nSendBufferMaxSize;
nReceiveFloodSize = connOptions.nReceiveFloodSize;
nMaxOutboundLimit = connOptions.nMaxOutboundLimit;
nMaxOutboundTimeframe = connOptions.nMaxOutboundTimeframe;
SetBestHeight(connOptions.nBestHeight);
clientInterface = connOptions.uiInterface;
if (clientInterface) {
clientInterface->InitMessage(_("Loading addresses..."));
}
// Load addresses from peers.dat
int64_t nStart = GetTimeMillis();
{
CAddrDB adb;
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;
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("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 = new CSemaphore(
std::min((nMaxOutbound + nMaxFeeler), nMaxConnections));
}
if (semAddnode == nullptr) {
// initialize semaphore
semAddnode = new CSemaphore(nMaxAddnode);
}
//
// Start threads
//
InterruptSocks5(false);
interruptNet.reset();
flagInterruptMsgProc = false;
{
std::unique_lock lock(mutexMsgProc);
fMsgProcWake = false;
}
// Send and receive from sockets, accept connections
threadSocketHandler = std::thread(
&TraceThread>, "net",
std::function(std::bind(&CConnman::ThreadSocketHandler, this)));
if (!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)));
// Initiate outbound connections unless connect=0
if (!mapMultiArgs.count("-connect") ||
mapMultiArgs.at("-connect").size() != 1 ||
mapMultiArgs.at("-connect")[0] != "0") {
threadOpenConnections =
std::thread(&TraceThread>, "opencon",
std::function(
std::bind(&CConnman::ThreadOpenConnections, this)));
}
// Process messages
threadMessageHandler =
std::thread(&TraceThread>, "msghand",
std::function(
std::bind(&CConnman::ThreadMessageHandler, this)));
// Dump network addresses
scheduler.scheduleEvery(boost::bind(&CConnman::DumpData, this),
DUMP_ADDRESSES_INTERVAL);
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();
delete semOutbound;
semOutbound = nullptr;
delete semAddnode;
semAddnode = nullptr;
}
void CConnman::DeleteNode(CNode *pnode) {
assert(pnode);
bool fUpdateConnectionTime = false;
GetNodeSignals().FinalizeNode(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::AddNewAddress(const CAddress &addr, const CAddress &addrFrom,
int64_t nTimePenalty) {
addrman.Add(addr, addrFrom, nTimePenalty);
}
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 (std::vector::const_iterator it = vAddedNodes.begin();
it != vAddedNodes.end(); ++it) {
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 (std::vector::const_iterator it = vNodes.begin();
it != vNodes.end(); ++it) {
if (flags & ((*it)->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->id) {
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;
}
unsigned int CConnman::GetSendBufferSize() const {
return nSendBufferMaxSize;
}
CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn,
int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn,
uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn,
const std::string &addrNameIn, bool fInboundIn)
: nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn),
fInbound(fInboundIn), id(idIn), nKeyedNetGroup(nKeyedNetGroupIn),
addrKnown(5000, 0.001), filterInventoryKnown(50000, 0.000001),
nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn),
nMyStartingHeight(nMyStartingHeightIn), nSendVersion(0) {
nServices = NODE_NONE;
nServicesExpected = 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;
fAddnode = false;
// set by version message
fClient = 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 = new CBloomFilter();
timeLastMempoolReq = 0;
nLastBlockTime = 0;
nLastTXTime = 0;
nPingNonceSent = 0;
nPingUsecStart = 0;
nPingUsecTime = 0;
fPingQueued = false;
- // set when etablishing connection
- fUsesCashMagic = true;
nMinPingUsecTime = std::numeric_limits::max();
minFeeFilter = Amount(0);
lastSentFeeFilter = Amount(0);
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("net", "Added connection to %s peer=%d\n", addrName, id);
} else {
LogPrint("net", "Added connection peer=%d\n", id);
}
}
CNode::~CNode() {
CloseSocket(hSocket);
if (pfilter) {
delete pfilter;
}
}
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("net", "askfor %s %d (%s) peer=%d\n", inv.ToString(),
nRequestTime,
DateTimeStrFormat("%H:%M:%S", 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("net", "sending %s (%d bytes) peer=%d\n",
SanitizeString(msg.command.c_str()), nMessageSize, pnode->id);
std::vector serializedHeader;
serializedHeader.reserve(CMessageHeader::HEADER_SIZE);
uint256 hash = Hash(msg.data.data(), msg.data.data() + nMessageSize);
- CMessageHeader hdr(pnode->GetMagic(Params()), msg.command.c_str(),
- nMessageSize);
+ CMessageHeader hdr(Params().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->id == 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[0], 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);
// sanitize comments per BIP-0014, format user agent and check total size
if (mapMultiArgs.count("-uacomment")) {
for (const std::string &cmt : mapMultiArgs.at("-uacomment")) {
if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT)) {
LogPrintf(
"User Agent comment (%s) contains unsafe characters. "
"We are going to use a sanitize version of the comment.\n",
cmt);
}
uacomments.push_back(cmt);
}
}
std::string subversion =
FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
if (subversion.size() > MAX_SUBVERSION_LENGTH) {
LogPrintf("Total length of network version string (%i) exceeds maximum "
"length (%i). Reduce the number or size of uacomments. "
"String has been resized to the max length allowed.\n",
subversion.size(), MAX_SUBVERSION_LENGTH);
subversion.resize(MAX_SUBVERSION_LENGTH - 2);
subversion.append(")/");
LogPrintf("Current network string has been set to: %s\n", subversion);
}
return subversion;
}
diff --git a/src/net.h b/src/net.h
index d9a090e0d..0dbae4e15 100644
--- a/src/net.h
+++ b/src/net.h
@@ -1,831 +1,823 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_NET_H
#define BITCOIN_NET_H
#include "addrdb.h"
#include "addrman.h"
#include "amount.h"
#include "bloom.h"
#include "chainparams.h"
#include "compat.h"
#include "hash.h"
#include "limitedmap.h"
#include "netaddress.h"
#include "protocol.h"
#include "random.h"
#include "streams.h"
#include "sync.h"
#include "threadinterrupt.h"
#include "uint256.h"
#include
#include
#include
#include
#include
#include
#ifndef WIN32
#include
#endif
#include
#include
class CAddrMan;
class Config;
class CNode;
class CScheduler;
namespace boost {
class thread_group;
} // namespace boost
/** Time between pings automatically sent out for latency probing and keepalive
* (in seconds). */
static const int PING_INTERVAL = 2 * 60;
/** Time after which to disconnect, after waiting for a ping response (or
* inactivity). */
static const int TIMEOUT_INTERVAL = 20 * 60;
/** Run the feeler connection loop once every 2 minutes or 120 seconds. **/
static const int FEELER_INTERVAL = 120;
/** The maximum number of entries in an 'inv' protocol message */
static const unsigned int MAX_INV_SZ = 50000;
/** The maximum number of new addresses to accumulate before announcing. */
static const unsigned int MAX_ADDR_TO_SEND = 1000;
/** Maximum length of incoming protocol messages (no message over 4 MB is
* currently acceptable). */
static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 32 * 1000 * 1000;
/** Maximum length of strSubVer in `version` message */
static const unsigned int MAX_SUBVERSION_LENGTH = 256;
/** Maximum number of automatic outgoing nodes */
static const int MAX_OUTBOUND_CONNECTIONS = 8;
/** Maximum number of addnode outgoing nodes */
static const int MAX_ADDNODE_CONNECTIONS = 8;
/** -listen default */
static const bool DEFAULT_LISTEN = true;
/** -upnp default */
#ifdef USE_UPNP
static const bool DEFAULT_UPNP = USE_UPNP;
#else
static const bool DEFAULT_UPNP = false;
#endif
/** The maximum number of entries in mapAskFor */
static const size_t MAPASKFOR_MAX_SZ = MAX_INV_SZ;
/** The maximum number of entries in setAskFor (larger due to getdata latency)*/
static const size_t SETASKFOR_MAX_SZ = 2 * MAX_INV_SZ;
/** The maximum number of peer connections to maintain. */
static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125;
/** The default for -maxuploadtarget. 0 = Unlimited */
static const uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0;
/** The default timeframe for -maxuploadtarget. 1 day. */
static const uint64_t MAX_UPLOAD_TIMEFRAME = 60 * 60 * 24;
/** Default for blocks only*/
static const bool DEFAULT_BLOCKSONLY = false;
// Force DNS seed use ahead of UAHF fork, to ensure peers are found
// as long as seeders are working.
// TODO: Change this back to false after the forked network is stable.
static const bool DEFAULT_FORCEDNSSEED = true;
static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000;
static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000;
static const ServiceFlags REQUIRED_SERVICES =
ServiceFlags(NODE_NETWORK | NODE_BITCOIN_CASH);
// Default 24-hour ban.
// NOTE: When adjusting this, update rpcnet:setban's help ("24h")
static const unsigned int DEFAULT_MISBEHAVING_BANTIME = 60 * 60 * 24;
typedef int64_t NodeId;
struct AddedNodeInfo {
std::string strAddedNode;
CService resolvedAddress;
bool fConnected;
bool fInbound;
};
class CTransaction;
class CNodeStats;
class CClientUIInterface;
struct CSerializedNetMsg {
CSerializedNetMsg() = default;
CSerializedNetMsg(CSerializedNetMsg &&) = default;
CSerializedNetMsg &operator=(CSerializedNetMsg &&) = default;
// No copying, only moves.
CSerializedNetMsg(const CSerializedNetMsg &msg) = delete;
CSerializedNetMsg &operator=(const CSerializedNetMsg &) = delete;
std::vector data;
std::string command;
};
class CConnman {
public:
enum NumConnections {
CONNECTIONS_NONE = 0,
CONNECTIONS_IN = (1U << 0),
CONNECTIONS_OUT = (1U << 1),
CONNECTIONS_ALL = (CONNECTIONS_IN | CONNECTIONS_OUT),
};
struct Options {
ServiceFlags nLocalServices = NODE_NONE;
ServiceFlags nRelevantServices = NODE_NONE;
int nMaxConnections = 0;
int nMaxOutbound = 0;
int nMaxAddnode = 0;
int nMaxFeeler = 0;
int nBestHeight = 0;
CClientUIInterface *uiInterface = nullptr;
unsigned int nSendBufferMaxSize = 0;
unsigned int nReceiveFloodSize = 0;
uint64_t nMaxOutboundTimeframe = 0;
uint64_t nMaxOutboundLimit = 0;
};
CConnman(const Config &configIn, uint64_t seed0, uint64_t seed1);
~CConnman();
bool Start(CScheduler &scheduler, std::string &strNodeError,
Options options);
void Stop();
void Interrupt();
bool BindListenPort(const CService &bindAddr, std::string &strError,
bool fWhitelisted = false);
bool GetNetworkActive() const { return fNetworkActive; };
void SetNetworkActive(bool active);
bool OpenNetworkConnection(const CAddress &addrConnect, bool fCountFailure,
CSemaphoreGrant *grantOutbound = nullptr,
const char *strDest = nullptr,
bool fOneShot = false, bool fFeeler = false,
bool fAddnode = false);
bool CheckIncomingNonce(uint64_t nonce);
bool ForNode(NodeId id, std::function func);
void PushMessage(CNode *pnode, CSerializedNetMsg &&msg);
template void ForEachNode(Callable &&func) {
LOCK(cs_vNodes);
for (auto &&node : vNodes) {
if (NodeFullyConnected(node)) func(node);
}
};
template void ForEachNode(Callable &&func) const {
LOCK(cs_vNodes);
for (auto &&node : vNodes) {
if (NodeFullyConnected(node)) func(node);
}
};
template
void ForEachNodeThen(Callable &&pre, CallableAfter &&post) {
LOCK(cs_vNodes);
for (auto &&node : vNodes) {
if (NodeFullyConnected(node)) pre(node);
}
post();
};
template
void ForEachNodeThen(Callable &&pre, CallableAfter &&post) const {
LOCK(cs_vNodes);
for (auto &&node : vNodes) {
if (NodeFullyConnected(node)) pre(node);
}
post();
};
// Addrman functions
size_t GetAddressCount() const;
void SetServices(const CService &addr, ServiceFlags nServices);
void MarkAddressGood(const CAddress &addr);
void AddNewAddress(const CAddress &addr, const CAddress &addrFrom,
int64_t nTimePenalty = 0);
void AddNewAddresses(const std::vector &vAddr,
const CAddress &addrFrom, int64_t nTimePenalty = 0);
std::vector GetAddresses();
void AddressCurrentlyConnected(const CService &addr);
// Denial-of-service detection/prevention. The idea is to detect peers that
// are behaving badly and disconnect/ban them, but do it in a
// one-coding-mistake-won't-shatter-the-entire-network way.
// IMPORTANT: There should be nothing I can give a node that it will forward
// on that will make that node's peers drop it. If there is, an attacker can
// isolate a node and/or try to split the network. Dropping a node for
// sending stuff that is invalid now but might be valid in a later version
// is also dangerous, because it can cause a network split between nodes
// running old code and nodes running new code.
void Ban(const CNetAddr &netAddr, const BanReason &reason,
int64_t bantimeoffset = 0, bool sinceUnixEpoch = false);
void Ban(const CSubNet &subNet, const BanReason &reason,
int64_t bantimeoffset = 0, bool sinceUnixEpoch = false);
// Needed for unit testing.
void ClearBanned();
bool IsBanned(CNetAddr ip);
bool IsBanned(CSubNet subnet);
bool Unban(const CNetAddr &ip);
bool Unban(const CSubNet &ip);
void GetBanned(banmap_t &banmap);
void SetBanned(const banmap_t &banmap);
void AddOneShot(const std::string &strDest);
bool AddNode(const std::string &node);
bool RemoveAddedNode(const std::string &node);
std::vector GetAddedNodeInfo();
size_t GetNodeCount(NumConnections num);
void GetNodeStats(std::vector &vstats);
bool DisconnectNode(const std::string &node);
bool DisconnectNode(NodeId id);
unsigned int GetSendBufferSize() const;
void AddWhitelistedRange(const CSubNet &subnet);
ServiceFlags GetLocalServices() const;
//! set the max outbound target in bytes.
void SetMaxOutboundTarget(uint64_t limit);
uint64_t GetMaxOutboundTarget();
//! set the timeframe for the max outbound target.
void SetMaxOutboundTimeframe(uint64_t timeframe);
uint64_t GetMaxOutboundTimeframe();
//! check if the outbound target is reached.
// If param historicalBlockServingLimit is set true, the function will
// response true if the limit for serving historical blocks has been
// reached.
bool OutboundTargetReached(bool historicalBlockServingLimit);
//! response the bytes left in the current max outbound cycle
// in case of no limit, it will always response 0
uint64_t GetOutboundTargetBytesLeft();
//! response the time in second left in the current max outbound cycle
// in case of no limit, it will always response 0
uint64_t GetMaxOutboundTimeLeftInCycle();
uint64_t GetTotalBytesRecv();
uint64_t GetTotalBytesSent();
void SetBestHeight(int height);
int GetBestHeight() const;
/** Get a unique deterministic randomizer. */
CSipHasher GetDeterministicRandomizer(uint64_t id) const;
unsigned int GetReceiveFloodSize() const;
void WakeMessageHandler();
private:
struct ListenSocket {
SOCKET socket;
bool whitelisted;
ListenSocket(SOCKET socket_, bool whitelisted_)
: socket(socket_), whitelisted(whitelisted_) {}
};
void ThreadOpenAddedConnections();
void ProcessOneShot();
void ThreadOpenConnections();
void ThreadMessageHandler();
void AcceptConnection(const ListenSocket &hListenSocket);
void ThreadSocketHandler();
void ThreadDNSAddressSeed();
uint64_t CalculateKeyedNetGroup(const CAddress &ad) const;
CNode *FindNode(const CNetAddr &ip);
CNode *FindNode(const CSubNet &subNet);
CNode *FindNode(const std::string &addrName);
CNode *FindNode(const CService &addr);
bool AttemptToEvictConnection();
CNode *ConnectNode(CAddress addrConnect, const char *pszDest,
bool fCountFailure);
bool IsWhitelistedRange(const CNetAddr &addr);
void DeleteNode(CNode *pnode);
NodeId GetNewNodeId();
size_t SocketSendData(CNode *pnode) const;
//! check is the banlist has unwritten changes
bool BannedSetIsDirty();
//! set the "dirty" flag for the banlist
void SetBannedSetDirty(bool dirty = true);
//! clean unused entries (if bantime has expired)
void SweepBanned();
void DumpAddresses();
void DumpData();
void DumpBanlist();
// Network stats
void RecordBytesRecv(uint64_t bytes);
void RecordBytesSent(uint64_t bytes);
// Whether the node should be passed out in ForEach* callbacks
static bool NodeFullyConnected(const CNode *pnode);
const Config *config;
// Network usage totals
CCriticalSection cs_totalBytesRecv;
CCriticalSection cs_totalBytesSent;
uint64_t nTotalBytesRecv;
uint64_t nTotalBytesSent;
// outbound limit & stats
uint64_t nMaxOutboundTotalBytesSentInCycle;
uint64_t nMaxOutboundCycleStartTime;
uint64_t nMaxOutboundLimit;
uint64_t nMaxOutboundTimeframe;
// Whitelisted ranges. Any node connecting from these is automatically
// whitelisted (as well as those connecting to whitelisted binds).
std::vector vWhitelistedRange;
CCriticalSection cs_vWhitelistedRange;
unsigned int nSendBufferMaxSize;
unsigned int nReceiveFloodSize;
std::vector vhListenSocket;
std::atomic fNetworkActive;
banmap_t setBanned;
CCriticalSection cs_setBanned;
bool setBannedIsDirty;
bool fAddressesInitialized;
CAddrMan addrman;
std::deque vOneShots;
CCriticalSection cs_vOneShots;
std::vector vAddedNodes;
CCriticalSection cs_vAddedNodes;
std::vector vNodes;
std::list vNodesDisconnected;
mutable CCriticalSection cs_vNodes;
std::atomic nLastNodeId;
/** Services this instance offers */
ServiceFlags nLocalServices;
/** Services this instance cares about */
ServiceFlags nRelevantServices;
CSemaphore *semOutbound;
CSemaphore *semAddnode;
int nMaxConnections;
int nMaxOutbound;
int nMaxAddnode;
int nMaxFeeler;
std::atomic nBestHeight;
CClientUIInterface *clientInterface;
/** SipHasher seeds for deterministic randomness */
const uint64_t nSeed0, nSeed1;
/** flag for waking the message processor. */
bool fMsgProcWake;
std::condition_variable condMsgProc;
std::mutex mutexMsgProc;
std::atomic flagInterruptMsgProc;
CThreadInterrupt interruptNet;
std::thread threadDNSAddressSeed;
std::thread threadSocketHandler;
std::thread threadOpenAddedConnections;
std::thread threadOpenConnections;
std::thread threadMessageHandler;
};
extern std::unique_ptr g_connman;
void Discover(boost::thread_group &threadGroup);
void MapPort(bool fUseUPnP);
unsigned short GetListenPort();
bool BindListenPort(const CService &bindAddr, std::string &strError,
bool fWhitelisted = false);
struct CombinerAll {
typedef bool result_type;
template bool operator()(I first, I last) const {
while (first != last) {
if (!(*first)) {
return false;
}
++first;
}
return true;
}
};
// Signals for message handling
struct CNodeSignals {
boost::signals2::signal &),
CombinerAll>
ProcessMessages;
boost::signals2::signal &),
CombinerAll>
SendMessages;
boost::signals2::signal
InitializeNode;
boost::signals2::signal FinalizeNode;
};
CNodeSignals &GetNodeSignals();
enum {
// unknown
LOCAL_NONE,
// address a local interface listens on
LOCAL_IF,
// address explicit bound to
LOCAL_BIND,
// address reported by UPnP
LOCAL_UPNP,
// address explicitly specified (-externalip=)
LOCAL_MANUAL,
LOCAL_MAX
};
bool IsPeerAddrLocalGood(CNode *pnode);
void AdvertiseLocal(CNode *pnode);
void SetLimited(enum Network net, bool fLimited = true);
bool IsLimited(enum Network net);
bool IsLimited(const CNetAddr &addr);
bool AddLocal(const CService &addr, int nScore = LOCAL_NONE);
bool AddLocal(const CNetAddr &addr, int nScore = LOCAL_NONE);
bool RemoveLocal(const CService &addr);
bool SeenLocal(const CService &addr);
bool IsLocal(const CService &addr);
bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr);
bool IsReachable(enum Network net);
bool IsReachable(const CNetAddr &addr);
CAddress GetLocalAddress(const CNetAddr *paddrPeer,
ServiceFlags nLocalServices);
extern bool fDiscover;
extern bool fListen;
extern bool fRelayTxes;
extern limitedmap mapAlreadyAskedFor;
struct LocalServiceInfo {
int nScore;
int nPort;
};
extern CCriticalSection cs_mapLocalHost;
extern std::map mapLocalHost;
// Command, total bytes
typedef std::map mapMsgCmdSize;
class CNodeStats {
public:
NodeId nodeid;
ServiceFlags nServices;
bool fRelayTxes;
int64_t nLastSend;
int64_t nLastRecv;
int64_t nTimeConnected;
int64_t nTimeOffset;
std::string addrName;
int nVersion;
std::string cleanSubVer;
bool fInbound;
bool fAddnode;
int nStartingHeight;
uint64_t nSendBytes;
mapMsgCmdSize mapSendBytesPerMsgCmd;
uint64_t nRecvBytes;
mapMsgCmdSize mapRecvBytesPerMsgCmd;
bool fWhitelisted;
- bool fUsesCashMagic;
double dPingTime;
double dPingWait;
double dMinPing;
std::string addrLocal;
CAddress addr;
};
class CNetMessage {
private:
mutable CHash256 hasher;
mutable uint256 data_hash;
public:
// Parsing header (false) or data (true)
bool in_data;
// Partially received header.
CDataStream hdrbuf;
// Complete header.
CMessageHeader hdr;
unsigned int nHdrPos;
// Received message data.
CDataStream vRecv;
unsigned int nDataPos;
// Time (in microseconds) of message receipt.
int64_t nTime;
CNetMessage(const CMessageHeader::MessageMagic &pchMessageStartIn,
int nTypeIn, int nVersionIn)
: hdrbuf(nTypeIn, nVersionIn), hdr(pchMessageStartIn),
vRecv(nTypeIn, nVersionIn) {
hdrbuf.resize(24);
in_data = false;
nHdrPos = 0;
nDataPos = 0;
nTime = 0;
}
bool complete() const {
if (!in_data) {
return false;
}
return (hdr.nMessageSize == nDataPos);
}
const uint256 &GetMessageHash() const;
void SetVersion(int nVersionIn) {
hdrbuf.SetVersion(nVersionIn);
vRecv.SetVersion(nVersionIn);
}
int readHeader(const char *pch, unsigned int nBytes);
int readData(const char *pch, unsigned int nBytes);
};
/** Information about a peer */
class CNode {
friend class CConnman;
public:
// socket
std::atomic nServices;
ServiceFlags nServicesExpected;
SOCKET hSocket;
// Total size of all vSendMsg entries.
size_t nSendSize;
// Offset inside the first vSendMsg already sent.
size_t nSendOffset;
uint64_t nSendBytes;
std::deque> vSendMsg;
CCriticalSection cs_vSend;
CCriticalSection cs_hSocket;
CCriticalSection cs_vRecv;
CCriticalSection cs_vProcessMsg;
std::list vProcessMsg;
size_t nProcessQueueSize;
CCriticalSection cs_sendProcessing;
std::deque vRecvGetData;
uint64_t nRecvBytes;
std::atomic nRecvVersion;
std::atomic nLastSend;
std::atomic nLastRecv;
const int64_t nTimeConnected;
std::atomic nTimeOffset;
const CAddress addr;
std::atomic nVersion;
// strSubVer is whatever byte array we read from the wire. However, this
// field is intended to be printed out, displayed to humans in various forms
// and so on. So we sanitize it and store the sanitized version in
// cleanSubVer. The original should be used when dealing with the network or
// wire types and the cleaned string used when displayed or logged.
std::string strSubVer, cleanSubVer;
// Used for both cleanSubVer and strSubVer.
CCriticalSection cs_SubVer;
// This peer can bypass DoS banning.
bool fWhitelisted;
// If true this node is being used as a short lived feeler.
bool fFeeler;
bool fOneShot;
bool fAddnode;
bool fClient;
const bool fInbound;
std::atomic_bool fSuccessfullyConnected;
std::atomic_bool fDisconnect;
// We use fRelayTxes for two purposes -
// a) it allows us to not relay tx invs before receiving the peer's version
// message.
// b) the peer may tell us in its version message that we should not relay
// tx invs unless it loads a bloom filter.
// protected by cs_filter
bool fRelayTxes;
bool fSentAddr;
CSemaphoreGrant grantOutbound;
CCriticalSection cs_filter;
CBloomFilter *pfilter;
std::atomic nRefCount;
const NodeId id;
const uint64_t nKeyedNetGroup;
std::atomic_bool fPauseRecv;
std::atomic_bool fPauseSend;
protected:
mapMsgCmdSize mapSendBytesPerMsgCmd;
mapMsgCmdSize mapRecvBytesPerMsgCmd;
public:
uint256 hashContinue;
std::atomic nStartingHeight;
// flood relay
std::vector vAddrToSend;
CRollingBloomFilter addrKnown;
bool fGetAddr;
std::set setKnown;
int64_t nNextAddrSend;
int64_t nNextLocalAddrSend;
// Inventory based relay.
CRollingBloomFilter filterInventoryKnown;
// Set of transaction ids we still have to announce. They are sorted by the
// mempool before relay, so the order is not important.
std::set setInventoryTxToSend;
// List of block ids we still have announce. There is no final sorting
// before sending, as they are always sent immediately and in the order
// requested.
std::vector vInventoryBlockToSend;
CCriticalSection cs_inventory;
std::set setAskFor;
std::multimap mapAskFor;
int64_t nNextInvSend;
// Used for headers announcements - unfiltered blocks to relay. Also
// protected by cs_inventory.
std::vector vBlockHashesToAnnounce;
// Used for BIP35 mempool sending, also protected by cs_inventory.
bool fSendMempool;
// Last time a "MEMPOOL" request was serviced.
std::atomic timeLastMempoolReq;
// Block and TXN accept times
std::atomic nLastBlockTime;
std::atomic nLastTXTime;
// Ping time measurement:
// The pong reply we're expecting, or 0 if no pong expected.
std::atomic nPingNonceSent;
// Time (in usec) the last ping was sent, or 0 if no ping was ever sent.
std::atomic nPingUsecStart;
// Last measured round-trip time.
std::atomic nPingUsecTime;
// Best measured round-trip time.
std::atomic nMinPingUsecTime;
// Whether a ping is requested.
std::atomic fPingQueued;
- // Whether the node uses the bitcoin cash magic to communicate.
- std::atomic fUsesCashMagic;
// Minimum fee rate with which to filter inv's to this node
Amount minFeeFilter;
CCriticalSection cs_feeFilter;
Amount lastSentFeeFilter;
int64_t nextSendTimeFeeFilter;
CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn,
SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn,
uint64_t nLocalHostNonceIn, const std::string &addrNameIn = "",
bool fInboundIn = false);
~CNode();
private:
CNode(const CNode &);
void operator=(const CNode &);
const uint64_t nLocalHostNonce;
// Services offered to this peer
const ServiceFlags nLocalServices;
const int nMyStartingHeight;
int nSendVersion;
// Used only by SocketHandler thread.
std::list vRecvMsg;
mutable CCriticalSection cs_addrName;
std::string addrName;
CService addrLocal;
mutable CCriticalSection cs_addrLocal;
public:
NodeId GetId() const { return id; }
uint64_t GetLocalNonce() const { return nLocalHostNonce; }
int GetMyStartingHeight() const { return nMyStartingHeight; }
int GetRefCount() {
assert(nRefCount >= 0);
return nRefCount;
}
bool ReceiveMsgBytes(const char *pch, unsigned int nBytes, bool &complete);
void SetRecvVersion(int nVersionIn) { nRecvVersion = nVersionIn; }
int GetRecvVersion() { return nRecvVersion; }
void SetSendVersion(int nVersionIn);
int GetSendVersion() const;
- const CMessageHeader::MessageMagic &
- GetMagic(const CChainParams ¶ms) const {
- return fUsesCashMagic ? params.NetMagic() : params.DiskMagic();
- }
-
CService GetAddrLocal() const;
//! May not be called more than once
void SetAddrLocal(const CService &addrLocalIn);
CNode *AddRef() {
nRefCount++;
return this;
}
void Release() { nRefCount--; }
void AddAddressKnown(const CAddress &_addr) {
addrKnown.insert(_addr.GetKey());
}
void PushAddress(const CAddress &_addr, FastRandomContext &insecure_rand) {
// Known checking here is only to save space from duplicates.
// SendMessages will filter it again for knowns that were added
// after addresses were pushed.
if (_addr.IsValid() && !addrKnown.contains(_addr.GetKey())) {
if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) {
vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] =
_addr;
} else {
vAddrToSend.push_back(_addr);
}
}
}
void AddInventoryKnown(const CInv &inv) {
LOCK(cs_inventory);
filterInventoryKnown.insert(inv.hash);
}
void PushInventory(const CInv &inv) {
LOCK(cs_inventory);
if (inv.type == MSG_TX) {
if (!filterInventoryKnown.contains(inv.hash)) {
setInventoryTxToSend.insert(inv.hash);
}
} else if (inv.type == MSG_BLOCK) {
vInventoryBlockToSend.push_back(inv.hash);
}
}
void PushBlockHash(const uint256 &hash) {
LOCK(cs_inventory);
vBlockHashesToAnnounce.push_back(hash);
}
void AskFor(const CInv &inv);
void CloseSocketDisconnect();
void copyStats(CNodeStats &stats);
ServiceFlags GetLocalServices() const { return nLocalServices; }
std::string GetAddrName() const;
//! Sets the addrName only if it was not previously set
void MaybeSetAddrName(const std::string &addrNameIn);
};
/**
* Return a timestamp in the future (in microseconds) for exponentially
* distributed events.
*/
int64_t PoissonNextSend(int64_t nNow, int average_interval_seconds);
std::string getSubVersionEB(uint64_t MaxBlockSize);
std::string userAgent(const Config &config);
#endif // BITCOIN_NET_H
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 9432b0931..d9303c3d8 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1,3846 +1,3835 @@
// 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.
#include "net_processing.h"
#include "addrman.h"
#include "arith_uint256.h"
#include "blockencodings.h"
#include "chainparams.h"
#include "config.h"
#include "consensus/validation.h"
#include "hash.h"
#include "init.h"
#include "merkleblock.h"
#include "net.h"
#include "netbase.h"
#include "netmessagemaker.h"
#include "policy/fees.h"
#include "policy/policy.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "random.h"
#include "tinyformat.h"
#include "txmempool.h"
#include "ui_interface.h"
#include "util.h"
#include "utilmoneystr.h"
#include "utilstrencodings.h"
#include "validation.h"
#include "validationinterface.h"
#include
#include
#if defined(NDEBUG)
#error "Bitcoin cannot be compiled without assertions."
#endif
// Used only to inform the wallet of when we last received a block.
std::atomic nTimeBestReceived(0);
struct IteratorComparator {
template bool operator()(const I &a, const I &b) {
return &(*a) < &(*b);
}
};
struct COrphanTx {
// When modifying, adapt the copy of this definition in tests/DoS_tests.
CTransactionRef tx;
NodeId fromPeer;
int64_t nTimeExpire;
};
std::map mapOrphanTransactions GUARDED_BY(cs_main);
std::map::iterator, IteratorComparator>>
mapOrphanTransactionsByPrev GUARDED_BY(cs_main);
void EraseOrphansFor(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
static size_t vExtraTxnForCompactIt = 0;
static std::vector>
vExtraTxnForCompact GUARDED_BY(cs_main);
// SHA256("main address relay")[0:8]
static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL;
// Internal stuff
namespace {
/** Number of nodes with fSyncStarted. */
int nSyncStarted = 0;
/**
* Sources of received blocks, saved to be able to send them reject messages or
* ban them when processing happens afterwards. Protected by cs_main.
* Set mapBlockSource[hash].second to false if the node should not be punished
* if the block is invalid.
*/
std::map> mapBlockSource;
/**
* Filter for transactions that were recently rejected by AcceptToMemoryPool.
* These are not rerequested until the chain tip changes, at which point the
* entire filter is reset. Protected by cs_main.
*
* Without this filter we'd be re-requesting txs from each of our peers,
* increasing bandwidth consumption considerably. For instance, with 100 peers,
* half of which relay a tx we don't accept, that might be a 50x bandwidth
* increase. A flooding attacker attempting to roll-over the filter using
* minimum-sized, 60byte, transactions might manage to send 1000/sec if we have
* fast peers, so we pick 120,000 to give our peers a two minute window to send
* invs to us.
*
* Decreasing the false positive rate is fairly cheap, so we pick one in a
* million to make it highly unlikely for users to have issues with this filter.
*
* Memory used: 1.3 MB
*/
std::unique_ptr recentRejects;
uint256 hashRecentRejectsChainTip;
/** Blocks that are in flight, and that are in the queue to be downloaded.
* Protected by cs_main. */
struct QueuedBlock {
uint256 hash;
//!< Optional.
const CBlockIndex *pindex;
//!< Whether this block has validated headers at the time of request.
bool fValidatedHeaders;
//!< Optional, used for CMPCTBLOCK downloads
std::unique_ptr partialBlock;
};
std::map::iterator>>
mapBlocksInFlight;
/** Stack of nodes which we have set to announce using compact blocks */
std::list lNodesAnnouncingHeaderAndIDs;
/** Number of preferable block download peers. */
int nPreferredDownload = 0;
/** Number of peers from which we're downloading blocks. */
int nPeersWithValidatedDownloads = 0;
/** Relay map, protected by cs_main. */
typedef std::map MapRelay;
MapRelay mapRelay;
/** Expiration-time ordered list of (expire time, relay map entry) pairs,
* protected by cs_main). */
std::deque> vRelayExpiration;
} // namespace
//////////////////////////////////////////////////////////////////////////////
//
// Registration of network node signals.
//
namespace {
struct CBlockReject {
uint8_t chRejectCode;
std::string strRejectReason;
uint256 hashBlock;
};
/**
* Maintain validation-specific state about nodes, protected by cs_main, instead
* by CNode's own locks. This simplifies asynchronous operation, where
* processing of incoming data is done after the ProcessMessage call returns,
* and we're no longer holding the node's locks.
*/
struct CNodeState {
//! The peer's address
const CService address;
//! Whether we have a fully established connection.
bool fCurrentlyConnected;
//! Accumulated misbehaviour score for this peer.
int nMisbehavior;
//! Whether this peer should be disconnected and banned (unless
//! whitelisted).
bool fShouldBan;
//! String name of this peer (debugging/logging purposes).
const std::string name;
//! List of asynchronously-determined block rejections to notify this peer
//! about.
std::vector rejects;
//! The best known block we know this peer has announced.
const CBlockIndex *pindexBestKnownBlock;
//! The hash of the last unknown block this peer has announced.
uint256 hashLastUnknownBlock;
//! The last full block we both have.
const CBlockIndex *pindexLastCommonBlock;
//! The best header we have sent our peer.
const CBlockIndex *pindexBestHeaderSent;
//! Length of current-streak of unconnecting headers announcements
int nUnconnectingHeaders;
//! Whether we've started headers synchronization with this peer.
bool fSyncStarted;
//! Since when we're stalling block download progress (in microseconds), or
//! 0.
int64_t nStallingSince;
std::list vBlocksInFlight;
//! When the first entry in vBlocksInFlight started downloading. Don't care
//! when vBlocksInFlight is empty.
int64_t nDownloadingSince;
int nBlocksInFlight;
int nBlocksInFlightValidHeaders;
//! Whether we consider this a preferred download peer.
bool fPreferredDownload;
//! Whether this peer wants invs or headers (when possible) for block
//! announcements.
bool fPreferHeaders;
//! Whether this peer wants invs or cmpctblocks (when possible) for block
//! announcements.
bool fPreferHeaderAndIDs;
/**
* Whether this peer will send us cmpctblocks if we request them.
* This is not used to gate request logic, as we really only care about
* fSupportsDesiredCmpctVersion, but is used as a flag to "lock in" the
* version of compact blocks we send.
*/
bool fProvidesHeaderAndIDs;
/**
* If we've announced NODE_WITNESS to this peer: whether the peer sends
* witnesses in cmpctblocks/blocktxns, otherwise: whether this peer sends
* non-witnesses in cmpctblocks/blocktxns.
*/
bool fSupportsDesiredCmpctVersion;
CNodeState(CAddress addrIn, std::string addrNameIn)
: address(addrIn), name(addrNameIn) {
fCurrentlyConnected = false;
nMisbehavior = 0;
fShouldBan = false;
pindexBestKnownBlock = nullptr;
hashLastUnknownBlock.SetNull();
pindexLastCommonBlock = nullptr;
pindexBestHeaderSent = nullptr;
nUnconnectingHeaders = 0;
fSyncStarted = false;
nStallingSince = 0;
nDownloadingSince = 0;
nBlocksInFlight = 0;
nBlocksInFlightValidHeaders = 0;
fPreferredDownload = false;
fPreferHeaders = false;
fPreferHeaderAndIDs = false;
fProvidesHeaderAndIDs = false;
fSupportsDesiredCmpctVersion = false;
}
};
/** Map maintaining per-node state. Requires cs_main. */
std::map mapNodeState;
// Requires cs_main.
CNodeState *State(NodeId pnode) {
std::map::iterator it = mapNodeState.find(pnode);
if (it == mapNodeState.end()) {
return nullptr;
}
return &it->second;
}
void UpdatePreferredDownload(CNode *node, CNodeState *state) {
nPreferredDownload -= state->fPreferredDownload;
// Whether this node should be marked as a preferred download node.
state->fPreferredDownload = (!node->fInbound || node->fWhitelisted) &&
!node->fOneShot && !node->fClient;
nPreferredDownload += state->fPreferredDownload;
}
void PushNodeVersion(const Config &config, CNode *pnode, CConnman &connman,
int64_t nTime) {
ServiceFlags nLocalNodeServices = pnode->GetLocalServices();
uint64_t nonce = pnode->GetLocalNonce();
int nNodeStartingHeight = pnode->GetMyStartingHeight();
NodeId nodeid = pnode->GetId();
CAddress addr = pnode->addr;
CAddress addrYou = (addr.IsRoutable() && !IsProxy(addr)
? addr
: CAddress(CService(), addr.nServices));
CAddress addrMe = CAddress(CService(), nLocalNodeServices);
connman.PushMessage(pnode,
CNetMsgMaker(INIT_PROTO_VERSION)
.Make(NetMsgType::VERSION, PROTOCOL_VERSION,
(uint64_t)nLocalNodeServices, nTime, addrYou,
addrMe, nonce, userAgent(config),
nNodeStartingHeight, ::fRelayTxes));
if (fLogIPs) {
LogPrint("net", "send version message: version %d, blocks=%d, us=%s, "
"them=%s, peer=%d\n",
PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(),
addrYou.ToString(), nodeid);
} else {
LogPrint(
"net",
"send version message: version %d, blocks=%d, us=%s, peer=%d\n",
PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), nodeid);
}
}
void InitializeNode(const Config &config, CNode *pnode, CConnman &connman) {
CAddress addr = pnode->addr;
std::string addrName = pnode->GetAddrName();
NodeId nodeid = pnode->GetId();
{
LOCK(cs_main);
mapNodeState.emplace_hint(
mapNodeState.end(), std::piecewise_construct,
std::forward_as_tuple(nodeid),
std::forward_as_tuple(addr, std::move(addrName)));
}
if (!pnode->fInbound) {
PushNodeVersion(config, pnode, connman, GetTime());
}
}
void FinalizeNode(NodeId nodeid, bool &fUpdateConnectionTime) {
fUpdateConnectionTime = false;
LOCK(cs_main);
CNodeState *state = State(nodeid);
if (state->fSyncStarted) {
nSyncStarted--;
}
if (state->nMisbehavior == 0 && state->fCurrentlyConnected) {
fUpdateConnectionTime = true;
}
for (const QueuedBlock &entry : state->vBlocksInFlight) {
mapBlocksInFlight.erase(entry.hash);
}
// Get rid of stale mapBlockSource entries for this peer as they may leak
// if we don't clean them up (I saw on the order of ~100 stale entries on
// a full resynch in my testing -- these entries stay forever).
// Performance note: most of the time mapBlockSource has 0 or 1 entries.
// During synch of blockchain it may end up with as many as 1000 entries,
// which still only takes ~1ms to iterate through on even old hardware.
// So this memleak cleanup is not expensive and worth doing since even
// small leaks are bad. :)
for (auto it = mapBlockSource.begin(); it != mapBlockSource.end(); /*NA*/) {
if (it->second.first == nodeid) {
mapBlockSource.erase(it++);
} else {
++it;
}
}
EraseOrphansFor(nodeid);
nPreferredDownload -= state->fPreferredDownload;
nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0);
assert(nPeersWithValidatedDownloads >= 0);
mapNodeState.erase(nodeid);
if (mapNodeState.empty()) {
// Do a consistency check after the last peer is removed.
assert(mapBlocksInFlight.empty());
assert(nPreferredDownload == 0);
assert(nPeersWithValidatedDownloads == 0);
}
}
// Requires cs_main.
// Returns a bool indicating whether we requested this block.
// Also used if a block was /not/ received and timed out or started with another
// peer.
bool MarkBlockAsReceived(const uint256 &hash) {
std::map::iterator>>::iterator
itInFlight = mapBlocksInFlight.find(hash);
if (itInFlight != mapBlocksInFlight.end()) {
CNodeState *state = State(itInFlight->second.first);
state->nBlocksInFlightValidHeaders -=
itInFlight->second.second->fValidatedHeaders;
if (state->nBlocksInFlightValidHeaders == 0 &&
itInFlight->second.second->fValidatedHeaders) {
// Last validated block on the queue was received.
nPeersWithValidatedDownloads--;
}
if (state->vBlocksInFlight.begin() == itInFlight->second.second) {
// First block on the queue was received, update the start download
// time for the next one
state->nDownloadingSince =
std::max(state->nDownloadingSince, GetTimeMicros());
}
state->vBlocksInFlight.erase(itInFlight->second.second);
state->nBlocksInFlight--;
state->nStallingSince = 0;
mapBlocksInFlight.erase(itInFlight);
return true;
}
return false;
}
// Requires cs_main.
// returns false, still setting pit, if the block was already in flight from the
// same peer pit will only be valid as long as the same cs_main lock is being
// held.
static bool
MarkBlockAsInFlight(const Config &config, NodeId nodeid, const uint256 &hash,
const Consensus::Params &consensusParams,
const CBlockIndex *pindex = nullptr,
std::list::iterator **pit = nullptr) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
// Short-circuit most stuff in case its from the same node.
std::map::iterator>>::iterator
itInFlight = mapBlocksInFlight.find(hash);
if (itInFlight != mapBlocksInFlight.end() &&
itInFlight->second.first == nodeid) {
*pit = &itInFlight->second.second;
return false;
}
// Make sure it's not listed somewhere already.
MarkBlockAsReceived(hash);
std::list::iterator it = state->vBlocksInFlight.insert(
state->vBlocksInFlight.end(),
{hash, pindex, pindex != nullptr,
std::unique_ptr(
pit ? new PartiallyDownloadedBlock(config, &mempool) : nullptr)});
state->nBlocksInFlight++;
state->nBlocksInFlightValidHeaders += it->fValidatedHeaders;
if (state->nBlocksInFlight == 1) {
// We're starting a block download (batch) from this peer.
state->nDownloadingSince = GetTimeMicros();
}
if (state->nBlocksInFlightValidHeaders == 1 && pindex != nullptr) {
nPeersWithValidatedDownloads++;
}
itInFlight = mapBlocksInFlight
.insert(std::make_pair(hash, std::make_pair(nodeid, it)))
.first;
if (pit) {
*pit = &itInFlight->second.second;
}
return true;
}
/** Check whether the last unknown block a peer advertised is not yet known. */
void ProcessBlockAvailability(NodeId nodeid) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
if (!state->hashLastUnknownBlock.IsNull()) {
BlockMap::iterator itOld =
mapBlockIndex.find(state->hashLastUnknownBlock);
if (itOld != mapBlockIndex.end() && itOld->second->nChainWork > 0) {
if (state->pindexBestKnownBlock == nullptr ||
itOld->second->nChainWork >=
state->pindexBestKnownBlock->nChainWork) {
state->pindexBestKnownBlock = itOld->second;
}
state->hashLastUnknownBlock.SetNull();
}
}
}
/** Update tracking information about which blocks a peer is assumed to have. */
void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
ProcessBlockAvailability(nodeid);
BlockMap::iterator it = mapBlockIndex.find(hash);
if (it != mapBlockIndex.end() && it->second->nChainWork > 0) {
// An actually better block was announced.
if (state->pindexBestKnownBlock == nullptr ||
it->second->nChainWork >= state->pindexBestKnownBlock->nChainWork) {
state->pindexBestKnownBlock = it->second;
}
} else {
// An unknown block was announced; just assume that the latest one is
// the best one.
state->hashLastUnknownBlock = hash;
}
}
void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman &connman) {
AssertLockHeld(cs_main);
CNodeState *nodestate = State(nodeid);
if (!nodestate) {
LogPrint("net", "node state unavailable: peer=%d\n", nodeid);
return;
}
if (!nodestate->fProvidesHeaderAndIDs) {
return;
}
for (std::list::iterator it = lNodesAnnouncingHeaderAndIDs.begin();
it != lNodesAnnouncingHeaderAndIDs.end(); it++) {
if (*it == nodeid) {
lNodesAnnouncingHeaderAndIDs.erase(it);
lNodesAnnouncingHeaderAndIDs.push_back(nodeid);
return;
}
}
connman.ForNode(nodeid, [&connman](CNode *pfrom) {
bool fAnnounceUsingCMPCTBLOCK = false;
uint64_t nCMPCTBLOCKVersion = 1;
if (lNodesAnnouncingHeaderAndIDs.size() >= 3) {
// As per BIP152, we only get 3 of our peers to announce
// blocks using compact encodings.
connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(),
[&connman, fAnnounceUsingCMPCTBLOCK,
nCMPCTBLOCKVersion](CNode *pnodeStop) {
connman.PushMessage(
pnodeStop,
CNetMsgMaker(pnodeStop->GetSendVersion())
.Make(NetMsgType::SENDCMPCT,
fAnnounceUsingCMPCTBLOCK,
nCMPCTBLOCKVersion));
return true;
});
lNodesAnnouncingHeaderAndIDs.pop_front();
}
fAnnounceUsingCMPCTBLOCK = true;
connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion())
.Make(NetMsgType::SENDCMPCT,
fAnnounceUsingCMPCTBLOCK,
nCMPCTBLOCKVersion));
lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId());
return true;
});
}
// Requires cs_main
bool CanDirectFetch(const Consensus::Params &consensusParams) {
return chainActive.Tip()->GetBlockTime() >
GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20;
}
// Requires cs_main
bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) {
if (state->pindexBestKnownBlock &&
pindex == state->pindexBestKnownBlock->GetAncestor(pindex->nHeight)) {
return true;
}
if (state->pindexBestHeaderSent &&
pindex == state->pindexBestHeaderSent->GetAncestor(pindex->nHeight)) {
return true;
}
return false;
}
/**
* Find the last common ancestor two blocks have.
* Both pa and pb must be non null.
*/
const CBlockIndex *LastCommonAncestor(const CBlockIndex *pa,
const CBlockIndex *pb) {
if (pa->nHeight > pb->nHeight) {
pa = pa->GetAncestor(pb->nHeight);
} else if (pb->nHeight > pa->nHeight) {
pb = pb->GetAncestor(pa->nHeight);
}
while (pa != pb && pa && pb) {
pa = pa->pprev;
pb = pb->pprev;
}
// Eventually all chain branches meet at the genesis block.
assert(pa == pb);
return pa;
}
/** Update pindexLastCommonBlock and add not-in-flight missing successors to
* vBlocks, until it has at most count entries. */
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count,
std::vector &vBlocks,
NodeId &nodeStaller,
const Consensus::Params &consensusParams) {
if (count == 0) {
return;
}
vBlocks.reserve(vBlocks.size() + count);
CNodeState *state = State(nodeid);
assert(state != nullptr);
// Make sure pindexBestKnownBlock is up to date, we'll need it.
ProcessBlockAvailability(nodeid);
if (state->pindexBestKnownBlock == nullptr ||
state->pindexBestKnownBlock->nChainWork <
chainActive.Tip()->nChainWork) {
// This peer has nothing interesting.
return;
}
if (state->pindexLastCommonBlock == nullptr) {
// Bootstrap quickly by guessing a parent of our best tip is the forking
// point. Guessing wrong in either direction is not a problem.
state->pindexLastCommonBlock = chainActive[std::min(
state->pindexBestKnownBlock->nHeight, chainActive.Height())];
}
// If the peer reorganized, our previous pindexLastCommonBlock may not be an
// ancestor of its current tip anymore. Go back enough to fix that.
state->pindexLastCommonBlock = LastCommonAncestor(
state->pindexLastCommonBlock, state->pindexBestKnownBlock);
if (state->pindexLastCommonBlock == state->pindexBestKnownBlock) {
return;
}
std::vector vToFetch;
const CBlockIndex *pindexWalk = state->pindexLastCommonBlock;
// Never fetch further than the best block we know the peer has, or more
// than BLOCK_DOWNLOAD_WINDOW + 1 beyond the last linked block we have in
// common with this peer. The +1 is so we can detect stalling, namely if we
// would be able to download that next block if the window were 1 larger.
int nWindowEnd =
state->pindexLastCommonBlock->nHeight + BLOCK_DOWNLOAD_WINDOW;
int nMaxHeight =
std::min(state->pindexBestKnownBlock->nHeight, nWindowEnd + 1);
NodeId waitingfor = -1;
while (pindexWalk->nHeight < nMaxHeight) {
// Read up to 128 (or more, if more blocks than that are needed)
// successors of pindexWalk (towards pindexBestKnownBlock) into
// vToFetch. We fetch 128, because CBlockIndex::GetAncestor may be as
// expensive as iterating over ~100 CBlockIndex* entries anyway.
int nToFetch = std::min(nMaxHeight - pindexWalk->nHeight,
std::max(count - vBlocks.size(), 128));
vToFetch.resize(nToFetch);
pindexWalk = state->pindexBestKnownBlock->GetAncestor(
pindexWalk->nHeight + nToFetch);
vToFetch[nToFetch - 1] = pindexWalk;
for (unsigned int i = nToFetch - 1; i > 0; i--) {
vToFetch[i - 1] = vToFetch[i]->pprev;
}
// Iterate over those blocks in vToFetch (in forward direction), adding
// the ones that are not yet downloaded and not in flight to vBlocks. In
// the mean time, update pindexLastCommonBlock as long as all ancestors
// are already downloaded, or if it's already part of our chain (and
// therefore don't need it even if pruned).
for (const CBlockIndex *pindex : vToFetch) {
if (!pindex->IsValid(BLOCK_VALID_TREE)) {
// We consider the chain that this peer is on invalid.
return;
}
if (pindex->nStatus & BLOCK_HAVE_DATA ||
chainActive.Contains(pindex)) {
if (pindex->nChainTx) {
state->pindexLastCommonBlock = pindex;
}
} else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) {
// The block is not already downloaded, and not yet in flight.
if (pindex->nHeight > nWindowEnd) {
// We reached the end of the window.
if (vBlocks.size() == 0 && waitingfor != nodeid) {
// We aren't able to fetch anything, but we would be if
// the download window was one larger.
nodeStaller = waitingfor;
}
return;
}
vBlocks.push_back(pindex);
if (vBlocks.size() == count) {
return;
}
} else if (waitingfor == -1) {
// This is the first already-in-flight block.
waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first;
}
}
}
}
} // namespace
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) {
LOCK(cs_main);
CNodeState *state = State(nodeid);
if (state == nullptr) {
return false;
}
stats.nMisbehavior = state->nMisbehavior;
stats.nSyncHeight =
state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
stats.nCommonHeight = state->pindexLastCommonBlock
? state->pindexLastCommonBlock->nHeight
: -1;
for (const QueuedBlock &queue : state->vBlocksInFlight) {
if (queue.pindex) {
stats.vHeightInFlight.push_back(queue.pindex->nHeight);
}
}
return true;
}
void RegisterNodeSignals(CNodeSignals &nodeSignals) {
nodeSignals.ProcessMessages.connect(&ProcessMessages);
nodeSignals.SendMessages.connect(&SendMessages);
nodeSignals.InitializeNode.connect(&InitializeNode);
nodeSignals.FinalizeNode.connect(&FinalizeNode);
}
void UnregisterNodeSignals(CNodeSignals &nodeSignals) {
nodeSignals.ProcessMessages.disconnect(&ProcessMessages);
nodeSignals.SendMessages.disconnect(&SendMessages);
nodeSignals.InitializeNode.disconnect(&InitializeNode);
nodeSignals.FinalizeNode.disconnect(&FinalizeNode);
}
//////////////////////////////////////////////////////////////////////////////
//
// mapOrphanTransactions
//
void AddToCompactExtraTransactions(const CTransactionRef &tx) {
size_t max_extra_txn = GetArg("-blockreconstructionextratxn",
DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN);
if (max_extra_txn <= 0) {
return;
}
if (!vExtraTxnForCompact.size()) {
vExtraTxnForCompact.resize(max_extra_txn);
}
vExtraTxnForCompact[vExtraTxnForCompactIt] =
std::make_pair(tx->GetHash(), tx);
vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn;
}
bool AddOrphanTx(const CTransactionRef &tx, NodeId peer)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
const uint256 &txid = tx->GetId();
if (mapOrphanTransactions.count(txid)) {
return false;
}
// Ignore big transactions, to avoid a send-big-orphans memory exhaustion
// attack. If a peer has a legitimate large transaction with a missing
// parent then we assume it will rebroadcast it later, after the parent
// transaction(s) have been mined or received.
// 100 orphans, each of which is at most 99,999 bytes big is at most 10
// megabytes of orphans and somewhat more byprev index (in the worst case):
unsigned int sz = GetTransactionSize(*tx);
if (sz >= MAX_STANDARD_TX_SIZE) {
LogPrint("mempool", "ignoring large orphan tx (size: %u, hash: %s)\n",
sz, txid.ToString());
return false;
}
auto ret = mapOrphanTransactions.emplace(
txid, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME});
assert(ret.second);
for (const CTxIn &txin : tx->vin) {
mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first);
}
AddToCompactExtraTransactions(tx);
LogPrint("mempool", "stored orphan tx %s (mapsz %u outsz %u)\n",
txid.ToString(), mapOrphanTransactions.size(),
mapOrphanTransactionsByPrev.size());
return true;
}
static int EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
std::map::iterator it =
mapOrphanTransactions.find(hash);
if (it == mapOrphanTransactions.end()) {
return 0;
}
for (const CTxIn &txin : it->second.tx->vin) {
auto itPrev = mapOrphanTransactionsByPrev.find(txin.prevout);
if (itPrev == mapOrphanTransactionsByPrev.end()) {
continue;
}
itPrev->second.erase(it);
if (itPrev->second.empty()) {
mapOrphanTransactionsByPrev.erase(itPrev);
}
}
mapOrphanTransactions.erase(it);
return 1;
}
void EraseOrphansFor(NodeId peer) {
int nErased = 0;
std::map::iterator iter = mapOrphanTransactions.begin();
while (iter != mapOrphanTransactions.end()) {
// Increment to avoid iterator becoming invalid.
std::map::iterator maybeErase = iter++;
if (maybeErase->second.fromPeer == peer) {
nErased += EraseOrphanTx(maybeErase->second.tx->GetId());
}
}
if (nErased > 0) {
LogPrint("mempool", "Erased %d orphan tx from peer=%d\n", nErased,
peer);
}
}
unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
unsigned int nEvicted = 0;
static int64_t nNextSweep;
int64_t nNow = GetTime();
if (nNextSweep <= nNow) {
// Sweep out expired orphan pool entries:
int nErased = 0;
int64_t nMinExpTime =
nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL;
std::map::iterator iter =
mapOrphanTransactions.begin();
while (iter != mapOrphanTransactions.end()) {
std::map::iterator maybeErase = iter++;
if (maybeErase->second.nTimeExpire <= nNow) {
nErased += EraseOrphanTx(maybeErase->second.tx->GetId());
} else {
nMinExpTime =
std::min(maybeErase->second.nTimeExpire, nMinExpTime);
}
}
// Sweep again 5 minutes after the next entry that expires in order to
// batch the linear scan.
nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL;
if (nErased > 0) {
LogPrint("mempool", "Erased %d orphan tx due to expiration\n",
nErased);
}
}
while (mapOrphanTransactions.size() > nMaxOrphans) {
// Evict a random orphan:
uint256 randomhash = GetRandHash();
std::map::iterator it =
mapOrphanTransactions.lower_bound(randomhash);
if (it == mapOrphanTransactions.end()) {
it = mapOrphanTransactions.begin();
}
EraseOrphanTx(it->first);
++nEvicted;
}
return nEvicted;
}
// Requires cs_main.
void Misbehaving(NodeId pnode, int howmuch, const std::string &reason) {
if (howmuch == 0) {
return;
}
CNodeState *state = State(pnode);
if (state == nullptr) {
return;
}
state->nMisbehavior += howmuch;
int banscore = GetArg("-banscore", DEFAULT_BANSCORE_THRESHOLD);
if (state->nMisbehavior >= banscore &&
state->nMisbehavior - howmuch < banscore) {
LogPrintf(
"%s: %s peer=%d (%d -> %d) reason: %s BAN THRESHOLD EXCEEDED\n",
__func__, state->name, pnode, state->nMisbehavior - howmuch,
state->nMisbehavior, reason.c_str());
state->fShouldBan = true;
} else {
LogPrintf("%s: %s peer=%d (%d -> %d) reason: %s\n", __func__,
state->name, pnode, state->nMisbehavior - howmuch,
state->nMisbehavior, reason.c_str());
}
}
// overloaded variant of above to operate on CNode*s
static void Misbehaving(CNode *node, int howmuch, const std::string &reason) {
Misbehaving(node->GetId(), howmuch, reason);
}
//////////////////////////////////////////////////////////////////////////////
//
// blockchain -> download logic notification
//
PeerLogicValidation::PeerLogicValidation(CConnman *connmanIn)
: connman(connmanIn) {
// Initialize global variables that cannot be constructed at startup.
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
}
void PeerLogicValidation::SyncTransaction(const CTransaction &tx,
const CBlockIndex *pindex,
int nPosInBlock) {
if (nPosInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK) {
return;
}
LOCK(cs_main);
std::vector vOrphanErase;
// Which orphan pool entries must we evict?
for (size_t j = 0; j < tx.vin.size(); j++) {
auto itByPrev = mapOrphanTransactionsByPrev.find(tx.vin[j].prevout);
if (itByPrev == mapOrphanTransactionsByPrev.end()) {
continue;
}
for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end();
++mi) {
const CTransaction &orphanTx = *(*mi)->second.tx;
const uint256 &orphanId = orphanTx.GetId();
vOrphanErase.push_back(orphanId);
}
}
// Erase orphan transactions include or precluded by this block
if (vOrphanErase.size()) {
int nErased = 0;
for (uint256 &orphanId : vOrphanErase) {
nErased += EraseOrphanTx(orphanId);
}
LogPrint("mempool",
"Erased %d orphan tx included or conflicted by block\n",
nErased);
}
}
static CCriticalSection cs_most_recent_block;
static std::shared_ptr most_recent_block;
static std::shared_ptr
most_recent_compact_block;
static uint256 most_recent_block_hash;
void PeerLogicValidation::NewPoWValidBlock(
const CBlockIndex *pindex, const std::shared_ptr &pblock) {
std::shared_ptr pcmpctblock =
std::make_shared(*pblock);
const CNetMsgMaker msgMaker(PROTOCOL_VERSION);
LOCK(cs_main);
static int nHighestFastAnnounce = 0;
if (pindex->nHeight <= nHighestFastAnnounce) {
return;
}
nHighestFastAnnounce = pindex->nHeight;
uint256 hashBlock(pblock->GetHash());
{
LOCK(cs_most_recent_block);
most_recent_block_hash = hashBlock;
most_recent_block = pblock;
most_recent_compact_block = pcmpctblock;
}
connman->ForEachNode([this, &pcmpctblock, pindex, &msgMaker,
&hashBlock](CNode *pnode) {
// TODO: Avoid the repeated-serialization here
if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) {
return;
}
ProcessBlockAvailability(pnode->GetId());
CNodeState &state = *State(pnode->GetId());
// If the peer has, or we announced to them the previous block already,
// but we don't think they have this one, go ahead and announce it.
if (state.fPreferHeaderAndIDs && !PeerHasHeader(&state, pindex) &&
PeerHasHeader(&state, pindex->pprev)) {
LogPrint("net", "%s sending header-and-ids %s to peer=%d\n",
"PeerLogicValidation::NewPoWValidBlock",
hashBlock.ToString(), pnode->id);
connman->PushMessage(
pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock));
state.pindexBestHeaderSent = pindex;
}
});
}
void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew,
const CBlockIndex *pindexFork,
bool fInitialDownload) {
const int nNewHeight = pindexNew->nHeight;
connman->SetBestHeight(nNewHeight);
if (!fInitialDownload) {
// Find the hashes of all blocks that weren't previously in the best
// chain.
std::vector vHashes;
const CBlockIndex *pindexToAnnounce = pindexNew;
while (pindexToAnnounce != pindexFork) {
vHashes.push_back(pindexToAnnounce->GetBlockHash());
pindexToAnnounce = pindexToAnnounce->pprev;
if (vHashes.size() == MAX_BLOCKS_TO_ANNOUNCE) {
// Limit announcements in case of a huge reorganization. Rely on
// the peer's synchronization mechanism in that case.
break;
}
}
// Relay inventory, but don't relay old inventory during initial block
// download.
connman->ForEachNode([nNewHeight, &vHashes](CNode *pnode) {
if (nNewHeight > (pnode->nStartingHeight != -1
? pnode->nStartingHeight - 2000
: 0)) {
for (const uint256 &hash : boost::adaptors::reverse(vHashes)) {
pnode->PushBlockHash(hash);
}
}
});
connman->WakeMessageHandler();
}
nTimeBestReceived = GetTime();
}
void PeerLogicValidation::BlockChecked(const CBlock &block,
const CValidationState &state) {
LOCK(cs_main);
const uint256 hash(block.GetHash());
std::map>::iterator it =
mapBlockSource.find(hash);
int nDoS = 0;
if (state.IsInvalid(nDoS)) {
if (it != mapBlockSource.end() && State(it->second.first)) {
// Blocks are never rejected with internal reject codes.
assert(state.GetRejectCode() < REJECT_INTERNAL);
CBlockReject reject = {
uint8_t(state.GetRejectCode()),
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH),
hash};
State(it->second.first)->rejects.push_back(reject);
if (nDoS > 0 && it->second.second) {
Misbehaving(it->second.first, nDoS, state.GetRejectReason());
}
}
}
// Check that:
// 1. The block is valid
// 2. We're not in initial block download
// 3. This is currently the best block we're aware of. We haven't updated
// the tip yet so we have no way to check this directly here. Instead we
// just check that there are currently no other blocks in flight.
else if (state.IsValid() && !IsInitialBlockDownload() &&
mapBlocksInFlight.count(hash) == mapBlocksInFlight.size()) {
if (it != mapBlockSource.end()) {
MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first, *connman);
}
}
if (it != mapBlockSource.end()) {
mapBlockSource.erase(it);
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Messages
//
static bool AlreadyHave(const CInv &inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
switch (inv.type) {
case MSG_TX: {
assert(recentRejects);
if (chainActive.Tip()->GetBlockHash() !=
hashRecentRejectsChainTip) {
// If the chain tip has changed previously rejected transactions
// might be now valid, e.g. due to a nLockTime'd tx becoming
// valid, or a double-spend. Reset the rejects filter and give
// those txs a second chance.
hashRecentRejectsChainTip = chainActive.Tip()->GetBlockHash();
recentRejects->reset();
}
// Use pcoinsTip->HaveCoinInCache as a quick approximation to
// exclude requesting or processing some txs which have already been
// included in a block. As this is best effort, we only check for
// output 0 and 1. This works well enough in practice and we get
// diminishing returns with 2 onward.
return recentRejects->contains(inv.hash) ||
mempool.exists(inv.hash) ||
mapOrphanTransactions.count(inv.hash) ||
pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 0)) ||
pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 1));
}
case MSG_BLOCK:
return mapBlockIndex.count(inv.hash);
}
// Don't know what it is, just say we already got one
return true;
}
static void RelayTransaction(const CTransaction &tx, CConnman &connman) {
CInv inv(MSG_TX, tx.GetId());
connman.ForEachNode([&inv](CNode *pnode) { pnode->PushInventory(inv); });
}
static void RelayAddress(const CAddress &addr, bool fReachable,
CConnman &connman) {
// Limited relaying of addresses outside our network(s)
unsigned int nRelayNodes = fReachable ? 2 : 1;
// Relay to a limited number of other nodes.
// Use deterministic randomness to send to the same nodes for 24 hours at a
// time so the addrKnowns of the chosen nodes prevent repeats.
uint64_t hashAddr = addr.GetHash();
const CSipHasher hasher =
connman.GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY)
.Write(hashAddr << 32)
.Write((GetTime() + hashAddr) / (24 * 60 * 60));
FastRandomContext insecure_rand;
std::array, 2> best{
{{0, nullptr}, {0, nullptr}}};
assert(nRelayNodes <= best.size());
auto sortfunc = [&best, &hasher, nRelayNodes](CNode *pnode) {
if (pnode->nVersion >= CADDR_TIME_VERSION) {
uint64_t hashKey = CSipHasher(hasher).Write(pnode->id).Finalize();
for (unsigned int i = 0; i < nRelayNodes; i++) {
if (hashKey > best[i].first) {
std::copy(best.begin() + i, best.begin() + nRelayNodes - 1,
best.begin() + i + 1);
best[i] = std::make_pair(hashKey, pnode);
break;
}
}
}
};
auto pushfunc = [&addr, &best, nRelayNodes, &insecure_rand] {
for (unsigned int i = 0; i < nRelayNodes && best[i].first != 0; i++) {
best[i].second->PushAddress(addr, insecure_rand);
}
};
connman.ForEachNodeThen(std::move(sortfunc), std::move(pushfunc));
}
static void ProcessGetData(const Config &config, CNode *pfrom,
const Consensus::Params &consensusParams,
CConnman &connman,
const std::atomic &interruptMsgProc) {
std::deque::iterator it = pfrom->vRecvGetData.begin();
std::vector vNotFound;
const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
LOCK(cs_main);
while (it != pfrom->vRecvGetData.end()) {
// Don't bother if send buffer is too full to respond anyway.
if (pfrom->fPauseSend) {
break;
}
const CInv &inv = *it;
{
if (interruptMsgProc) {
return;
}
it++;
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK ||
inv.type == MSG_CMPCT_BLOCK) {
bool send = false;
BlockMap::iterator mi = mapBlockIndex.find(inv.hash);
if (mi != mapBlockIndex.end()) {
if (mi->second->nChainTx &&
!mi->second->IsValid(BLOCK_VALID_SCRIPTS) &&
mi->second->IsValid(BLOCK_VALID_TREE)) {
// If we have the block and all of its parents, but have
// not yet validated it, we might be in the middle of
// connecting it (ie in the unlock of cs_main before
// ActivateBestChain but after AcceptBlock). In this
// case, we need to run ActivateBestChain prior to
// checking the relay conditions below.
std::shared_ptr a_recent_block;
{
LOCK(cs_most_recent_block);
a_recent_block = most_recent_block;
}
CValidationState dummy;
ActivateBestChain(config, dummy, a_recent_block);
}
if (chainActive.Contains(mi->second)) {
send = true;
} else {
static const int nOneMonth = 30 * 24 * 60 * 60;
// To prevent fingerprinting attacks, only send blocks
// outside of the active chain if they are valid, and no
// more than a month older (both in time, and in best
// equivalent proof of work) than the best header chain
// we know about.
send = mi->second->IsValid(BLOCK_VALID_SCRIPTS) &&
(pindexBestHeader != nullptr) &&
(pindexBestHeader->GetBlockTime() -
mi->second->GetBlockTime() <
nOneMonth) &&
(GetBlockProofEquivalentTime(
*pindexBestHeader, *mi->second,
*pindexBestHeader,
consensusParams) < nOneMonth);
if (!send) {
LogPrintf("%s: ignoring request from peer=%i for "
"old block that isn't in the main "
"chain\n",
__func__, pfrom->GetId());
}
}
}
// Disconnect node in case we have reached the outbound limit
// for serving historical blocks never disconnect whitelisted
// nodes.
// assume > 1 week = historical
static const int nOneWeek = 7 * 24 * 60 * 60;
if (send && connman.OutboundTargetReached(true) &&
(((pindexBestHeader != nullptr) &&
(pindexBestHeader->GetBlockTime() -
mi->second->GetBlockTime() >
nOneWeek)) ||
inv.type == MSG_FILTERED_BLOCK) &&
!pfrom->fWhitelisted) {
LogPrint("net", "historical block serving limit reached, "
"disconnect peer=%d\n",
pfrom->GetId());
// disconnect node
pfrom->fDisconnect = true;
send = false;
}
// Pruned nodes may have deleted the block, so check whether
// it's available before trying to send.
if (send && (mi->second->nStatus & BLOCK_HAVE_DATA)) {
// Send block from disk
CBlock block;
if (!ReadBlockFromDisk(block, (*mi).second, config)) {
assert(!"cannot load block from disk");
}
if (inv.type == MSG_BLOCK) {
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::BLOCK, block));
} else if (inv.type == MSG_FILTERED_BLOCK) {
bool sendMerkleBlock = false;
CMerkleBlock merkleBlock;
{
LOCK(pfrom->cs_filter);
if (pfrom->pfilter) {
sendMerkleBlock = true;
merkleBlock =
CMerkleBlock(block, *pfrom->pfilter);
}
}
if (sendMerkleBlock) {
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK,
merkleBlock));
// CMerkleBlock just contains hashes, so also push
// any transactions in the block the client did not
// see. This avoids hurting performance by
// pointlessly requiring a round-trip. Note that
// there is currently no way for a node to request
// any single transactions we didn't send here -
// they must either disconnect and retry or request
// the full block. Thus, the protocol spec specified
// allows for us to provide duplicate txn here,
// however we MUST always provide at least what the
// remote peer needs.
typedef std::pair PairType;
for (PairType &pair : merkleBlock.vMatchedTxn) {
connman.PushMessage(
pfrom,
msgMaker.Make(NetMsgType::TX,
*block.vtx[pair.first]));
}
}
// else
// no response
} else if (inv.type == MSG_CMPCT_BLOCK) {
// If a peer is asking for old blocks, we're almost
// guaranteed they won't have a useful mempool to match
// against a compact block, and we don't feel like
// constructing the object for them, so instead we
// respond with the full, non-compact block.
int nSendFlags = 0;
if (CanDirectFetch(consensusParams) &&
mi->second->nHeight >=
chainActive.Height() - MAX_CMPCTBLOCK_DEPTH) {
CBlockHeaderAndShortTxIDs cmpctblock(block);
connman.PushMessage(
pfrom, msgMaker.Make(nSendFlags,
NetMsgType::CMPCTBLOCK,
cmpctblock));
} else {
connman.PushMessage(
pfrom, msgMaker.Make(nSendFlags,
NetMsgType::BLOCK, block));
}
}
// Trigger the peer node to send a getblocks request for the
// next batch of inventory.
if (inv.hash == pfrom->hashContinue) {
// Bypass PushInventory, this must send even if
// redundant, and we want it right after the last block
// so they don't wait for other stuff first.
std::vector vInv;
vInv.push_back(
CInv(MSG_BLOCK, chainActive.Tip()->GetBlockHash()));
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::INV, vInv));
pfrom->hashContinue.SetNull();
}
}
} else if (inv.type == MSG_TX) {
// Send stream from relay memory
bool push = false;
auto mi = mapRelay.find(inv.hash);
int nSendFlags = 0;
if (mi != mapRelay.end()) {
connman.PushMessage(
pfrom,
msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second));
push = true;
} else if (pfrom->timeLastMempoolReq) {
auto txinfo = mempool.info(inv.hash);
// To protect privacy, do not answer getdata using the
// mempool when that TX couldn't have been INVed in reply to
// a MEMPOOL request.
if (txinfo.tx &&
txinfo.nTime <= pfrom->timeLastMempoolReq) {
connman.PushMessage(pfrom, msgMaker.Make(nSendFlags,
NetMsgType::TX,
*txinfo.tx));
push = true;
}
}
if (!push) {
vNotFound.push_back(inv);
}
}
// Track requests for our stuff.
GetMainSignals().Inventory(inv.hash);
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK ||
inv.type == MSG_CMPCT_BLOCK) {
break;
}
}
}
pfrom->vRecvGetData.erase(pfrom->vRecvGetData.begin(), it);
if (!vNotFound.empty()) {
// Let the peer know that we didn't find what it asked for, so it
// doesn't have to wait around forever. Currently only SPV clients
// actually care about this message: it's needed when they are
// recursively walking the dependencies of relevant unconfirmed
// transactions. SPV clients want to do that because they want to know
// about (and store and rebroadcast and risk analyze) the dependencies
// of transactions relevant to them, without having to download the
// entire memory pool.
connman.PushMessage(pfrom,
msgMaker.Make(NetMsgType::NOTFOUND, vNotFound));
}
}
uint32_t GetFetchFlags(CNode *pfrom, const CBlockIndex *pprev,
const Consensus::Params &chainparams) {
uint32_t nFetchFlags = 0;
return nFetchFlags;
}
inline static void SendBlockTransactions(const CBlock &block,
const BlockTransactionsRequest &req,
CNode *pfrom, CConnman &connman) {
BlockTransactions resp(req);
for (size_t i = 0; i < req.indexes.size(); i++) {
if (req.indexes[i] >= block.vtx.size()) {
LOCK(cs_main);
Misbehaving(pfrom, 100, "out-of-bound-tx-index");
LogPrintf(
"Peer %d sent us a getblocktxn with out-of-bounds tx indices",
pfrom->id);
return;
}
resp.txn[i] = block.vtx[req.indexes[i]];
}
LOCK(cs_main);
const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
int nSendFlags = 0;
connman.PushMessage(pfrom,
msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp));
}
static bool ProcessMessage(const Config &config, CNode *pfrom,
const std::string &strCommand, CDataStream &vRecv,
int64_t nTimeReceived,
const CChainParams &chainparams, CConnman &connman,
const std::atomic &interruptMsgProc) {
LogPrint("net", "received: %s (%u bytes) peer=%d\n",
SanitizeString(strCommand), vRecv.size(), pfrom->id);
if (IsArgSet("-dropmessagestest") &&
GetRand(GetArg("-dropmessagestest", 0)) == 0) {
LogPrintf("dropmessagestest DROPPING RECV MESSAGE\n");
return true;
}
if (!(pfrom->GetLocalServices() & NODE_BLOOM) &&
(strCommand == NetMsgType::FILTERLOAD ||
strCommand == NetMsgType::FILTERADD)) {
if (pfrom->nVersion >= NO_BLOOM_VERSION) {
LOCK(cs_main);
Misbehaving(pfrom, 100, "no-bloom-version");
return false;
} else {
pfrom->fDisconnect = true;
return false;
}
}
if (strCommand == NetMsgType::REJECT) {
if (fDebug) {
try {
std::string strMsg;
uint8_t ccode;
std::string strReason;
vRecv >> LIMITED_STRING(strMsg, CMessageHeader::COMMAND_SIZE) >>
ccode >>
LIMITED_STRING(strReason, MAX_REJECT_MESSAGE_LENGTH);
std::ostringstream ss;
ss << strMsg << " code " << itostr(ccode) << ": " << strReason;
if (strMsg == NetMsgType::BLOCK || strMsg == NetMsgType::TX) {
uint256 hash;
vRecv >> hash;
ss << ": hash " << hash.ToString();
}
LogPrint("net", "Reject %s\n", SanitizeString(ss.str()));
} catch (const std::ios_base::failure &) {
// Avoid feedback loops by preventing reject messages from
// triggering a new reject message.
LogPrint("net", "Unparseable reject message received\n");
}
}
}
else if (strCommand == NetMsgType::VERSION) {
// Each connection can only send one version message
if (pfrom->nVersion != 0) {
connman.PushMessage(
pfrom,
CNetMsgMaker(INIT_PROTO_VERSION)
.Make(NetMsgType::REJECT, strCommand, REJECT_DUPLICATE,
std::string("Duplicate version message")));
LOCK(cs_main);
Misbehaving(pfrom, 1, "multiple-version");
return false;
}
int64_t nTime;
CAddress addrMe;
CAddress addrFrom;
uint64_t nNonce = 1;
uint64_t nServiceInt;
ServiceFlags nServices;
int nVersion;
int nSendVersion;
std::string strSubVer;
std::string cleanSubVer;
int nStartingHeight = -1;
bool fRelay = true;
vRecv >> nVersion >> nServiceInt >> nTime >> addrMe;
nSendVersion = std::min(nVersion, PROTOCOL_VERSION);
nServices = ServiceFlags(nServiceInt);
if (!pfrom->fInbound) {
connman.SetServices(pfrom->addr, nServices);
}
if (pfrom->nServicesExpected & ~nServices) {
LogPrint("net", "peer=%d does not offer the expected services "
"(%08x offered, %08x expected); disconnecting\n",
pfrom->id, nServices, pfrom->nServicesExpected);
connman.PushMessage(
pfrom,
CNetMsgMaker(INIT_PROTO_VERSION)
.Make(NetMsgType::REJECT, strCommand, REJECT_NONSTANDARD,
strprintf("Expected to offer services %08x",
pfrom->nServicesExpected)));
pfrom->fDisconnect = true;
return false;
}
if (nVersion < MIN_PEER_PROTO_VERSION) {
// disconnect from peers older than this proto version
LogPrintf("peer=%d using obsolete version %i; disconnecting\n",
pfrom->id, nVersion);
connman.PushMessage(
pfrom,
CNetMsgMaker(INIT_PROTO_VERSION)
.Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater",
MIN_PEER_PROTO_VERSION)));
pfrom->fDisconnect = true;
return false;
}
if (!vRecv.empty()) {
vRecv >> addrFrom >> nNonce;
}
if (!vRecv.empty()) {
vRecv >> LIMITED_STRING(strSubVer, MAX_SUBVERSION_LENGTH);
cleanSubVer = SanitizeString(strSubVer);
}
if (!vRecv.empty()) {
vRecv >> nStartingHeight;
}
if (!vRecv.empty()) {
vRecv >> fRelay;
}
// Disconnect if we connected to ourself
if (pfrom->fInbound && !connman.CheckIncomingNonce(nNonce)) {
LogPrintf("connected to self at %s, disconnecting\n",
pfrom->addr.ToString());
pfrom->fDisconnect = true;
return true;
}
if (pfrom->fInbound && addrMe.IsRoutable()) {
SeenLocal(addrMe);
}
// Be shy and don't send version until we hear
if (pfrom->fInbound) {
PushNodeVersion(config, pfrom, connman, GetAdjustedTime());
}
connman.PushMessage(
pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERACK));
pfrom->nServices = nServices;
pfrom->SetAddrLocal(addrMe);
{
LOCK(pfrom->cs_SubVer);
pfrom->strSubVer = strSubVer;
pfrom->cleanSubVer = cleanSubVer;
}
pfrom->nStartingHeight = nStartingHeight;
pfrom->fClient = !(nServices & NODE_NETWORK);
{
LOCK(pfrom->cs_filter);
// set to true after we get the first filter* message
pfrom->fRelayTxes = fRelay;
}
// Change version
pfrom->SetSendVersion(nSendVersion);
pfrom->nVersion = nVersion;
// Potentially mark this peer as a preferred download peer.
{
LOCK(cs_main);
UpdatePreferredDownload(pfrom, State(pfrom->GetId()));
}
if (!pfrom->fInbound) {
// Advertise our address
if (fListen && !IsInitialBlockDownload()) {
CAddress addr =
GetLocalAddress(&pfrom->addr, pfrom->GetLocalServices());
FastRandomContext insecure_rand;
if (addr.IsRoutable()) {
LogPrint("net", "ProcessMessages: advertising address %s\n",
addr.ToString());
pfrom->PushAddress(addr, insecure_rand);
} else if (IsPeerAddrLocalGood(pfrom)) {
addr.SetIP(addrMe);
LogPrint("net", "ProcessMessages: advertising address %s\n",
addr.ToString());
pfrom->PushAddress(addr, insecure_rand);
}
}
// Get recent addresses
if (pfrom->fOneShot || pfrom->nVersion >= CADDR_TIME_VERSION ||
connman.GetAddressCount() < 1000) {
connman.PushMessage(
pfrom,
CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR));
pfrom->fGetAddr = true;
}
connman.MarkAddressGood(pfrom->addr);
}
std::string remoteAddr;
if (fLogIPs) {
remoteAddr = ", peeraddr=" + pfrom->addr.ToString();
}
LogPrintf("receive version message: [%s] %s: version %d, blocks=%d, "
"us=%s, peer=%d%s\n",
pfrom->addr.ToString().c_str(), cleanSubVer, pfrom->nVersion,
pfrom->nStartingHeight, addrMe.ToString(), pfrom->id,
remoteAddr);
- if (pfrom->fUsesCashMagic) {
- LogPrintf("peer %d uses CASH magic in its headers\n", pfrom->id);
- }
int64_t nTimeOffset = nTime - GetTime();
pfrom->nTimeOffset = nTimeOffset;
AddTimeData(pfrom->addr, nTimeOffset);
// If the peer is old enough to have the old alert system, send it the
// final alert.
if (pfrom->nVersion <= 70012) {
CDataStream finalAlert(
ParseHex("60010000000000000000000000ffffff7f00000000ffffff7ffef"
"fff7f01ffffff7f00000000ffffff7f00ffffff7f002f55524745"
"4e543a20416c657274206b657920636f6d70726f6d697365642c2"
"075706772616465207265717569726564004630440220653febd6"
"410f470f6bae11cad19c48413becb1ac2c17f908fd0fd53bdc3ab"
"d5202206d0e9c96fe88d4a0f01ed9dedae2b6f9e00da94cad0fec"
"aae66ecf689bf71b50"),
SER_NETWORK, PROTOCOL_VERSION);
connman.PushMessage(
pfrom, CNetMsgMaker(nSendVersion).Make("alert", finalAlert));
}
// Feeler connections exist only to verify if address is online.
if (pfrom->fFeeler) {
assert(pfrom->fInbound == false);
pfrom->fDisconnect = true;
}
return true;
}
else if (pfrom->nVersion == 0) {
// Must have a version message before anything else
LOCK(cs_main);
Misbehaving(pfrom, 1, "missing-version");
return false;
}
// At this point, the outgoing message serialization version can't change.
const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
if (strCommand == NetMsgType::VERACK) {
pfrom->SetRecvVersion(
std::min(pfrom->nVersion.load(), PROTOCOL_VERSION));
if (!pfrom->fInbound) {
// Mark this node as currently connected, so we update its timestamp
// later.
LOCK(cs_main);
State(pfrom->GetId())->fCurrentlyConnected = true;
}
if (pfrom->nVersion >= SENDHEADERS_VERSION) {
// Tell our peer we prefer to receive headers rather than inv's
// We send this to non-NODE NETWORK peers as well, because even
// non-NODE NETWORK peers can announce blocks (such as pruning
// nodes)
connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::SENDHEADERS));
}
if (pfrom->nVersion >= SHORT_IDS_BLOCKS_VERSION) {
// Tell our peer we are willing to provide version 1 or 2
// cmpctblocks. However, we do not request new block announcements
// using cmpctblock messages. We send this to non-NODE NETWORK peers
// as well, because they may wish to request compact blocks from us.
bool fAnnounceUsingCMPCTBLOCK = false;
uint64_t nCMPCTBLOCKVersion = 1;
connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::SENDCMPCT,
fAnnounceUsingCMPCTBLOCK,
nCMPCTBLOCKVersion));
}
pfrom->fSuccessfullyConnected = true;
}
else if (!pfrom->fSuccessfullyConnected) {
// Must have a verack message before anything else
LOCK(cs_main);
Misbehaving(pfrom, 1, "missing-verack");
return false;
}
else if (strCommand == NetMsgType::ADDR) {
std::vector vAddr;
vRecv >> vAddr;
// Don't want addr from older versions unless seeding
if (pfrom->nVersion < CADDR_TIME_VERSION &&
connman.GetAddressCount() > 1000) {
return true;
}
if (vAddr.size() > 1000) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "oversized-addr");
return error("message addr size() = %u", vAddr.size());
}
// Store the new addresses
std::vector vAddrOk;
int64_t nNow = GetAdjustedTime();
int64_t nSince = nNow - 10 * 60;
for (CAddress &addr : vAddr) {
if (interruptMsgProc) {
return true;
}
if ((addr.nServices & REQUIRED_SERVICES) != REQUIRED_SERVICES) {
continue;
}
if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) {
addr.nTime = nNow - 5 * 24 * 60 * 60;
}
pfrom->AddAddressKnown(addr);
bool fReachable = IsReachable(addr);
if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 &&
addr.IsRoutable()) {
// Relay to a limited number of other nodes
RelayAddress(addr, fReachable, connman);
}
// Do not store addresses outside our network
if (fReachable) {
vAddrOk.push_back(addr);
}
}
connman.AddNewAddresses(vAddrOk, pfrom->addr, 2 * 60 * 60);
if (vAddr.size() < 1000) {
pfrom->fGetAddr = false;
}
if (pfrom->fOneShot) {
pfrom->fDisconnect = true;
}
}
else if (strCommand == NetMsgType::SENDHEADERS) {
LOCK(cs_main);
State(pfrom->GetId())->fPreferHeaders = true;
}
else if (strCommand == NetMsgType::SENDCMPCT) {
bool fAnnounceUsingCMPCTBLOCK = false;
uint64_t nCMPCTBLOCKVersion = 0;
vRecv >> fAnnounceUsingCMPCTBLOCK >> nCMPCTBLOCKVersion;
if (nCMPCTBLOCKVersion == 1) {
LOCK(cs_main);
// fProvidesHeaderAndIDs is used to "lock in" version of compact
// blocks we send.
if (!State(pfrom->GetId())->fProvidesHeaderAndIDs) {
State(pfrom->GetId())->fProvidesHeaderAndIDs = true;
}
State(pfrom->GetId())->fPreferHeaderAndIDs =
fAnnounceUsingCMPCTBLOCK;
if (!State(pfrom->GetId())->fSupportsDesiredCmpctVersion) {
State(pfrom->GetId())->fSupportsDesiredCmpctVersion = true;
}
}
}
else if (strCommand == NetMsgType::INV) {
std::vector vInv;
vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "oversized-inv");
return error("message inv size() = %u", vInv.size());
}
bool fBlocksOnly = !fRelayTxes;
// Allow whitelisted peers to send data other than blocks in blocks only
// mode if whitelistrelay is true
if (pfrom->fWhitelisted &&
GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) {
fBlocksOnly = false;
}
LOCK(cs_main);
uint32_t nFetchFlags =
GetFetchFlags(pfrom, chainActive.Tip(), chainparams.GetConsensus());
std::vector vToFetch;
for (size_t nInv = 0; nInv < vInv.size(); nInv++) {
CInv &inv = vInv[nInv];
if (interruptMsgProc) {
return true;
}
bool fAlreadyHave = AlreadyHave(inv);
LogPrint("net", "got inv: %s %s peer=%d\n", inv.ToString(),
fAlreadyHave ? "have" : "new", pfrom->id);
if (inv.type == MSG_TX) {
inv.type |= nFetchFlags;
}
if (inv.type == MSG_BLOCK) {
UpdateBlockAvailability(pfrom->GetId(), inv.hash);
if (!fAlreadyHave && !fImporting && !fReindex &&
!mapBlocksInFlight.count(inv.hash)) {
// We used to request the full block here, but since
// headers-announcements are now the primary method of
// announcement on the network, and since, in the case that
// a node fell back to inv we probably have a reorg which we
// should get the headers for first, we now only provide a
// getheaders response here. When we receive the headers, we
// will then ask for the blocks we need.
connman.PushMessage(
pfrom,
msgMaker.Make(NetMsgType::GETHEADERS,
chainActive.GetLocator(pindexBestHeader),
inv.hash));
LogPrint("net", "getheaders (%d) %s to peer=%d\n",
pindexBestHeader->nHeight, inv.hash.ToString(),
pfrom->id);
}
} else {
pfrom->AddInventoryKnown(inv);
if (fBlocksOnly) {
LogPrint("net", "transaction (%s) inv sent in violation of "
"protocol peer=%d\n",
inv.hash.ToString(), pfrom->id);
} else if (!fAlreadyHave && !fImporting && !fReindex &&
!IsInitialBlockDownload()) {
pfrom->AskFor(inv);
}
}
// Track requests for our stuff
GetMainSignals().Inventory(inv.hash);
}
if (!vToFetch.empty()) {
connman.PushMessage(pfrom,
msgMaker.Make(NetMsgType::GETDATA, vToFetch));
}
}
else if (strCommand == NetMsgType::GETDATA) {
std::vector vInv;
vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "too-many-inv");
return error("message getdata size() = %u", vInv.size());
}
if (fDebug || (vInv.size() != 1)) {
LogPrint("net", "received getdata (%u invsz) peer=%d\n",
vInv.size(), pfrom->id);
}
if ((fDebug && vInv.size() > 0) || (vInv.size() == 1)) {
LogPrint("net", "received getdata for: %s peer=%d\n",
vInv[0].ToString(), pfrom->id);
}
pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(),
vInv.end());
ProcessGetData(config, pfrom, chainparams.GetConsensus(), connman,
interruptMsgProc);
}
else if (strCommand == NetMsgType::GETBLOCKS) {
CBlockLocator locator;
uint256 hashStop;
vRecv >> locator >> hashStop;
// We might have announced the currently-being-connected tip using a
// compact block, which resulted in the peer sending a getblocks
// request, which we would otherwise respond to without the new block.
// To avoid this situation we simply verify that we are on our best
// known chain now. This is super overkill, but we handle it better
// for getheaders requests, and there are no known nodes which support
// compact blocks but still use getblocks to request blocks.
{
std::shared_ptr a_recent_block;
{
LOCK(cs_most_recent_block);
a_recent_block = most_recent_block;
}
CValidationState dummy;
ActivateBestChain(config, dummy, a_recent_block);
}
LOCK(cs_main);
// Find the last block the caller has in the main chain
const CBlockIndex *pindex = FindForkInGlobalIndex(chainActive, locator);
// Send the rest of the chain
if (pindex) {
pindex = chainActive.Next(pindex);
}
int nLimit = 500;
LogPrint("net", "getblocks %d to %s limit %d from peer=%d\n",
(pindex ? pindex->nHeight : -1),
hashStop.IsNull() ? "end" : hashStop.ToString(), nLimit,
pfrom->id);
for (; pindex; pindex = chainActive.Next(pindex)) {
if (pindex->GetBlockHash() == hashStop) {
LogPrint("net", " getblocks stopping at %d %s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
break;
}
// If pruning, don't inv blocks unless we have on disk and are
// likely to still have for some reasonable time window (1 hour)
// that block relay might require.
const int nPrunedBlocksLikelyToHave =
MIN_BLOCKS_TO_KEEP -
3600 / chainparams.GetConsensus().nPowTargetSpacing;
if (fPruneMode &&
(!(pindex->nStatus & BLOCK_HAVE_DATA) ||
pindex->nHeight <=
chainActive.Tip()->nHeight - nPrunedBlocksLikelyToHave)) {
LogPrint(
"net",
" getblocks stopping, pruned or too old block at %d %s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
break;
}
pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash()));
if (--nLimit <= 0) {
// When this block is requested, we'll send an inv that'll
// trigger the peer to getblocks the next batch of inventory.
LogPrint("net", " getblocks stopping at limit %d %s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
pfrom->hashContinue = pindex->GetBlockHash();
break;
}
}
}
else if (strCommand == NetMsgType::GETBLOCKTXN) {
BlockTransactionsRequest req;
vRecv >> req;
std::shared_ptr recent_block;
{
LOCK(cs_most_recent_block);
if (most_recent_block_hash == req.blockhash) {
recent_block = most_recent_block;
}
// Unlock cs_most_recent_block to avoid cs_main lock inversion
}
if (recent_block) {
SendBlockTransactions(*recent_block, req, pfrom, connman);
return true;
}
LOCK(cs_main);
BlockMap::iterator it = mapBlockIndex.find(req.blockhash);
if (it == mapBlockIndex.end() ||
!(it->second->nStatus & BLOCK_HAVE_DATA)) {
LogPrintf("Peer %d sent us a getblocktxn for a block we don't have",
pfrom->id);
return true;
}
if (it->second->nHeight < chainActive.Height() - MAX_BLOCKTXN_DEPTH) {
// If an older block is requested (should never happen in practice,
// but can happen in tests) send a block response instead of a
// blocktxn response. Sending a full block response instead of a
// small blocktxn response is preferable in the case where a peer
// might maliciously send lots of getblocktxn requests to trigger
// expensive disk reads, because it will require the peer to
// actually receive all the data read from disk over the network.
LogPrint("net",
"Peer %d sent us a getblocktxn for a block > %i deep",
pfrom->id, MAX_BLOCKTXN_DEPTH);
CInv inv;
inv.type = MSG_BLOCK;
inv.hash = req.blockhash;
pfrom->vRecvGetData.push_back(inv);
ProcessGetData(config, pfrom, chainparams.GetConsensus(), connman,
interruptMsgProc);
return true;
}
CBlock block;
bool ret = ReadBlockFromDisk(block, it->second, config);
assert(ret);
SendBlockTransactions(block, req, pfrom, connman);
}
else if (strCommand == NetMsgType::GETHEADERS) {
CBlockLocator locator;
uint256 hashStop;
vRecv >> locator >> hashStop;
LOCK(cs_main);
if (IsInitialBlockDownload() && !pfrom->fWhitelisted) {
LogPrint("net", "Ignoring getheaders from peer=%d because node is "
"in initial block download\n",
pfrom->id);
return true;
}
CNodeState *nodestate = State(pfrom->GetId());
const CBlockIndex *pindex = nullptr;
if (locator.IsNull()) {
// If locator is null, return the hashStop block
BlockMap::iterator mi = mapBlockIndex.find(hashStop);
if (mi == mapBlockIndex.end()) {
return true;
}
pindex = (*mi).second;
} else {
// Find the last block the caller has in the main chain
pindex = FindForkInGlobalIndex(chainActive, locator);
if (pindex) {
pindex = chainActive.Next(pindex);
}
}
// we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx
// count at the end
std::vector vHeaders;
int nLimit = MAX_HEADERS_RESULTS;
LogPrint("net", "getheaders %d to %s from peer=%d\n",
(pindex ? pindex->nHeight : -1),
hashStop.IsNull() ? "end" : hashStop.ToString(), pfrom->id);
for (; pindex; pindex = chainActive.Next(pindex)) {
vHeaders.push_back(pindex->GetBlockHeader());
if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop) {
break;
}
}
// pindex can be nullptr either if we sent chainActive.Tip() OR
// if our peer has chainActive.Tip() (and thus we are sending an empty
// headers message). In both cases it's safe to update
// pindexBestHeaderSent to be our tip.
//
// It is important that we simply reset the BestHeaderSent value here,
// and not max(BestHeaderSent, newHeaderSent). We might have announced
// the currently-being-connected tip using a compact block, which
// resulted in the peer sending a headers request, which we respond to
// without the new block. By resetting the BestHeaderSent, we ensure we
// will re-announce the new block via headers (or compact blocks again)
// in the SendMessages logic.
nodestate->pindexBestHeaderSent = pindex ? pindex : chainActive.Tip();
connman.PushMessage(pfrom,
msgMaker.Make(NetMsgType::HEADERS, vHeaders));
}
else if (strCommand == NetMsgType::TX) {
// Stop processing the transaction early if
// We are in blocks only mode and peer is either not whitelisted or
// whitelistrelay is off
if (!fRelayTxes &&
(!pfrom->fWhitelisted ||
!GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))) {
LogPrint("net",
"transaction sent in violation of protocol peer=%d\n",
pfrom->id);
return true;
}
std::deque vWorkQueue;
std::vector vEraseQueue;
CTransactionRef ptx;
vRecv >> ptx;
const CTransaction &tx = *ptx;
CInv inv(MSG_TX, tx.GetId());
pfrom->AddInventoryKnown(inv);
LOCK(cs_main);
bool fMissingInputs = false;
CValidationState state;
pfrom->setAskFor.erase(inv.hash);
mapAlreadyAskedFor.erase(inv.hash);
std::list lRemovedTxn;
if (!AlreadyHave(inv) &&
AcceptToMemoryPool(config, mempool, state, ptx, true,
&fMissingInputs, &lRemovedTxn)) {
mempool.check(pcoinsTip);
RelayTransaction(tx, connman);
for (size_t i = 0; i < tx.vout.size(); i++) {
vWorkQueue.emplace_back(inv.hash, i);
}
pfrom->nLastTXTime = GetTime();
LogPrint("mempool", "AcceptToMemoryPool: peer=%d: accepted %s "
"(poolsz %u txn, %u kB)\n",
pfrom->id, tx.GetId().ToString(), mempool.size(),
mempool.DynamicMemoryUsage() / 1000);
// Recursively process any orphan transactions that depended on this
// one
std::set setMisbehaving;
while (!vWorkQueue.empty()) {
auto itByPrev =
mapOrphanTransactionsByPrev.find(vWorkQueue.front());
vWorkQueue.pop_front();
if (itByPrev == mapOrphanTransactionsByPrev.end()) {
continue;
}
for (auto mi = itByPrev->second.begin();
mi != itByPrev->second.end(); ++mi) {
const CTransactionRef &porphanTx = (*mi)->second.tx;
const CTransaction &orphanTx = *porphanTx;
const uint256 &orphanId = orphanTx.GetId();
NodeId fromPeer = (*mi)->second.fromPeer;
bool fMissingInputs2 = false;
// Use a dummy CValidationState so someone can't setup nodes
// to counter-DoS based on orphan resolution (that is,
// feeding people an invalid transaction based on LegitTxX
// in order to get anyone relaying LegitTxX banned)
CValidationState stateDummy;
if (setMisbehaving.count(fromPeer)) {
continue;
}
if (AcceptToMemoryPool(config, mempool, stateDummy,
porphanTx, true, &fMissingInputs2,
&lRemovedTxn)) {
LogPrint("mempool", " accepted orphan tx %s\n",
orphanId.ToString());
RelayTransaction(orphanTx, connman);
for (size_t i = 0; i < orphanTx.vout.size(); i++) {
vWorkQueue.emplace_back(orphanId, i);
}
vEraseQueue.push_back(orphanId);
} else if (!fMissingInputs2) {
int nDos = 0;
if (stateDummy.IsInvalid(nDos) && nDos > 0) {
// Punish peer that gave us an invalid orphan tx
Misbehaving(fromPeer, nDos, "invalid-orphan-tx");
setMisbehaving.insert(fromPeer);
LogPrint("mempool", " invalid orphan tx %s\n",
orphanId.ToString());
}
// Has inputs but not accepted to mempool
// Probably non-standard or insufficient fee/priority
LogPrint("mempool", " removed orphan tx %s\n",
orphanId.ToString());
vEraseQueue.push_back(orphanId);
if (!stateDummy.CorruptionPossible()) {
// Do not use rejection cache for witness
// transactions or witness-stripped transactions, as
// they can have been malleated. See
// https://github.com/bitcoin/bitcoin/issues/8279
// for details.
assert(recentRejects);
recentRejects->insert(orphanId);
}
}
mempool.check(pcoinsTip);
}
}
for (uint256 hash : vEraseQueue) {
EraseOrphanTx(hash);
}
} else if (fMissingInputs) {
// It may be the case that the orphans parents have all been
// rejected.
bool fRejectedParents = false;
for (const CTxIn &txin : tx.vin) {
if (recentRejects->contains(txin.prevout.hash)) {
fRejectedParents = true;
break;
}
}
if (!fRejectedParents) {
uint32_t nFetchFlags = GetFetchFlags(
pfrom, chainActive.Tip(), chainparams.GetConsensus());
for (const CTxIn &txin : tx.vin) {
CInv _inv(MSG_TX | nFetchFlags, txin.prevout.hash);
pfrom->AddInventoryKnown(_inv);
if (!AlreadyHave(_inv)) {
pfrom->AskFor(_inv);
}
}
AddOrphanTx(ptx, pfrom->GetId());
// DoS prevention: do not allow mapOrphanTransactions to grow
// unbounded
unsigned int nMaxOrphanTx = (unsigned int)std::max(
int64_t(0),
GetArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS));
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
if (nEvicted > 0) {
LogPrint("mempool", "mapOrphan overflow, removed %u tx\n",
nEvicted);
}
} else {
LogPrint("mempool",
"not keeping orphan with rejected parents %s\n",
tx.GetId().ToString());
// We will continue to reject this tx since it has rejected
// parents so avoid re-requesting it from other peers.
recentRejects->insert(tx.GetId());
}
} else {
if (!state.CorruptionPossible()) {
// Do not use rejection cache for witness transactions or
// witness-stripped transactions, as they can have been
// malleated. See https://github.com/bitcoin/bitcoin/issues/8279
// for details.
assert(recentRejects);
recentRejects->insert(tx.GetId());
if (RecursiveDynamicUsage(*ptx) < 100000) {
AddToCompactExtraTransactions(ptx);
}
}
if (pfrom->fWhitelisted &&
GetBoolArg("-whitelistforcerelay",
DEFAULT_WHITELISTFORCERELAY)) {
// Always relay transactions received from whitelisted peers,
// even if they were already in the mempool or rejected from it
// due to policy, allowing the node to function as a gateway for
// nodes hidden behind it.
//
// Never relay transactions that we would assign a non-zero DoS
// score for, as we expect peers to do the same with us in that
// case.
int nDoS = 0;
if (!state.IsInvalid(nDoS) || nDoS == 0) {
LogPrintf("Force relaying tx %s from whitelisted peer=%d\n",
tx.GetId().ToString(), pfrom->id);
RelayTransaction(tx, connman);
} else {
LogPrintf("Not relaying invalid transaction %s from "
"whitelisted peer=%d (%s)\n",
tx.GetId().ToString(), pfrom->id,
FormatStateMessage(state));
}
}
}
for (const CTransactionRef &removedTx : lRemovedTxn) {
AddToCompactExtraTransactions(removedTx);
}
int nDoS = 0;
if (state.IsInvalid(nDoS)) {
LogPrint("mempoolrej", "%s from peer=%d was not accepted: %s\n",
tx.GetId().ToString(), pfrom->id,
FormatStateMessage(state));
// Never send AcceptToMemoryPool's internal codes over P2P.
if (state.GetRejectCode() < REJECT_INTERNAL) {
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand,
uint8_t(state.GetRejectCode()),
state.GetRejectReason().substr(
0, MAX_REJECT_MESSAGE_LENGTH),
inv.hash));
}
if (nDoS > 0) {
Misbehaving(pfrom, nDoS, state.GetRejectReason());
}
}
}
// Ignore blocks received while importing
else if (strCommand == NetMsgType::CMPCTBLOCK && !fImporting && !fReindex) {
CBlockHeaderAndShortTxIDs cmpctblock;
vRecv >> cmpctblock;
{
LOCK(cs_main);
if (mapBlockIndex.find(cmpctblock.header.hashPrevBlock) ==
mapBlockIndex.end()) {
// Doesn't connect (or is genesis), instead of DoSing in
// AcceptBlockHeader, request deeper headers
if (!IsInitialBlockDownload()) {
connman.PushMessage(
pfrom,
msgMaker.Make(NetMsgType::GETHEADERS,
chainActive.GetLocator(pindexBestHeader),
uint256()));
}
return true;
}
}
const CBlockIndex *pindex = nullptr;
CValidationState state;
if (!ProcessNewBlockHeaders(config, {cmpctblock.header}, state,
&pindex)) {
int nDoS;
if (state.IsInvalid(nDoS)) {
if (nDoS > 0) {
LOCK(cs_main);
Misbehaving(pfrom, nDoS, state.GetRejectReason());
}
LogPrintf("Peer %d sent us invalid header via cmpctblock\n",
pfrom->id);
return true;
}
}
// When we succeed in decoding a block's txids from a cmpctblock
// message we typically jump to the BLOCKTXN handling code, with a
// dummy (empty) BLOCKTXN message, to re-use the logic there in
// completing processing of the putative block (without cs_main).
bool fProcessBLOCKTXN = false;
CDataStream blockTxnMsg(SER_NETWORK, PROTOCOL_VERSION);
// If we end up treating this as a plain headers message, call that as
// well
// without cs_main.
bool fRevertToHeaderProcessing = false;
CDataStream vHeadersMsg(SER_NETWORK, PROTOCOL_VERSION);
// Keep a CBlock for "optimistic" compactblock reconstructions (see
// below)
std::shared_ptr pblock = std::make_shared();
bool fBlockReconstructed = false;
{
LOCK(cs_main);
// If AcceptBlockHeader returned true, it set pindex
assert(pindex);
UpdateBlockAvailability(pfrom->GetId(), pindex->GetBlockHash());
std::map::iterator>>::
iterator blockInFlightIt =
mapBlocksInFlight.find(pindex->GetBlockHash());
bool fAlreadyInFlight = blockInFlightIt != mapBlocksInFlight.end();
if (pindex->nStatus & BLOCK_HAVE_DATA) {
// Nothing to do here
return true;
}
if (pindex->nChainWork <=
chainActive.Tip()->nChainWork || // We know something better
pindex->nTx != 0) {
// We had this block at some point, but pruned it
if (fAlreadyInFlight) {
// We requested this block for some reason, but our mempool
// will probably be useless so we just grab the block via
// normal getdata.
std::vector vInv(1);
vInv[0] = CInv(
MSG_BLOCK | GetFetchFlags(pfrom, pindex->pprev,
chainparams.GetConsensus()),
cmpctblock.header.GetHash());
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
}
return true;
}
// If we're not close to tip yet, give up and let parallel block
// fetch work its magic.
if (!fAlreadyInFlight &&
!CanDirectFetch(chainparams.GetConsensus())) {
return true;
}
CNodeState *nodestate = State(pfrom->GetId());
// We want to be a bit conservative just to be extra careful about
// DoS possibilities in compact block processing...
if (pindex->nHeight <= chainActive.Height() + 2) {
if ((!fAlreadyInFlight &&
nodestate->nBlocksInFlight <
MAX_BLOCKS_IN_TRANSIT_PER_PEER) ||
(fAlreadyInFlight &&
blockInFlightIt->second.first == pfrom->GetId())) {
std::list::iterator *queuedBlockIt = nullptr;
if (!MarkBlockAsInFlight(config, pfrom->GetId(),
pindex->GetBlockHash(),
chainparams.GetConsensus(), pindex,
&queuedBlockIt)) {
if (!(*queuedBlockIt)->partialBlock) {
(*queuedBlockIt)
->partialBlock.reset(
new PartiallyDownloadedBlock(config,
&mempool));
} else {
// The block was already in flight using compact
// blocks from the same peer.
LogPrint("net", "Peer sent us compact block we "
"were already syncing!\n");
return true;
}
}
PartiallyDownloadedBlock &partialBlock =
*(*queuedBlockIt)->partialBlock;
ReadStatus status =
partialBlock.InitData(cmpctblock, vExtraTxnForCompact);
if (status == READ_STATUS_INVALID) {
// Reset in-flight state in case of whitelist
MarkBlockAsReceived(pindex->GetBlockHash());
Misbehaving(pfrom, 100, "invalid-cmpctblk");
LogPrintf("Peer %d sent us invalid compact block\n",
pfrom->id);
return true;
} else if (status == READ_STATUS_FAILED) {
// Duplicate txindexes, the block is now in-flight, so
// just request it.
std::vector vInv(1);
vInv[0] =
CInv(MSG_BLOCK |
GetFetchFlags(pfrom, pindex->pprev,
chainparams.GetConsensus()),
cmpctblock.header.GetHash());
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
return true;
}
BlockTransactionsRequest req;
for (size_t i = 0; i < cmpctblock.BlockTxCount(); i++) {
if (!partialBlock.IsTxAvailable(i)) {
req.indexes.push_back(i);
}
}
if (req.indexes.empty()) {
// Dirty hack to jump to BLOCKTXN code (TODO: move
// message handling into their own functions)
BlockTransactions txn;
txn.blockhash = cmpctblock.header.GetHash();
blockTxnMsg << txn;
fProcessBLOCKTXN = true;
} else {
req.blockhash = pindex->GetBlockHash();
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
}
} else {
// This block is either already in flight from a different
// peer, or this peer has too many blocks outstanding to
// download from. Optimistically try to reconstruct anyway
// since we might be able to without any round trips.
PartiallyDownloadedBlock tempBlock(config, &mempool);
ReadStatus status =
tempBlock.InitData(cmpctblock, vExtraTxnForCompact);
if (status != READ_STATUS_OK) {
// TODO: don't ignore failures
return true;
}
std::vector dummy;
status = tempBlock.FillBlock(*pblock, dummy);
if (status == READ_STATUS_OK) {
fBlockReconstructed = true;
}
}
} else {
if (fAlreadyInFlight) {
// We requested this block, but its far into the future, so
// our mempool will probably be useless - request the block
// normally.
std::vector vInv(1);
vInv[0] = CInv(
MSG_BLOCK | GetFetchFlags(pfrom, pindex->pprev,
chainparams.GetConsensus()),
cmpctblock.header.GetHash());
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
return true;
} else {
// If this was an announce-cmpctblock, we want the same
// treatment as a header message. Dirty hack to process as
// if it were just a headers message (TODO: move message
// handling into their own functions)
std::vector headers;
headers.push_back(cmpctblock.header);
vHeadersMsg << headers;
fRevertToHeaderProcessing = true;
}
}
} // cs_main
if (fProcessBLOCKTXN) {
return ProcessMessage(config, pfrom, NetMsgType::BLOCKTXN,
blockTxnMsg, nTimeReceived, chainparams,
connman, interruptMsgProc);
}
if (fRevertToHeaderProcessing) {
return ProcessMessage(config, pfrom, NetMsgType::HEADERS,
vHeadersMsg, nTimeReceived, chainparams,
connman, interruptMsgProc);
}
if (fBlockReconstructed) {
// If we got here, we were able to optimistically reconstruct a
// block that is in flight from some other peer.
{
LOCK(cs_main);
mapBlockSource.emplace(pblock->GetHash(),
std::make_pair(pfrom->GetId(), false));
}
bool fNewBlock = false;
ProcessNewBlock(config, pblock, true, &fNewBlock);
if (fNewBlock) {
pfrom->nLastBlockTime = GetTime();
}
// hold cs_main for CBlockIndex::IsValid()
LOCK(cs_main);
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS)) {
// Clear download state for this block, which is in process from
// some other peer. We do this after calling. ProcessNewBlock so
// that a malleated cmpctblock announcement can't be used to
// interfere with block relay.
MarkBlockAsReceived(pblock->GetHash());
}
}
}
else if (strCommand == NetMsgType::BLOCKTXN && !fImporting &&
!fReindex) // Ignore blocks received while importing
{
BlockTransactions resp;
vRecv >> resp;
std::shared_ptr pblock = std::make_shared();
bool fBlockRead = false;
{
LOCK(cs_main);
std::map::iterator>>::iterator it =
mapBlocksInFlight.find(resp.blockhash);
if (it == mapBlocksInFlight.end() ||
!it->second.second->partialBlock ||
it->second.first != pfrom->GetId()) {
LogPrint("net", "Peer %d sent us block transactions for block "
"we weren't expecting\n",
pfrom->id);
return true;
}
PartiallyDownloadedBlock &partialBlock =
*it->second.second->partialBlock;
ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn);
if (status == READ_STATUS_INVALID) {
// Reset in-flight state in case of whitelist.
MarkBlockAsReceived(resp.blockhash);
Misbehaving(pfrom, 100, "invalid-cmpctblk-txns");
LogPrintf("Peer %d sent us invalid compact block/non-matching "
"block transactions\n",
pfrom->id);
return true;
} else if (status == READ_STATUS_FAILED) {
// Might have collided, fall back to getdata now :(
std::vector invs;
invs.push_back(
CInv(MSG_BLOCK | GetFetchFlags(pfrom, chainActive.Tip(),
chainparams.GetConsensus()),
resp.blockhash));
connman.PushMessage(pfrom,
msgMaker.Make(NetMsgType::GETDATA, invs));
} else {
// Block is either okay, or possibly we received
// READ_STATUS_CHECKBLOCK_FAILED.
// Note that CheckBlock can only fail for one of a few reasons:
// 1. bad-proof-of-work (impossible here, because we've already
// accepted the header)
// 2. merkleroot doesn't match the transactions given (already
// caught in FillBlock with READ_STATUS_FAILED, so
// impossible here)
// 3. the block is otherwise invalid (eg invalid coinbase,
// block is too big, too many legacy sigops, etc).
// So if CheckBlock failed, #3 is the only possibility.
// Under BIP 152, we don't DoS-ban unless proof of work is
// invalid (we don't require all the stateless checks to have
// been run). This is handled below, so just treat this as
// though the block was successfully read, and rely on the
// handling in ProcessNewBlock to ensure the block index is
// updated, reject messages go out, etc.
// it is now an empty pointer
MarkBlockAsReceived(resp.blockhash);
fBlockRead = true;
// mapBlockSource is only used for sending reject messages and
// DoS scores, so the race between here and cs_main in
// ProcessNewBlock is fine. BIP 152 permits peers to relay
// compact blocks after validating the header only; we should
// not punish peers if the block turns out to be invalid.
mapBlockSource.emplace(resp.blockhash,
std::make_pair(pfrom->GetId(), false));
}
} // Don't hold cs_main when we call into ProcessNewBlock
if (fBlockRead) {
bool fNewBlock = false;
// Since we requested this block (it was in mapBlocksInFlight),
// force it to be processed, even if it would not be a candidate for
// new tip (missing previous block, chain not long enough, etc)
ProcessNewBlock(config, pblock, true, &fNewBlock);
if (fNewBlock) {
pfrom->nLastBlockTime = GetTime();
}
}
}
// Ignore headers received while importing
else if (strCommand == NetMsgType::HEADERS && !fImporting && !fReindex) {
std::vector headers;
// Bypass the normal CBlock deserialization, as we don't want to risk
// deserializing 2000 full blocks.
unsigned int nCount = ReadCompactSize(vRecv);
if (nCount > MAX_HEADERS_RESULTS) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "too-many-headers");
return error("headers message size = %u", nCount);
}
headers.resize(nCount);
for (unsigned int n = 0; n < nCount; n++) {
vRecv >> headers[n];
// Ignore tx count; assume it is 0.
ReadCompactSize(vRecv);
}
if (nCount == 0) {
// Nothing interesting. Stop asking this peers for more headers.
return true;
}
const CBlockIndex *pindexLast = nullptr;
{
LOCK(cs_main);
CNodeState *nodestate = State(pfrom->GetId());
// If this looks like it could be a block announcement (nCount <
// MAX_BLOCKS_TO_ANNOUNCE), use special logic for handling headers
// that
// don't connect:
// - Send a getheaders message in response to try to connect the
// chain.
// - The peer can send up to MAX_UNCONNECTING_HEADERS in a row that
// don't connect before giving DoS points
// - Once a headers message is received that is valid and does
// connect,
// nUnconnectingHeaders gets reset back to 0.
if (mapBlockIndex.find(headers[0].hashPrevBlock) ==
mapBlockIndex.end() &&
nCount < MAX_BLOCKS_TO_ANNOUNCE) {
nodestate->nUnconnectingHeaders++;
connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS,
chainActive.GetLocator(
pindexBestHeader),
uint256()));
LogPrint("net", "received header %s: missing prev block %s, "
"sending getheaders (%d) to end (peer=%d, "
"nUnconnectingHeaders=%d)\n",
headers[0].GetHash().ToString(),
headers[0].hashPrevBlock.ToString(),
pindexBestHeader->nHeight, pfrom->id,
nodestate->nUnconnectingHeaders);
// Set hashLastUnknownBlock for this peer, so that if we
// eventually get the headers - even from a different peer -
// we can use this peer to download.
UpdateBlockAvailability(pfrom->GetId(),
headers.back().GetHash());
if (nodestate->nUnconnectingHeaders %
MAX_UNCONNECTING_HEADERS ==
0) {
// The peer is sending us many headers we can't connect.
Misbehaving(pfrom, 20, "too-many-unconnected-headers");
}
return true;
}
uint256 hashLastBlock;
for (const CBlockHeader &header : headers) {
if (!hashLastBlock.IsNull() &&
header.hashPrevBlock != hashLastBlock) {
Misbehaving(pfrom, 20, "disconnected-header");
return error("non-continuous headers sequence");
}
hashLastBlock = header.GetHash();
}
}
CValidationState state;
if (!ProcessNewBlockHeaders(config, headers, state, &pindexLast)) {
int nDoS;
if (state.IsInvalid(nDoS)) {
if (nDoS > 0) {
LOCK(cs_main);
Misbehaving(pfrom, nDoS, state.GetRejectReason());
}
return error("invalid header received");
}
}
{
LOCK(cs_main);
CNodeState *nodestate = State(pfrom->GetId());
if (nodestate->nUnconnectingHeaders > 0) {
LogPrint("net",
"peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n",
pfrom->id, nodestate->nUnconnectingHeaders);
}
nodestate->nUnconnectingHeaders = 0;
assert(pindexLast);
UpdateBlockAvailability(pfrom->GetId(), pindexLast->GetBlockHash());
if (nCount == MAX_HEADERS_RESULTS) {
// Headers message had its maximum size; the peer may have more
// headers.
// TODO: optimize: if pindexLast is an ancestor of
// chainActive.Tip or pindexBestHeader, continue from there
// instead.
LogPrint(
"net",
"more getheaders (%d) to end to peer=%d (startheight:%d)\n",
pindexLast->nHeight, pfrom->id, pfrom->nStartingHeight);
connman.PushMessage(
pfrom, msgMaker.Make(NetMsgType::GETHEADERS,
chainActive.GetLocator(pindexLast),
uint256()));
}
bool fCanDirectFetch = CanDirectFetch(chainparams.GetConsensus());
// If this set of headers is valid and ends in a block with at least
// as much work as our tip, download as much as possible.
if (fCanDirectFetch && pindexLast->IsValid(BLOCK_VALID_TREE) &&
chainActive.Tip()->nChainWork <= pindexLast->nChainWork) {
std::vector vToFetch;
const CBlockIndex *pindexWalk = pindexLast;
// Calculate all the blocks we'd need to switch to pindexLast,
// up to a limit.
while (pindexWalk && !chainActive.Contains(pindexWalk) &&
vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) &&
!mapBlocksInFlight.count(pindexWalk->GetBlockHash())) {
// We don't have this block, and it's not yet in flight.
vToFetch.push_back(pindexWalk);
}
pindexWalk = pindexWalk->pprev;
}
// If pindexWalk still isn't on our main chain, we're looking at
// a very large reorg at a time we think we're close to caught
// up to the main chain -- this shouldn't really happen. Bail
// out on the direct fetch and rely on parallel download
// instead.
if (!chainActive.Contains(pindexWalk)) {
LogPrint("net",
"Large reorg, won't direct fetch to %s (%d)\n",
pindexLast->GetBlockHash().ToString(),
pindexLast->nHeight);
} else {
std::vector vGetData;
// Download as much as possible, from earliest to latest.
for (const CBlockIndex *pindex :
boost::adaptors::reverse(vToFetch)) {
if (nodestate->nBlocksInFlight >=
MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
// Can't download any more from this peer
break;
}
uint32_t nFetchFlags = GetFetchFlags(
pfrom, pindex->pprev, chainparams.GetConsensus());
vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags,
pindex->GetBlockHash()));
MarkBlockAsInFlight(config, pfrom->GetId(),
pindex->GetBlockHash(),
chainparams.GetConsensus(), pindex);
LogPrint("net", "Requesting block %s from peer=%d\n",
pindex->GetBlockHash().ToString(), pfrom->id);
}
if (vGetData.size() > 1) {
LogPrint("net", "Downloading blocks toward %s (%d) via "
"headers direct fetch\n",
pindexLast->GetBlockHash().ToString(),
pindexLast->nHeight);
}
if (vGetData.size() > 0) {
if (nodestate->fSupportsDesiredCmpctVersion &&
vGetData.size() == 1 &&
mapBlocksInFlight.size() == 1 &&
pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) {
// In any case, we want to download using a compact
// block, not a regular one.
vGetData[0] =
CInv(MSG_CMPCT_BLOCK, vGetData[0].hash);
}
connman.PushMessage(
pfrom,
msgMaker.Make(NetMsgType::GETDATA, vGetData));
}
}
}
}
}
else if (strCommand == NetMsgType::BLOCK && !fImporting &&
!fReindex) // Ignore blocks received while importing
{
std::shared_ptr pblock = std::make_shared();
vRecv >> *pblock;
LogPrint("net", "received block %s peer=%d\n",
pblock->GetHash().ToString(), pfrom->id);
// Process all blocks from whitelisted peers, even if not requested,
// unless we're still syncing with the network. Such an unrequested
// block may still be processed, subject to the conditions in
// AcceptBlock().
bool forceProcessing = pfrom->fWhitelisted && !IsInitialBlockDownload();
const uint256 hash(pblock->GetHash());
{
LOCK(cs_main);
// Also always process if we requested the block explicitly, as we
// may need it even though it is not a candidate for a new best tip.
forceProcessing |= MarkBlockAsReceived(hash);
// mapBlockSource is only used for sending reject messages and DoS
// scores, so the race between here and cs_main in ProcessNewBlock
// is fine.
mapBlockSource.emplace(hash, std::make_pair(pfrom->GetId(), true));
}
bool fNewBlock = false;
ProcessNewBlock(config, pblock, forceProcessing, &fNewBlock);
if (fNewBlock) {
pfrom->nLastBlockTime = GetTime();
}
}
else if (strCommand == NetMsgType::GETADDR) {
// This asymmetric behavior for inbound and outbound connections was
// introduced to prevent a fingerprinting attack: an attacker can send
// specific fake addresses to users' AddrMan and later request them by
// sending getaddr messages. Making nodes which are behind NAT and can
// only make outgoing connections ignore the getaddr message mitigates
// the attack.
if (!pfrom->fInbound) {
LogPrint("net",
"Ignoring \"getaddr\" from outbound connection. peer=%d\n",
pfrom->id);
return true;
}
// Only send one GetAddr response per connection to reduce resource
// waste and discourage addr stamping of INV announcements.
if (pfrom->fSentAddr) {
LogPrint("net", "Ignoring repeated \"getaddr\". peer=%d\n",
pfrom->id);
return true;
}
pfrom->fSentAddr = true;
pfrom->vAddrToSend.clear();
std::vector vAddr = connman.GetAddresses();
FastRandomContext insecure_rand;
for (const CAddress &addr : vAddr) {
pfrom->PushAddress(addr, insecure_rand);
}
}
else if (strCommand == NetMsgType::MEMPOOL) {
if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->fWhitelisted) {
LogPrint("net", "mempool request with bloom filters disabled, "
"disconnect peer=%d\n",
pfrom->GetId());
pfrom->fDisconnect = true;
return true;
}
if (connman.OutboundTargetReached(false) && !pfrom->fWhitelisted) {
LogPrint("net", "mempool request with bandwidth limit reached, "
"disconnect peer=%d\n",
pfrom->GetId());
pfrom->fDisconnect = true;
return true;
}
LOCK(pfrom->cs_inventory);
pfrom->fSendMempool = true;
}
else if (strCommand == NetMsgType::PING) {
if (pfrom->nVersion > BIP0031_VERSION) {
uint64_t nonce = 0;
vRecv >> nonce;
// Echo the message back with the nonce. This allows for two useful
// features:
//
// 1) A remote node can quickly check if the connection is
// operational.
// 2) Remote nodes can measure the latency of the network thread. If
// this node is overloaded it won't respond to pings quickly and the
// remote node can avoid sending us more work, like chain download
// requests.
//
// The nonce stops the remote getting confused between different
// pings: without it, if the remote node sends a ping once per
// second and this node takes 5 seconds to respond to each, the 5th
// ping the remote sends would appear to return very quickly.
connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::PONG, nonce));
}
}
else if (strCommand == NetMsgType::PONG) {
int64_t pingUsecEnd = nTimeReceived;
uint64_t nonce = 0;
size_t nAvail = vRecv.in_avail();
bool bPingFinished = false;
std::string sProblem;
if (nAvail >= sizeof(nonce)) {
vRecv >> nonce;
// Only process pong message if there is an outstanding ping (old
// ping without nonce should never pong)
if (pfrom->nPingNonceSent != 0) {
if (nonce == pfrom->nPingNonceSent) {
// Matching pong received, this ping is no longer
// outstanding
bPingFinished = true;
int64_t pingUsecTime = pingUsecEnd - pfrom->nPingUsecStart;
if (pingUsecTime > 0) {
// Successful ping time measurement, replace previous
pfrom->nPingUsecTime = pingUsecTime;
pfrom->nMinPingUsecTime = std::min(
pfrom->nMinPingUsecTime.load(), pingUsecTime);
} else {
// This should never happen
sProblem = "Timing mishap";
}
} else {
// Nonce mismatches are normal when pings are overlapping
sProblem = "Nonce mismatch";
if (nonce == 0) {
// This is most likely a bug in another implementation
// somewhere; cancel this ping
bPingFinished = true;
sProblem = "Nonce zero";
}
}
} else {
sProblem = "Unsolicited pong without ping";
}
} else {
// This is most likely a bug in another implementation somewhere;
// cancel this ping
bPingFinished = true;
sProblem = "Short payload";
}
if (!(sProblem.empty())) {
LogPrint("net",
"pong peer=%d: %s, %x expected, %x received, %u bytes\n",
pfrom->id, sProblem, pfrom->nPingNonceSent, nonce, nAvail);
}
if (bPingFinished) {
pfrom->nPingNonceSent = 0;
}
}
else if (strCommand == NetMsgType::FILTERLOAD) {
CBloomFilter filter;
vRecv >> filter;
if (!filter.IsWithinSizeConstraints()) {
// There is no excuse for sending a too-large filter
LOCK(cs_main);
Misbehaving(pfrom, 100, "oversized-bloom-filter");
} else {
LOCK(pfrom->cs_filter);
delete pfrom->pfilter;
pfrom->pfilter = new CBloomFilter(filter);
pfrom->pfilter->UpdateEmptyFull();
pfrom->fRelayTxes = true;
}
}
else if (strCommand == NetMsgType::FILTERADD) {
std::vector vData;
vRecv >> vData;
// Nodes must NEVER send a data item > 520 bytes (the max size for a
// script data object, and thus, the maximum size any matched object can
// have) in a filteradd message.
bool bad = false;
if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) {
bad = true;
} else {
LOCK(pfrom->cs_filter);
if (pfrom->pfilter) {
pfrom->pfilter->insert(vData);
} else {
bad = true;
}
}
if (bad) {
LOCK(cs_main);
// The structure of this code doesn't really allow for a good error
// code. We'll go generic.
Misbehaving(pfrom, 100, "invalid-filteradd");
}
}
else if (strCommand == NetMsgType::FILTERCLEAR) {
LOCK(pfrom->cs_filter);
if (pfrom->GetLocalServices() & NODE_BLOOM) {
delete pfrom->pfilter;
pfrom->pfilter = new CBloomFilter();
}
pfrom->fRelayTxes = true;
}
else if (strCommand == NetMsgType::FEEFILTER) {
Amount newFeeFilter(0);
vRecv >> newFeeFilter;
if (MoneyRange(newFeeFilter)) {
{
LOCK(pfrom->cs_feeFilter);
pfrom->minFeeFilter = newFeeFilter;
}
LogPrint("net", "received: feefilter of %s from peer=%d\n",
CFeeRate(newFeeFilter).ToString(), pfrom->id);
}
}
else if (strCommand == NetMsgType::NOTFOUND) {
// We do not care about the NOTFOUND message, but logging an Unknown
// Command message would be undesirable as we transmit it ourselves.
}
else {
// Ignore unknown commands for extensibility
LogPrint("net", "Unknown command \"%s\" from peer=%d\n",
SanitizeString(strCommand), pfrom->id);
}
return true;
}
static bool SendRejectsAndCheckIfBanned(CNode *pnode, CConnman &connman) {
AssertLockHeld(cs_main);
CNodeState &state = *State(pnode->GetId());
for (const CBlockReject &reject : state.rejects) {
connman.PushMessage(
pnode, CNetMsgMaker(INIT_PROTO_VERSION)
.Make(NetMsgType::REJECT, (std::string)NetMsgType::BLOCK,
reject.chRejectCode, reject.strRejectReason,
reject.hashBlock));
}
state.rejects.clear();
if (state.fShouldBan) {
state.fShouldBan = false;
if (pnode->fWhitelisted) {
LogPrintf("Warning: not punishing whitelisted peer %s!\n",
pnode->addr.ToString());
} else if (pnode->fAddnode) {
LogPrintf("Warning: not punishing addnoded peer %s!\n",
pnode->addr.ToString());
} else {
pnode->fDisconnect = true;
if (pnode->addr.IsLocal()) {
LogPrintf("Warning: not banning local peer %s!\n",
pnode->addr.ToString());
} else {
connman.Ban(pnode->addr, BanReasonNodeMisbehaving);
}
}
return true;
}
return false;
}
bool ProcessMessages(const Config &config, CNode *pfrom, CConnman &connman,
const std::atomic &interruptMsgProc) {
const CChainParams &chainparams = Params();
//
// Message format
// (4) message start
// (12) command
// (4) size
// (4) checksum
// (x) data
//
bool fMoreWork = false;
if (!pfrom->vRecvGetData.empty()) {
ProcessGetData(config, pfrom, chainparams.GetConsensus(), connman,
interruptMsgProc);
}
if (pfrom->fDisconnect) {
return false;
}
// this maintains the order of responses
if (!pfrom->vRecvGetData.empty()) {
return true;
}
// Don't bother if send buffer is too full to respond anyway
if (pfrom->fPauseSend) {
return false;
}
std::list msgs;
{
LOCK(pfrom->cs_vProcessMsg);
if (pfrom->vProcessMsg.empty()) {
return false;
}
// Just take one message
msgs.splice(msgs.begin(), pfrom->vProcessMsg,
pfrom->vProcessMsg.begin());
pfrom->nProcessQueueSize -=
msgs.front().vRecv.size() + CMessageHeader::HEADER_SIZE;
pfrom->fPauseRecv =
pfrom->nProcessQueueSize > connman.GetReceiveFloodSize();
fMoreWork = !pfrom->vProcessMsg.empty();
}
CNetMessage &msg(msgs.front());
msg.SetVersion(pfrom->GetRecvVersion());
- // This is a new peer. Before doing anything, we need to detect what magic
- // the peer is using.
- if (pfrom->nVersion == 0 &&
- memcmp(msg.hdr.pchMessageStart, chainparams.DiskMagic(),
- CMessageHeader::MESSAGE_START_SIZE) == 0) {
- pfrom->fUsesCashMagic = false;
- }
-
// Scan for message start
- if (memcmp(msg.hdr.pchMessageStart, pfrom->GetMagic(chainparams),
+ if (memcmp(msg.hdr.pchMessageStart, chainparams.NetMagic(),
CMessageHeader::MESSAGE_START_SIZE) != 0) {
LogPrintf("PROCESSMESSAGE: INVALID MESSAGESTART %s peer=%d\n",
SanitizeString(msg.hdr.GetCommand()), pfrom->id);
pfrom->fDisconnect = true;
return false;
}
// Read header
CMessageHeader &hdr = msg.hdr;
- if (!hdr.IsValid(pfrom->GetMagic(chainparams))) {
+ if (!hdr.IsValid(chainparams.NetMagic())) {
LogPrintf("PROCESSMESSAGE: ERRORS IN HEADER %s peer=%d\n",
SanitizeString(hdr.GetCommand()), pfrom->id);
return fMoreWork;
}
std::string strCommand = hdr.GetCommand();
// Message size
unsigned int nMessageSize = hdr.nMessageSize;
// Checksum
CDataStream &vRecv = msg.vRecv;
const uint256 &hash = msg.GetMessageHash();
if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) !=
0) {
LogPrintf(
"%s(%s, %u bytes): CHECKSUM ERROR expected %s was %s\n", __func__,
SanitizeString(strCommand), nMessageSize,
HexStr(hash.begin(), hash.begin() + CMessageHeader::CHECKSUM_SIZE),
HexStr(hdr.pchChecksum,
hdr.pchChecksum + CMessageHeader::CHECKSUM_SIZE));
return fMoreWork;
}
// Process message
bool fRet = false;
try {
fRet = ProcessMessage(config, pfrom, strCommand, vRecv, msg.nTime,
chainparams, connman, interruptMsgProc);
if (interruptMsgProc) {
return false;
}
if (!pfrom->vRecvGetData.empty()) {
fMoreWork = true;
}
} catch (const std::ios_base::failure &e) {
connman.PushMessage(
pfrom, CNetMsgMaker(INIT_PROTO_VERSION)
.Make(NetMsgType::REJECT, strCommand, REJECT_MALFORMED,
std::string("error parsing message")));
if (strstr(e.what(), "end of data")) {
// Allow exceptions from under-length message on vRecv
LogPrintf(
"%s(%s, %u bytes): Exception '%s' caught, normally caused by a "
"message being shorter than its stated length\n",
__func__, SanitizeString(strCommand), nMessageSize, e.what());
} else if (strstr(e.what(), "size too large")) {
// Allow exceptions from over-long size
LogPrintf("%s(%s, %u bytes): Exception '%s' caught\n", __func__,
SanitizeString(strCommand), nMessageSize, e.what());
} else if (strstr(e.what(), "non-canonical ReadCompactSize()")) {
// Allow exceptions from non-canonical encoding
LogPrintf("%s(%s, %u bytes): Exception '%s' caught\n", __func__,
SanitizeString(strCommand), nMessageSize, e.what());
} else {
PrintExceptionContinue(&e, "ProcessMessages()");
}
} catch (const std::exception &e) {
PrintExceptionContinue(&e, "ProcessMessages()");
} catch (...) {
PrintExceptionContinue(nullptr, "ProcessMessages()");
}
if (!fRet) {
LogPrintf("%s(%s, %u bytes) FAILED peer=%d\n", __func__,
SanitizeString(strCommand), nMessageSize, pfrom->id);
}
LOCK(cs_main);
SendRejectsAndCheckIfBanned(pfrom, connman);
return fMoreWork;
}
class CompareInvMempoolOrder {
CTxMemPool *mp;
public:
CompareInvMempoolOrder(CTxMemPool *_mempool) { mp = _mempool; }
bool operator()(std::set::iterator a,
std::set::iterator b) {
/* As std::make_heap produces a max-heap, we want the entries with the
* fewest ancestors/highest fee to sort later. */
return mp->CompareDepthAndScore(*b, *a);
}
};
bool SendMessages(const Config &config, CNode *pto, CConnman &connman,
const std::atomic &interruptMsgProc) {
const Consensus::Params &consensusParams = Params().GetConsensus();
// Don't send anything until the version handshake is complete
if (!pto->fSuccessfullyConnected || pto->fDisconnect) {
return true;
}
// If we get here, the outgoing message serialization version is set and
// can't change.
const CNetMsgMaker msgMaker(pto->GetSendVersion());
//
// Message: ping
//
bool pingSend = false;
if (pto->fPingQueued) {
// RPC ping request by user
pingSend = true;
}
if (pto->nPingNonceSent == 0 &&
pto->nPingUsecStart + PING_INTERVAL * 1000000 < GetTimeMicros()) {
// Ping automatically sent as a latency probe & keepalive.
pingSend = true;
}
if (pingSend) {
uint64_t nonce = 0;
while (nonce == 0) {
GetRandBytes((uint8_t *)&nonce, sizeof(nonce));
}
pto->fPingQueued = false;
pto->nPingUsecStart = GetTimeMicros();
if (pto->nVersion > BIP0031_VERSION) {
pto->nPingNonceSent = nonce;
connman.PushMessage(pto, msgMaker.Make(NetMsgType::PING, nonce));
} else {
// Peer is too old to support ping command with nonce, pong will
// never arrive.
pto->nPingNonceSent = 0;
connman.PushMessage(pto, msgMaker.Make(NetMsgType::PING));
}
}
// Acquire cs_main for IsInitialBlockDownload() and CNodeState()
TRY_LOCK(cs_main, lockMain);
if (!lockMain) {
return true;
}
if (SendRejectsAndCheckIfBanned(pto, connman)) {
return true;
}
CNodeState &state = *State(pto->GetId());
// Address refresh broadcast
int64_t nNow = GetTimeMicros();
if (!IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) {
AdvertiseLocal(pto);
pto->nNextLocalAddrSend =
PoissonNextSend(nNow, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
}
//
// Message: addr
//
if (pto->nNextAddrSend < nNow) {
pto->nNextAddrSend =
PoissonNextSend(nNow, AVG_ADDRESS_BROADCAST_INTERVAL);
std::vector vAddr;
vAddr.reserve(pto->vAddrToSend.size());
for (const CAddress &addr : pto->vAddrToSend) {
if (!pto->addrKnown.contains(addr.GetKey())) {
pto->addrKnown.insert(addr.GetKey());
vAddr.push_back(addr);
// receiver rejects addr messages larger than 1000
if (vAddr.size() >= 1000) {
connman.PushMessage(pto,
msgMaker.Make(NetMsgType::ADDR, vAddr));
vAddr.clear();
}
}
}
pto->vAddrToSend.clear();
if (!vAddr.empty()) {
connman.PushMessage(pto, msgMaker.Make(NetMsgType::ADDR, vAddr));
}
// we only send the big addr message once
if (pto->vAddrToSend.capacity() > 40) {
pto->vAddrToSend.shrink_to_fit();
}
}
// Start block sync
if (pindexBestHeader == nullptr) {
pindexBestHeader = chainActive.Tip();
}
// Download if this is a nice peer, or we have no nice peers and this one
// might do.
bool fFetch = state.fPreferredDownload ||
(nPreferredDownload == 0 && !pto->fClient && !pto->fOneShot);
if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) {
// Only actively request headers from a single peer, unless we're close
// to today.
if ((nSyncStarted == 0 && fFetch) ||
pindexBestHeader->GetBlockTime() >
GetAdjustedTime() - 24 * 60 * 60) {
state.fSyncStarted = true;
nSyncStarted++;
const CBlockIndex *pindexStart = pindexBestHeader;
/**
* If possible, start at the block preceding the currently best
* known header. This ensures that we always get a non-empty list of
* headers back as long as the peer is up-to-date. With a non-empty
* response, we can initialise the peer's known best block. This
* wouldn't be possible if we requested starting at pindexBestHeader
* and got back an empty response.
*/
if (pindexStart->pprev) {
pindexStart = pindexStart->pprev;
}
LogPrint("net",
"initial getheaders (%d) to peer=%d (startheight:%d)\n",
pindexStart->nHeight, pto->id, pto->nStartingHeight);
connman.PushMessage(
pto,
msgMaker.Make(NetMsgType::GETHEADERS,
chainActive.GetLocator(pindexStart), uint256()));
}
}
// Resend wallet transactions that haven't gotten in a block yet
// Except during reindex, importing and IBD, when old wallet transactions
// become unconfirmed and spams other nodes.
if (!fReindex && !fImporting && !IsInitialBlockDownload()) {
GetMainSignals().Broadcast(nTimeBestReceived, &connman);
}
//
// Try sending block announcements via headers
//
{
// If we have less than MAX_BLOCKS_TO_ANNOUNCE in our list of block
// hashes we're relaying, and our peer wants headers announcements, then
// find the first header not yet known to our peer but would connect,
// and send. If no header would connect, or if we have too many blocks,
// or if the peer doesn't want headers, just add all to the inv queue.
LOCK(pto->cs_inventory);
std::vector vHeaders;
bool fRevertToInv =
((!state.fPreferHeaders &&
(!state.fPreferHeaderAndIDs ||
pto->vBlockHashesToAnnounce.size() > 1)) ||
pto->vBlockHashesToAnnounce.size() > MAX_BLOCKS_TO_ANNOUNCE);
// last header queued for delivery
const CBlockIndex *pBestIndex = nullptr;
// ensure pindexBestKnownBlock is up-to-date
ProcessBlockAvailability(pto->id);
if (!fRevertToInv) {
bool fFoundStartingHeader = false;
// Try to find first header that our peer doesn't have, and then
// send all headers past that one. If we come across an headers that
// aren't on chainActive, give up.
for (const uint256 &hash : pto->vBlockHashesToAnnounce) {
BlockMap::iterator mi = mapBlockIndex.find(hash);
assert(mi != mapBlockIndex.end());
const CBlockIndex *pindex = mi->second;
if (chainActive[pindex->nHeight] != pindex) {
// Bail out if we reorged away from this block
fRevertToInv = true;
break;
}
if (pBestIndex != nullptr && pindex->pprev != pBestIndex) {
// This means that the list of blocks to announce don't
// connect to each other. This shouldn't really be possible
// to hit during regular operation (because reorgs should
// take us to a chain that has some block not on the prior
// chain, which should be caught by the prior check), but
// one way this could happen is by using invalidateblock /
// reconsiderblock repeatedly on the tip, causing it to be
// added multiple times to vBlockHashesToAnnounce. Robustly
// deal with this rare situation by reverting to an inv.
fRevertToInv = true;
break;
}
pBestIndex = pindex;
if (fFoundStartingHeader) {
// add this to the headers message
vHeaders.push_back(pindex->GetBlockHeader());
} else if (PeerHasHeader(&state, pindex)) {
// Keep looking for the first new block.
continue;
} else if (pindex->pprev == nullptr ||
PeerHasHeader(&state, pindex->pprev)) {
// Peer doesn't have this header but they do have the prior
// one.
// Start sending headers.
fFoundStartingHeader = true;
vHeaders.push_back(pindex->GetBlockHeader());
} else {
// Peer doesn't have this header or the prior one --
// nothing will connect, so bail out.
fRevertToInv = true;
break;
}
}
}
if (!fRevertToInv && !vHeaders.empty()) {
if (vHeaders.size() == 1 && state.fPreferHeaderAndIDs) {
// We only send up to 1 block as header-and-ids, as otherwise
// probably means we're doing an initial-ish-sync or they're
// slow.
LogPrint("net", "%s sending header-and-ids %s to peer=%d\n",
__func__, vHeaders.front().GetHash().ToString(),
pto->id);
int nSendFlags = 0;
bool fGotBlockFromCache = false;
{
LOCK(cs_most_recent_block);
if (most_recent_block_hash == pBestIndex->GetBlockHash()) {
CBlockHeaderAndShortTxIDs cmpctblock(
*most_recent_block);
connman.PushMessage(
pto,
msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK,
cmpctblock));
fGotBlockFromCache = true;
}
}
if (!fGotBlockFromCache) {
CBlock block;
bool ret = ReadBlockFromDisk(block, pBestIndex, config);
assert(ret);
CBlockHeaderAndShortTxIDs cmpctblock(block);
connman.PushMessage(
pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK,
cmpctblock));
}
state.pindexBestHeaderSent = pBestIndex;
} else if (state.fPreferHeaders) {
if (vHeaders.size() > 1) {
LogPrint("net",
"%s: %u headers, range (%s, %s), to peer=%d\n",
__func__, vHeaders.size(),
vHeaders.front().GetHash().ToString(),
vHeaders.back().GetHash().ToString(), pto->id);
} else {
LogPrint("net", "%s: sending header %s to peer=%d\n",
__func__, vHeaders.front().GetHash().ToString(),
pto->id);
}
connman.PushMessage(
pto, msgMaker.Make(NetMsgType::HEADERS, vHeaders));
state.pindexBestHeaderSent = pBestIndex;
} else {
fRevertToInv = true;
}
}
if (fRevertToInv) {
// If falling back to using an inv, just try to inv the tip. The
// last entry in vBlockHashesToAnnounce was our tip at some point in
// the past.
if (!pto->vBlockHashesToAnnounce.empty()) {
const uint256 &hashToAnnounce =
pto->vBlockHashesToAnnounce.back();
BlockMap::iterator mi = mapBlockIndex.find(hashToAnnounce);
assert(mi != mapBlockIndex.end());
const CBlockIndex *pindex = mi->second;
// Warn if we're announcing a block that is not on the main
// chain. This should be very rare and could be optimized out.
// Just log for now.
if (chainActive[pindex->nHeight] != pindex) {
LogPrint("net",
"Announcing block %s not on main chain (tip=%s)\n",
hashToAnnounce.ToString(),
chainActive.Tip()->GetBlockHash().ToString());
}
// If the peer's chain has this block, don't inv it back.
if (!PeerHasHeader(&state, pindex)) {
pto->PushInventory(CInv(MSG_BLOCK, hashToAnnounce));
LogPrint("net", "%s: sending inv peer=%d hash=%s\n",
__func__, pto->id, hashToAnnounce.ToString());
}
}
}
pto->vBlockHashesToAnnounce.clear();
}
//
// Message: inventory
//
std::vector vInv;
{
LOCK(pto->cs_inventory);
vInv.reserve(std::max(pto->vInventoryBlockToSend.size(),
INVENTORY_BROADCAST_MAX));
// Add blocks
for (const uint256 &hash : pto->vInventoryBlockToSend) {
vInv.push_back(CInv(MSG_BLOCK, hash));
if (vInv.size() == MAX_INV_SZ) {
connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
}
pto->vInventoryBlockToSend.clear();
// Check whether periodic sends should happen
bool fSendTrickle = pto->fWhitelisted;
if (pto->nNextInvSend < nNow) {
fSendTrickle = true;
// Use half the delay for outbound peers, as there is less privacy
// concern for them.
pto->nNextInvSend = PoissonNextSend(
nNow, INVENTORY_BROADCAST_INTERVAL >> !pto->fInbound);
}
// Time to send but the peer has requested we not relay transactions.
if (fSendTrickle) {
LOCK(pto->cs_filter);
if (!pto->fRelayTxes) {
pto->setInventoryTxToSend.clear();
}
}
// Respond to BIP35 mempool requests
if (fSendTrickle && pto->fSendMempool) {
auto vtxinfo = mempool.infoAll();
pto->fSendMempool = false;
Amount filterrate(0);
{
LOCK(pto->cs_feeFilter);
filterrate = pto->minFeeFilter;
}
LOCK(pto->cs_filter);
for (const auto &txinfo : vtxinfo) {
const uint256 &txid = txinfo.tx->GetId();
CInv inv(MSG_TX, txid);
pto->setInventoryTxToSend.erase(txid);
if (filterrate != Amount(0)) {
if (txinfo.feeRate.GetFeePerK() < filterrate) {
continue;
}
}
if (pto->pfilter) {
if (!pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) {
continue;
}
}
pto->filterInventoryKnown.insert(txid);
vInv.push_back(inv);
if (vInv.size() == MAX_INV_SZ) {
connman.PushMessage(pto,
msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
}
pto->timeLastMempoolReq = GetTime();
}
// Determine transactions to relay
if (fSendTrickle) {
// Produce a vector with all candidates for sending
std::vector::iterator> vInvTx;
vInvTx.reserve(pto->setInventoryTxToSend.size());
for (std::set::iterator it =
pto->setInventoryTxToSend.begin();
it != pto->setInventoryTxToSend.end(); it++) {
vInvTx.push_back(it);
}
Amount filterrate(0);
{
LOCK(pto->cs_feeFilter);
filterrate = pto->minFeeFilter;
}
// Topologically and fee-rate sort the inventory we send for privacy
// and priority reasons. A heap is used so that not all items need
// sorting if only a few are being sent.
CompareInvMempoolOrder compareInvMempoolOrder(&mempool);
std::make_heap(vInvTx.begin(), vInvTx.end(),
compareInvMempoolOrder);
// No reason to drain out at many times the network's capacity,
// especially since we have many peers and some will draw much
// shorter delays.
unsigned int nRelayedTransactions = 0;
LOCK(pto->cs_filter);
while (!vInvTx.empty() &&
nRelayedTransactions < INVENTORY_BROADCAST_MAX) {
// Fetch the top element from the heap
std::pop_heap(vInvTx.begin(), vInvTx.end(),
compareInvMempoolOrder);
std::set::iterator it = vInvTx.back();
vInvTx.pop_back();
uint256 hash = *it;
// Remove it from the to-be-sent set
pto->setInventoryTxToSend.erase(it);
// Check if not in the filter already
if (pto->filterInventoryKnown.contains(hash)) {
continue;
}
// Not in the mempool anymore? don't bother sending it.
auto txinfo = mempool.info(hash);
if (!txinfo.tx) {
continue;
}
if (filterrate != Amount(0) &&
txinfo.feeRate.GetFeePerK() < filterrate) {
continue;
}
if (pto->pfilter &&
!pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) {
continue;
}
// Send
vInv.push_back(CInv(MSG_TX, hash));
nRelayedTransactions++;
{
// Expire old relay messages
while (!vRelayExpiration.empty() &&
vRelayExpiration.front().first < nNow) {
mapRelay.erase(vRelayExpiration.front().second);
vRelayExpiration.pop_front();
}
auto ret = mapRelay.insert(
std::make_pair(hash, std::move(txinfo.tx)));
if (ret.second) {
vRelayExpiration.push_back(std::make_pair(
nNow + 15 * 60 * 1000000, ret.first));
}
}
if (vInv.size() == MAX_INV_SZ) {
connman.PushMessage(pto,
msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
pto->filterInventoryKnown.insert(hash);
}
}
}
if (!vInv.empty()) {
connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
}
// Detect whether we're stalling
nNow = GetTimeMicros();
if (state.nStallingSince &&
state.nStallingSince < nNow - 1000000 * BLOCK_STALLING_TIMEOUT) {
// Stalling only triggers when the block download window cannot move.
// During normal steady state, the download window should be much larger
// than the to-be-downloaded set of blocks, so disconnection should only
// happen during initial block download.
LogPrintf("Peer=%d is stalling block download, disconnecting\n",
pto->id);
pto->fDisconnect = true;
return true;
}
// In case there is a block that has been in flight from this peer for 2 +
// 0.5 * N times the block interval (with N the number of peers from which
// we're downloading validated blocks), disconnect due to timeout. We
// compensate for other peers to prevent killing off peers due to our own
// downstream link being saturated. We only count validated in-flight blocks
// so peers can't advertise non-existing block hashes to unreasonably
// increase our timeout.
if (state.vBlocksInFlight.size() > 0) {
QueuedBlock &queuedBlock = state.vBlocksInFlight.front();
int nOtherPeersWithValidatedDownloads =
nPeersWithValidatedDownloads -
(state.nBlocksInFlightValidHeaders > 0);
if (nNow > state.nDownloadingSince +
consensusParams.nPowTargetSpacing *
(BLOCK_DOWNLOAD_TIMEOUT_BASE +
BLOCK_DOWNLOAD_TIMEOUT_PER_PEER *
nOtherPeersWithValidatedDownloads)) {
LogPrintf("Timeout downloading block %s from peer=%d, "
"disconnecting\n",
queuedBlock.hash.ToString(), pto->id);
pto->fDisconnect = true;
return true;
}
}
//
// Message: getdata (blocks)
//
std::vector vGetData;
if (!pto->fClient && (fFetch || !IsInitialBlockDownload()) &&
state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
std::vector vToDownload;
NodeId staller = -1;
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER -
state.nBlocksInFlight,
vToDownload, staller, consensusParams);
for (const CBlockIndex *pindex : vToDownload) {
uint32_t nFetchFlags =
GetFetchFlags(pto, pindex->pprev, consensusParams);
vGetData.push_back(
CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash()));
MarkBlockAsInFlight(config, pto->GetId(), pindex->GetBlockHash(),
consensusParams, pindex);
LogPrint("net", "Requesting block %s (%d) peer=%d\n",
pindex->GetBlockHash().ToString(), pindex->nHeight,
pto->id);
}
if (state.nBlocksInFlight == 0 && staller != -1) {
if (State(staller)->nStallingSince == 0) {
State(staller)->nStallingSince = nNow;
LogPrint("net", "Stall started peer=%d\n", staller);
}
}
}
//
// Message: getdata (non-blocks)
//
while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow) {
const CInv &inv = (*pto->mapAskFor.begin()).second;
if (!AlreadyHave(inv)) {
if (fDebug) {
LogPrint("net", "Requesting %s peer=%d\n", inv.ToString(),
pto->id);
}
vGetData.push_back(inv);
if (vGetData.size() >= 1000) {
connman.PushMessage(
pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
vGetData.clear();
}
} else {
// If we're not going to ask, don't expect a response.
pto->setAskFor.erase(inv.hash);
}
pto->mapAskFor.erase(pto->mapAskFor.begin());
}
if (!vGetData.empty()) {
connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
}
//
// Message: feefilter
//
// We don't want white listed peers to filter txs to us if we have
// -whitelistforcerelay
if (pto->nVersion >= FEEFILTER_VERSION &&
GetBoolArg("-feefilter", DEFAULT_FEEFILTER) &&
!(pto->fWhitelisted &&
GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY))) {
Amount currentFilter =
mempool
.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) *
1000000)
.GetFeePerK();
int64_t timeNow = GetTimeMicros();
if (timeNow > pto->nextSendTimeFeeFilter) {
static CFeeRate default_feerate =
CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
static FeeFilterRounder filterRounder(default_feerate);
Amount filterToSend = filterRounder.round(currentFilter);
// If we don't allow free transactions, then we always have a fee
// filter of at least minRelayTxFee
if (GetArg("-limitfreerelay", DEFAULT_LIMITFREERELAY) <= 0) {
filterToSend =
std::max(filterToSend, ::minRelayTxFee.GetFeePerK());
}
if (filterToSend != pto->lastSentFeeFilter) {
connman.PushMessage(
pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend));
pto->lastSentFeeFilter = filterToSend;
}
pto->nextSendTimeFeeFilter =
PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL);
}
// If the fee filter has changed substantially and it's still more than
// MAX_FEEFILTER_CHANGE_DELAY until scheduled broadcast, then move the
// broadcast to within MAX_FEEFILTER_CHANGE_DELAY.
else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 <
pto->nextSendTimeFeeFilter &&
(currentFilter < 3 * pto->lastSentFeeFilter / 4 ||
currentFilter > 4 * pto->lastSentFeeFilter / 3)) {
pto->nextSendTimeFeeFilter =
timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000;
}
}
return true;
}
class CNetProcessingCleanup {
public:
CNetProcessingCleanup() {}
~CNetProcessingCleanup() {
// orphan transactions
mapOrphanTransactions.clear();
mapOrphanTransactionsByPrev.clear();
}
} instance_of_cnetprocessingcleanup;
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 2ccb1eb96..4a3a15032 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -1,769 +1,768 @@
// 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.
#include "rpc/server.h"
#include "chainparams.h"
#include "clientversion.h"
#include "config.h"
#include "net.h"
#include "net_processing.h"
#include "netbase.h"
#include "policy/policy.h"
#include "protocol.h"
#include "sync.h"
#include "timedata.h"
#include "ui_interface.h"
#include "util.h"
#include "utilstrencodings.h"
#include "validation.h"
#include "version.h"
#include
static UniValue getconnectioncount(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
"getconnectioncount\n"
"\nReturns the number of connections to other nodes.\n"
"\nResult:\n"
"n (numeric) The connection count\n"
"\nExamples:\n" +
HelpExampleCli("getconnectioncount", "") +
HelpExampleRpc("getconnectioncount", ""));
if (!g_connman)
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
return (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL);
}
static UniValue ping(const Config &config, const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
"ping\n"
"\nRequests that a ping be sent to all other nodes, to measure "
"ping time.\n"
"Results provided in getpeerinfo, pingtime and pingwait fields are "
"decimal seconds.\n"
"Ping command is handled in queue with all other commands, so it "
"measures processing backlog, not just network ping.\n"
"\nExamples:\n" +
HelpExampleCli("ping", "") + HelpExampleRpc("ping", ""));
if (!g_connman)
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
// Request that each node send a ping during next message processing pass
g_connman->ForEachNode([](CNode *pnode) { pnode->fPingQueued = true; });
return NullUniValue;
}
static UniValue getpeerinfo(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"getpeerinfo\n"
"\nReturns data about each connected network node as a json array "
"of objects.\n"
"\nResult:\n"
"[\n"
" {\n"
" \"id\": n, (numeric) Peer index\n"
" \"addr\":\"host:port\", (string) The ip address and port "
"of the peer\n"
" \"addrlocal\":\"ip:port\", (string) local address\n"
" \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services "
"offered\n"
" \"relaytxes\":true|false, (boolean) Whether peer has asked "
"us to relay transactions to it\n"
" \"lastsend\": ttt, (numeric) The time in seconds "
"since epoch (Jan 1 1970 GMT) of the last send\n"
" \"lastrecv\": ttt, (numeric) The time in seconds "
"since epoch (Jan 1 1970 GMT) of the last receive\n"
" \"bytessent\": n, (numeric) The total bytes sent\n"
" \"bytesrecv\": n, (numeric) The total bytes "
"received\n"
" \"conntime\": ttt, (numeric) The connection time in "
"seconds since epoch (Jan 1 1970 GMT)\n"
" \"timeoffset\": ttt, (numeric) The time offset in "
"seconds\n"
" \"pingtime\": n, (numeric) ping time (if "
"available)\n"
" \"minping\": n, (numeric) minimum observed ping "
"time (if any at all)\n"
" \"pingwait\": n, (numeric) ping wait (if "
"non-zero)\n"
" \"version\": v, (numeric) The peer version, such "
"as 7001\n"
" \"subver\": \"/Satoshi:0.8.5/\", (string) The string "
"version\n"
" \"inbound\": true|false, (boolean) Inbound (true) or "
"Outbound (false)\n"
" \"addnode\": true|false, (boolean) Whether connection was "
"due to addnode and is using an addnode slot\n"
" \"startingheight\": n, (numeric) The starting height "
"(block) of the peer\n"
" \"banscore\": n, (numeric) The ban score\n"
" \"synced_headers\": n, (numeric) The last header we "
"have in common with this peer\n"
" \"synced_blocks\": n, (numeric) The last block we have "
"in common with this peer\n"
" \"inflight\": [\n"
" n, (numeric) The heights of blocks "
"we're currently asking from this peer\n"
" ...\n"
" ],\n"
" \"whitelisted\": true|false, (boolean) Whether the peer is "
"whitelisted\n"
" \"bytessent_per_msg\": {\n"
" \"addr\": n, (numeric) The total bytes sent "
"aggregated by message type\n"
" ...\n"
" },\n"
" \"bytesrecv_per_msg\": {\n"
" \"addr\": n, (numeric) The total bytes "
"received aggregated by message type\n"
" ...\n"
" }\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("getpeerinfo", "") +
HelpExampleRpc("getpeerinfo", ""));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::vector vstats;
g_connman->GetNodeStats(vstats);
UniValue ret(UniValue::VARR);
for (const CNodeStats &stats : vstats) {
UniValue obj(UniValue::VOBJ);
CNodeStateStats statestats;
bool fStateStats = GetNodeStateStats(stats.nodeid, statestats);
obj.push_back(Pair("id", stats.nodeid));
obj.push_back(Pair("addr", stats.addrName));
if (!(stats.addrLocal.empty())) {
obj.push_back(Pair("addrlocal", stats.addrLocal));
}
obj.push_back(Pair("services", strprintf("%016x", stats.nServices)));
obj.push_back(Pair("relaytxes", stats.fRelayTxes));
obj.push_back(Pair("lastsend", stats.nLastSend));
obj.push_back(Pair("lastrecv", stats.nLastRecv));
obj.push_back(Pair("bytessent", stats.nSendBytes));
obj.push_back(Pair("bytesrecv", stats.nRecvBytes));
obj.push_back(Pair("conntime", stats.nTimeConnected));
obj.push_back(Pair("timeoffset", stats.nTimeOffset));
if (stats.dPingTime > 0.0) {
obj.push_back(Pair("pingtime", stats.dPingTime));
}
if (stats.dMinPing < std::numeric_limits::max() / 1e6) {
obj.push_back(Pair("minping", stats.dMinPing));
}
if (stats.dPingWait > 0.0) {
obj.push_back(Pair("pingwait", stats.dPingWait));
}
obj.push_back(Pair("version", stats.nVersion));
// Use the sanitized form of subver here, to avoid tricksy remote peers
// from corrupting or modifying the JSON output by putting special
// characters in their ver message.
obj.push_back(Pair("subver", stats.cleanSubVer));
obj.push_back(Pair("inbound", stats.fInbound));
obj.push_back(Pair("addnode", stats.fAddnode));
obj.push_back(Pair("startingheight", stats.nStartingHeight));
if (fStateStats) {
obj.push_back(Pair("banscore", statestats.nMisbehavior));
obj.push_back(Pair("synced_headers", statestats.nSyncHeight));
obj.push_back(Pair("synced_blocks", statestats.nCommonHeight));
UniValue heights(UniValue::VARR);
for (int height : statestats.vHeightInFlight) {
heights.push_back(height);
}
obj.push_back(Pair("inflight", heights));
}
obj.push_back(Pair("whitelisted", stats.fWhitelisted));
- obj.push_back(Pair("cashmagic", stats.fUsesCashMagic));
UniValue sendPerMsgCmd(UniValue::VOBJ);
for (const mapMsgCmdSize::value_type &i : stats.mapSendBytesPerMsgCmd) {
if (i.second > 0) {
sendPerMsgCmd.push_back(Pair(i.first, i.second));
}
}
obj.push_back(Pair("bytessent_per_msg", sendPerMsgCmd));
UniValue recvPerMsgCmd(UniValue::VOBJ);
for (const mapMsgCmdSize::value_type &i : stats.mapRecvBytesPerMsgCmd) {
if (i.second > 0) {
recvPerMsgCmd.push_back(Pair(i.first, i.second));
}
}
obj.push_back(Pair("bytesrecv_per_msg", recvPerMsgCmd));
ret.push_back(obj);
}
return ret;
}
static UniValue addnode(const Config &config, const JSONRPCRequest &request) {
std::string strCommand;
if (request.params.size() == 2) strCommand = request.params[1].get_str();
if (request.fHelp || request.params.size() != 2 ||
(strCommand != "onetry" && strCommand != "add" &&
strCommand != "remove"))
throw std::runtime_error(
"addnode \"node\" \"add|remove|onetry\"\n"
"\nAttempts add or remove a node from the addnode list.\n"
"Or try a connection to a node once.\n"
"\nArguments:\n"
"1. \"node\" (string, required) The node (see getpeerinfo for "
"nodes)\n"
"2. \"command\" (string, required) 'add' to add a node to the "
"list, 'remove' to remove a node from the list, 'onetry' to try a "
"connection to the node once\n"
"\nExamples:\n" +
HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") +
HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\""));
if (!g_connman)
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
std::string strNode = request.params[0].get_str();
if (strCommand == "onetry") {
CAddress addr;
g_connman->OpenNetworkConnection(addr, false, nullptr, strNode.c_str());
return NullUniValue;
}
if (strCommand == "add") {
if (!g_connman->AddNode(strNode))
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: Node already added");
} else if (strCommand == "remove") {
if (!g_connman->RemoveAddedNode(strNode))
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node has not been added.");
}
return NullUniValue;
}
static UniValue disconnectnode(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() == 0 ||
request.params.size() >= 3) {
throw std::runtime_error(
"disconnectnode \"[address]\" [nodeid]\n"
"\nImmediately disconnects from the specified peer node.\n"
"\nStrictly one out of 'address' and 'nodeid' can be provided to "
"identify the node.\n"
"\nTo disconnect by nodeid, either set 'address' to the empty "
"string, or call using the named 'nodeid' argument only.\n"
"\nArguments:\n"
"1. \"address\" (string, optional) The IP address/port of the "
"node\n"
"2. \"nodeid\" (number, optional) The node ID (see "
"getpeerinfo for node IDs)\n"
"\nExamples:\n" +
HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleCli("disconnectnode", "\"\" 1") +
HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") +
HelpExampleRpc("disconnectnode", "\"\", 1"));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
bool success;
const UniValue &address_arg = request.params[0];
const UniValue &id_arg =
request.params.size() < 2 ? NullUniValue : request.params[1];
if (!address_arg.isNull() && id_arg.isNull()) {
/* handle disconnect-by-address */
success = g_connman->DisconnectNode(address_arg.get_str());
} else if (!id_arg.isNull() &&
(address_arg.isNull() ||
(address_arg.isStr() && address_arg.get_str().empty()))) {
/* handle disconnect-by-id */
NodeId nodeid = (NodeId)id_arg.get_int64();
success = g_connman->DisconnectNode(nodeid);
} else {
throw JSONRPCError(
RPC_INVALID_PARAMS,
"Only one of address and nodeid should be provided.");
}
if (!success) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED,
"Node not found in connected nodes");
}
return NullUniValue;
}
static UniValue getaddednodeinfo(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() > 1)
throw std::runtime_error(
"getaddednodeinfo ( \"node\" )\n"
"\nReturns information about the given added node, or all added "
"nodes\n"
"(note that onetry addnodes are not listed here)\n"
"\nArguments:\n"
"1. \"node\" (string, optional) If provided, return information "
"about this specific node, otherwise all nodes are returned.\n"
"\nResult:\n"
"[\n"
" {\n"
" \"addednode\" : \"192.168.0.201\", (string) The node ip "
"address or name (as provided to addnode)\n"
" \"connected\" : true|false, (boolean) If connected\n"
" \"addresses\" : [ (list of objects) Only "
"when connected = true\n"
" {\n"
" \"address\" : \"192.168.0.201:8333\", (string) The "
"bitcoin server IP and port we're connected to\n"
" \"connected\" : \"outbound\" (string) "
"connection, inbound or outbound\n"
" }\n"
" ]\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("getaddednodeinfo", "true") +
HelpExampleCli("getaddednodeinfo", "true \"192.168.0.201\"") +
HelpExampleRpc("getaddednodeinfo", "true, \"192.168.0.201\""));
if (!g_connman)
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
std::vector vInfo = g_connman->GetAddedNodeInfo();
if (request.params.size() == 1) {
bool found = false;
for (const AddedNodeInfo &info : vInfo) {
if (info.strAddedNode == request.params[0].get_str()) {
vInfo.assign(1, info);
found = true;
break;
}
}
if (!found) {
throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED,
"Error: Node has not been added.");
}
}
UniValue ret(UniValue::VARR);
for (const AddedNodeInfo &info : vInfo) {
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("addednode", info.strAddedNode));
obj.push_back(Pair("connected", info.fConnected));
UniValue addresses(UniValue::VARR);
if (info.fConnected) {
UniValue address(UniValue::VOBJ);
address.push_back(Pair("address", info.resolvedAddress.ToString()));
address.push_back(
Pair("connected", info.fInbound ? "inbound" : "outbound"));
addresses.push_back(address);
}
obj.push_back(Pair("addresses", addresses));
ret.push_back(obj);
}
return ret;
}
static UniValue getnettotals(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() > 0)
throw std::runtime_error(
"getnettotals\n"
"\nReturns information about network traffic, including bytes in, "
"bytes out,\n"
"and current time.\n"
"\nResult:\n"
"{\n"
" \"totalbytesrecv\": n, (numeric) Total bytes received\n"
" \"totalbytessent\": n, (numeric) Total bytes sent\n"
" \"timemillis\": t, (numeric) Current UNIX time in "
"milliseconds\n"
" \"uploadtarget\":\n"
" {\n"
" \"timeframe\": n, (numeric) Length of "
"the measuring timeframe in seconds\n"
" \"target\": n, (numeric) Target in "
"bytes\n"
" \"target_reached\": true|false, (boolean) True if "
"target is reached\n"
" \"serve_historical_blocks\": true|false, (boolean) True if "
"serving historical blocks\n"
" \"bytes_left_in_cycle\": t, (numeric) Bytes "
"left in current time cycle\n"
" \"time_left_in_cycle\": t (numeric) Seconds "
"left in current time cycle\n"
" }\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("getnettotals", "") +
HelpExampleRpc("getnettotals", ""));
if (!g_connman)
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("totalbytesrecv", g_connman->GetTotalBytesRecv()));
obj.push_back(Pair("totalbytessent", g_connman->GetTotalBytesSent()));
obj.push_back(Pair("timemillis", GetTimeMillis()));
UniValue outboundLimit(UniValue::VOBJ);
outboundLimit.push_back(
Pair("timeframe", g_connman->GetMaxOutboundTimeframe()));
outboundLimit.push_back(Pair("target", g_connman->GetMaxOutboundTarget()));
outboundLimit.push_back(
Pair("target_reached", g_connman->OutboundTargetReached(false)));
outboundLimit.push_back(Pair("serve_historical_blocks",
!g_connman->OutboundTargetReached(true)));
outboundLimit.push_back(
Pair("bytes_left_in_cycle", g_connman->GetOutboundTargetBytesLeft()));
outboundLimit.push_back(
Pair("time_left_in_cycle", g_connman->GetMaxOutboundTimeLeftInCycle()));
obj.push_back(Pair("uploadtarget", outboundLimit));
return obj;
}
static UniValue GetNetworksInfo() {
UniValue networks(UniValue::VARR);
for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast(n);
if (network == NET_UNROUTABLE) continue;
proxyType proxy;
UniValue obj(UniValue::VOBJ);
GetProxy(network, proxy);
obj.push_back(Pair("name", GetNetworkName(network)));
obj.push_back(Pair("limited", IsLimited(network)));
obj.push_back(Pair("reachable", IsReachable(network)));
obj.push_back(Pair("proxy", proxy.IsValid()
? proxy.proxy.ToStringIPPort()
: std::string()));
obj.push_back(
Pair("proxy_randomize_credentials", proxy.randomize_credentials));
networks.push_back(obj);
}
return networks;
}
static UniValue getnetworkinfo(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error(
"getnetworkinfo\n"
"Returns an object containing various state info regarding P2P "
"networking.\n"
"\nResult:\n"
"{\n"
" \"version\": xxxxx, (numeric) the server "
"version\n"
" \"subversion\": \"/Satoshi:x.x.x/\", (string) the server "
"subversion string\n"
" \"protocolversion\": xxxxx, (numeric) the protocol "
"version\n"
" \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services "
"we offer to the network\n"
" \"localrelay\": true|false, (bool) true if "
"transaction relay is requested from peers\n"
" \"timeoffset\": xxxxx, (numeric) the time "
"offset\n"
" \"connections\": xxxxx, (numeric) the number "
"of connections\n"
" \"networkactive\": true|false, (bool) whether p2p "
"networking is enabled\n"
" \"networks\": [ (array) information "
"per network\n"
" {\n"
" \"name\": \"xxx\", (string) network "
"(ipv4, ipv6 or onion)\n"
" \"limited\": true|false, (boolean) is the "
"network limited using -onlynet?\n"
" \"reachable\": true|false, (boolean) is the "
"network reachable?\n"
" \"proxy\": \"host:port\" (string) the proxy "
"that is used for this network, or empty if none\n"
" \"proxy_randomize_credentials\": true|false, (string) "
"Whether randomized credentials are used\n"
" }\n"
" ,...\n"
" ],\n"
" \"relayfee\": x.xxxxxxxx, (numeric) minimum "
"relay fee for non-free transactions in " +
CURRENCY_UNIT + "/kB\n"
" \"incrementalfee\": x.xxxxxxxx, "
"(numeric) minimum fee increment for mempool "
"limiting or BIP 125 replacement in " +
CURRENCY_UNIT + "/kB\n"
" \"localaddresses\": [ "
"(array) list of local addresses\n"
" {\n"
" \"address\": \"xxxx\", "
"(string) network address\n"
" \"port\": xxx, "
"(numeric) network port\n"
" \"score\": xxx "
"(numeric) relative score\n"
" }\n"
" ,...\n"
" ]\n"
" \"warnings\": \"...\" "
"(string) any network warnings\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("getnetworkinfo", "") +
HelpExampleRpc("getnetworkinfo", ""));
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("version", CLIENT_VERSION));
obj.push_back(Pair("subversion", userAgent(config)));
obj.push_back(Pair("protocolversion", PROTOCOL_VERSION));
if (g_connman)
obj.push_back(Pair("localservices",
strprintf("%016x", g_connman->GetLocalServices())));
obj.push_back(Pair("localrelay", fRelayTxes));
obj.push_back(Pair("timeoffset", GetTimeOffset()));
if (g_connman) {
obj.push_back(Pair("networkactive", g_connman->GetNetworkActive()));
obj.push_back(Pair("connections", (int)g_connman->GetNodeCount(
CConnman::CONNECTIONS_ALL)));
}
obj.push_back(Pair("networks", GetNetworksInfo()));
obj.push_back(
Pair("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())));
obj.push_back(Pair("incrementalfee",
ValueFromAmount(::incrementalRelayFee.GetFeePerK())));
UniValue localAddresses(UniValue::VARR);
{
LOCK(cs_mapLocalHost);
for (const std::pair &item : mapLocalHost) {
UniValue rec(UniValue::VOBJ);
rec.push_back(Pair("address", item.first.ToString()));
rec.push_back(Pair("port", item.second.nPort));
rec.push_back(Pair("score", item.second.nScore));
localAddresses.push_back(rec);
}
}
obj.push_back(Pair("localaddresses", localAddresses));
obj.push_back(Pair("warnings", GetWarnings("statusbar")));
return obj;
}
static UniValue setban(const Config &config, const JSONRPCRequest &request) {
std::string strCommand;
if (request.params.size() >= 2) {
strCommand = request.params[1].get_str();
}
if (request.fHelp || request.params.size() < 2 ||
(strCommand != "add" && strCommand != "remove")) {
throw std::runtime_error(
"setban \"subnet\" \"add|remove\" (bantime) (absolute)\n"
"\nAttempts add or remove a IP/Subnet from the banned list.\n"
"\nArguments:\n"
"1. \"subnet\" (string, required) The IP/Subnet (see "
"getpeerinfo for nodes ip) with a optional netmask (default is /32 "
"= single ip)\n"
"2. \"command\" (string, required) 'add' to add a IP/Subnet "
"to the list, 'remove' to remove a IP/Subnet from the list\n"
"3. \"bantime\" (numeric, optional) time in seconds how long "
"(or until when if [absolute] is set) the ip is banned (0 or empty "
"means using the default time of 24h which can also be overwritten "
"by the -bantime startup argument)\n"
"4. \"absolute\" (boolean, optional) If set, the bantime must "
"be a absolute timestamp in seconds since epoch (Jan 1 1970 GMT)\n"
"\nExamples:\n" +
HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") +
HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") +
HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400"));
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
CSubNet subNet;
CNetAddr netAddr;
bool isSubnet = false;
if (request.params[0].get_str().find("/") != std::string::npos) {
isSubnet = true;
}
if (!isSubnet) {
CNetAddr resolved;
LookupHost(request.params[0].get_str().c_str(), resolved, false);
netAddr = resolved;
} else {
LookupSubNet(request.params[0].get_str().c_str(), subNet);
}
if (!(isSubnet ? subNet.IsValid() : netAddr.IsValid())) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Invalid IP/Subnet");
}
if (strCommand == "add") {
if (isSubnet ? g_connman->IsBanned(subNet)
: g_connman->IsBanned(netAddr)) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED,
"Error: IP/Subnet already banned");
}
// Use standard bantime if not specified.
int64_t banTime = 0;
if (request.params.size() >= 3 && !request.params[2].isNull()) {
banTime = request.params[2].get_int64();
}
bool absolute = false;
if (request.params.size() == 4 && request.params[3].isTrue()) {
absolute = true;
}
isSubnet
? g_connman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute)
: g_connman->Ban(netAddr, BanReasonManuallyAdded, banTime,
absolute);
} else if (strCommand == "remove") {
if (!(isSubnet ? g_connman->Unban(subNet)
: g_connman->Unban(netAddr))) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET,
"Error: Unban failed. Requested address/subnet "
"was not previously banned.");
}
}
return NullUniValue;
}
static UniValue listbanned(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error("listbanned\n"
"\nList all banned IPs/Subnets.\n"
"\nExamples:\n" +
HelpExampleCli("listbanned", "") +
HelpExampleRpc("listbanned", ""));
if (!g_connman)
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
banmap_t banMap;
g_connman->GetBanned(banMap);
UniValue bannedAddresses(UniValue::VARR);
for (banmap_t::iterator it = banMap.begin(); it != banMap.end(); it++) {
CBanEntry banEntry = (*it).second;
UniValue rec(UniValue::VOBJ);
rec.push_back(Pair("address", (*it).first.ToString()));
rec.push_back(Pair("banned_until", banEntry.nBanUntil));
rec.push_back(Pair("ban_created", banEntry.nCreateTime));
rec.push_back(Pair("ban_reason", banEntry.banReasonToString()));
bannedAddresses.push_back(rec);
}
return bannedAddresses;
}
static UniValue clearbanned(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0)
throw std::runtime_error("clearbanned\n"
"\nClear all banned IPs.\n"
"\nExamples:\n" +
HelpExampleCli("clearbanned", "") +
HelpExampleRpc("clearbanned", ""));
if (!g_connman)
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
g_connman->ClearBanned();
return NullUniValue;
}
static UniValue setnetworkactive(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"setnetworkactive true|false\n"
"\nDisable/enable all p2p network activity.\n"
"\nArguments:\n"
"1. \"state\" (boolean, required) true to "
"enable networking, false to disable\n");
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
g_connman->SetNetworkActive(request.params[0].get_bool());
return g_connman->GetNetworkActive();
}
// clang-format off
static const CRPCCommand commands[] = {
// category name actor (function) okSafeMode
// ------------------- ------------------------ ---------------------- ----------
{ "network", "getconnectioncount", getconnectioncount, true, {} },
{ "network", "ping", ping, true, {} },
{ "network", "getpeerinfo", getpeerinfo, true, {} },
{ "network", "addnode", addnode, true, {"node","command"} },
{ "network", "disconnectnode", disconnectnode, true, {"address", "nodeid"} },
{ "network", "getaddednodeinfo", getaddednodeinfo, true, {"node"} },
{ "network", "getnettotals", getnettotals, true, {} },
{ "network", "getnetworkinfo", getnetworkinfo, true, {} },
{ "network", "setban", setban, true, {"subnet", "command", "bantime", "absolute"} },
{ "network", "listbanned", listbanned, true, {} },
{ "network", "clearbanned", clearbanned, true, {} },
{ "network", "setnetworkactive", setnetworkactive, true, {"state"} },
};
// clang-format on
void RegisterNetRPCCommands(CRPCTable &t) {
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}