diff --git a/src/net.cpp b/src/net.cpp index d2fd42a8f..b5927de40 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1,3095 +1,3099 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include "net.h" #include "addrman.h" #include "chainparams.h" #include "clientversion.h" #include "config.h" #include "consensus/consensus.h" #include "crypto/common.h" #include "crypto/sha256.h" #include "hash.h" #include "netbase.h" #include "primitives/transaction.h" #include "scheduler.h" #include "ui_interface.h" #include "utilstrencodings.h" #ifdef WIN32 #include #else #include #endif #ifdef USE_UPNP #include #include #include #include #endif #include // Dump addresses to peers.dat and banlist.dat every 15 minutes (900s) #define DUMP_ADDRESSES_INTERVAL 900 // We add a random period time (0 to 1 seconds) to feeler connections to prevent // synchronization. #define FEELER_SLEEP_WINDOW 1 // MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define // it as 0 #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL 0 #endif // MSG_DONTWAIT is not available on some platforms, if it doesn't exist define // it as 0 #if !defined(MSG_DONTWAIT) #define MSG_DONTWAIT 0 #endif // Fix for ancient MinGW versions, that don't have defined these in ws2tcpip.h. // Todo: Can be removed when our pull-tester is upgraded to a modern MinGW // version. #ifdef WIN32 #ifndef PROTECTION_LEVEL_UNRESTRICTED #define PROTECTION_LEVEL_UNRESTRICTED 10 #endif #ifndef IPV6_PROTECTION_LEVEL #define IPV6_PROTECTION_LEVEL 23 #endif #endif /** Used to pass flags to the Bind() function */ enum BindFlags { BF_NONE = 0, BF_EXPLICIT = (1U << 0), BF_REPORT_ERROR = (1U << 1), BF_WHITELIST = (1U << 2), }; const static std::string NET_MESSAGE_COMMAND_OTHER = "*other*"; // SHA256("netgroup")[0:8] static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("localhostnonce")[0:8] static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // // Global state variables // bool fDiscover = true; bool fListen = true; bool fRelayTxes = true; CCriticalSection cs_mapLocalHost; std::map mapLocalHost; static bool vfLimited[NET_MAX] = {}; limitedmap mapAlreadyAskedFor(MAX_INV_SZ); void CConnman::AddOneShot(const std::string &strDest) { LOCK(cs_vOneShots); vOneShots.push_back(strDest); } unsigned short GetListenPort() { return (unsigned short)(gArgs.GetArg("-port", Params().GetDefaultPort())); } // find 'best' local address for a particular peer bool GetLocal(CService &addr, const CNetAddr *paddrPeer) { if (!fListen) { return false; } int nBestScore = -1; int nBestReachability = -1; { LOCK(cs_mapLocalHost); for (const auto &entry : mapLocalHost) { int nScore = entry.second.nScore; int nReachability = entry.first.GetReachabilityFrom(paddrPeer); if (nReachability > nBestReachability || (nReachability == nBestReachability && nScore > nBestScore)) { addr = CService(entry.first, entry.second.nPort); nBestReachability = nReachability; nBestScore = nScore; } } } return nBestScore >= 0; } //! Convert the pnSeeds6 array into usable address objects. static std::vector convertSeed6(const std::vector &vSeedsIn) { // It'll only connect to one or two seed nodes because once it connects, // it'll get a pile of addresses with newer timestamps. Seed nodes are given // a random 'last seen time' of between one and two weeks ago. const int64_t nOneWeek = 7 * 24 * 60 * 60; std::vector vSeedsOut; vSeedsOut.reserve(vSeedsIn.size()); for (const auto &seed_in : vSeedsIn) { struct in6_addr ip; memcpy(&ip, seed_in.addr, sizeof(ip)); CAddress addr(CService(ip, seed_in.port), GetDesirableServiceFlags(NODE_NONE)); addr.nTime = GetTime() - GetRand(nOneWeek) - nOneWeek; vSeedsOut.push_back(addr); } return vSeedsOut; } // Get best local address for a particular peer as a CAddress. Otherwise, return // the unroutable 0.0.0.0 but filled in with the normal parameters, since the IP // may be changed to a useful one by discovery. CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices) { CAddress ret(CService(CNetAddr(), GetListenPort()), nLocalServices); CService addr; if (GetLocal(addr, paddrPeer)) { ret = CAddress(addr, nLocalServices); } ret.nTime = GetAdjustedTime(); return ret; } static int GetnScore(const CService &addr) { LOCK(cs_mapLocalHost); if (mapLocalHost.count(addr) == LOCAL_NONE) { return 0; } return mapLocalHost[addr].nScore; } // Is our peer's addrLocal potentially useful as an external IP source? bool IsPeerAddrLocalGood(CNode *pnode) { CService addrLocal = pnode->GetAddrLocal(); return fDiscover && pnode->addr.IsRoutable() && addrLocal.IsRoutable() && !IsLimited(addrLocal.GetNetwork()); } // Pushes our own address to a peer. void AdvertiseLocal(CNode *pnode) { if (fListen && pnode->fSuccessfullyConnected) { CAddress addrLocal = GetLocalAddress(&pnode->addr, pnode->GetLocalServices()); // If 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(BCLog::NET, "AdvertiseLocal: advertising address %s\n", addrLocal.ToString()); FastRandomContext insecure_rand; pnode->PushAddress(addrLocal, insecure_rand); } } } // Learn a new local address. bool AddLocal(const CService &addr, int nScore) { if (!addr.IsRoutable()) { return false; } if (!fDiscover && nScore < LOCAL_MANUAL) { return false; } if (IsLimited(addr)) { return false; } LogPrintf("AddLocal(%s,%i)\n", addr.ToString(), nScore); { LOCK(cs_mapLocalHost); bool fAlready = mapLocalHost.count(addr) > 0; LocalServiceInfo &info = mapLocalHost[addr]; if (!fAlready || nScore >= info.nScore) { info.nScore = nScore + (fAlready ? 1 : 0); info.nPort = addr.GetPort(); } } return true; } bool AddLocal(const CNetAddr &addr, int nScore) { return AddLocal(CService(addr, GetListenPort()), nScore); } void RemoveLocal(const CService &addr) { LOCK(cs_mapLocalHost); LogPrintf("RemoveLocal(%s)\n", addr.ToString()); mapLocalHost.erase(addr); } /** * Make a particular network entirely off-limits (no automatic connects to it). */ void SetLimited(enum Network net, bool fLimited) { if (net == NET_UNROUTABLE || net == NET_INTERNAL) { return; } LOCK(cs_mapLocalHost); vfLimited[net] = fLimited; } bool IsLimited(enum Network net) { LOCK(cs_mapLocalHost); return vfLimited[net]; } bool IsLimited(const CNetAddr &addr) { return IsLimited(addr.GetNetwork()); } /** vote for a local address */ bool SeenLocal(const CService &addr) { LOCK(cs_mapLocalHost); if (mapLocalHost.count(addr) == 0) { return false; } mapLocalHost[addr].nScore++; return true; } /** check whether a given address is potentially local */ bool IsLocal(const CService &addr) { LOCK(cs_mapLocalHost); return mapLocalHost.count(addr) > 0; } /** check whether a given network is one we can probably connect to */ bool IsReachable(enum Network net) { LOCK(cs_mapLocalHost); return !vfLimited[net]; } /** check whether a given address is in a network we can probably connect to */ bool IsReachable(const CNetAddr &addr) { enum Network net = addr.GetNetwork(); return IsReachable(net); } CNode *CConnman::FindNode(const CNetAddr &ip) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (static_cast(pnode->addr) == ip) { return pnode; } } return nullptr; } CNode *CConnman::FindNode(const CSubNet &subNet) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (subNet.Match(static_cast(pnode->addr))) { return pnode; } } return nullptr; } CNode *CConnman::FindNode(const std::string &addrName) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (pnode->GetAddrName() == addrName) { return pnode; } } return nullptr; } CNode *CConnman::FindNode(const CService &addr) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (static_cast(pnode->addr) == addr) { return pnode; } } return nullptr; } bool CConnman::CheckIncomingNonce(uint64_t nonce) { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (!pnode->fSuccessfullyConnected && !pnode->fInbound && pnode->GetLocalNonce() == nonce) return false; } return true; } /** Get the bind address for a socket as CAddress */ static CAddress GetBindAddress(SOCKET sock) { CAddress addr_bind; struct sockaddr_storage sockaddr_bind; socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); if (sock != INVALID_SOCKET) { if (!getsockname(sock, (struct sockaddr *)&sockaddr_bind, &sockaddr_bind_len)) { addr_bind.SetSockAddr((const struct sockaddr *)&sockaddr_bind); } else { LogPrint(BCLog::NET, "Warning: getsockname failed\n"); } } return addr_bind; } CNode *CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure) { if (pszDest == nullptr) { if (IsLocal(addrConnect)) { return nullptr; } // Look for an existing connection CNode *pnode = FindNode(static_cast(addrConnect)); if (pnode) { LogPrintf("Failed to open new connection, already connected\n"); return nullptr; } } /// debug print LogPrint(BCLog::NET, "trying connection %s lastseen=%.1fhrs\n", pszDest ? pszDest : addrConnect.ToString(), pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime) / 3600.0); // Resolve const int default_port = Params().GetDefaultPort(); if (pszDest) { std::vector resolved; if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) { addrConnect = CAddress(resolved[GetRand(resolved.size())], NODE_NONE); if (!addrConnect.IsValid()) { LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToString(), pszDest); return nullptr; } // It is possible that we already have a connection to the IP/port // pszDest resolved to. In that case, drop the connection that was // just created, and return the existing CNode instead. Also store // the name we used to connect in that CNode, so that future // FindNode() calls to that name catch this early. LOCK(cs_vNodes); CNode *pnode = FindNode(static_cast(addrConnect)); if (pnode) { pnode->MaybeSetAddrName(std::string(pszDest)); LogPrintf("Failed to open new connection, already connected\n"); return nullptr; } } } // Connect bool connected = false; SOCKET hSocket = INVALID_SOCKET; proxyType proxy; if (addrConnect.IsValid()) { bool proxyConnectionFailed = false; if (GetProxy(addrConnect.GetNetwork(), proxy)) { hSocket = CreateSocket(proxy.proxy); if (hSocket == INVALID_SOCKET) { return nullptr; } connected = ConnectThroughProxy( proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), hSocket, nConnectTimeout, &proxyConnectionFailed); } else { // no proxy needed (none set for target network) hSocket = CreateSocket(addrConnect); if (hSocket == INVALID_SOCKET) { return nullptr; } connected = ConnectSocketDirectly(addrConnect, hSocket, nConnectTimeout); } if (!proxyConnectionFailed) { // If a connection to the node was attempted, and failure (if any) // is not caused by a problem connecting to the proxy, mark this as // an attempt. addrman.Attempt(addrConnect, fCountFailure); } } else if (pszDest && GetNameProxy(proxy)) { hSocket = CreateSocket(proxy.proxy); if (hSocket == INVALID_SOCKET) { return nullptr; } std::string host; int port = default_port; SplitHostPort(std::string(pszDest), port, host); connected = ConnectThroughProxy(proxy, host, port, hSocket, nConnectTimeout, nullptr); } if (!connected) { CloseSocket(hSocket); return nullptr; } // Add node NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE) .Write(id) .Finalize(); CAddress addr_bind = GetBindAddress(hSocket); CNode *pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false); pnode->AddRef(); return pnode; } void CConnman::DumpBanlist() { // Clean unused entries (if bantime has expired) SweepBanned(); if (!BannedSetIsDirty()) { return; } int64_t nStart = GetTimeMillis(); CBanDB bandb(config->GetChainParams()); banmap_t banmap; GetBanned(banmap); if (bandb.Write(banmap)) { SetBannedSetDirty(false); } LogPrint(BCLog::NET, "Flushed %d banned node ips/subnets to banlist.dat %dms\n", banmap.size(), GetTimeMillis() - nStart); } void CNode::CloseSocketDisconnect() { fDisconnect = true; LOCK(cs_hSocket); if (hSocket != INVALID_SOCKET) { LogPrint(BCLog::NET, "disconnecting peer=%d\n", id); CloseSocket(hSocket); } } void CConnman::ClearBanned() { { LOCK(cs_setBanned); setBanned.clear(); setBannedIsDirty = true; } // Store banlist to disk. DumpBanlist(); if (clientInterface) { clientInterface->BannedListChanged(); } } bool CConnman::IsBanned(CNetAddr ip) { LOCK(cs_setBanned); for (const auto &it : setBanned) { CSubNet subNet = it.first; CBanEntry banEntry = it.second; if (subNet.Match(ip) && GetTime() < banEntry.nBanUntil) { return true; } } return false; } bool CConnman::IsBanned(CSubNet subnet) { LOCK(cs_setBanned); banmap_t::iterator i = setBanned.find(subnet); if (i != setBanned.end()) { CBanEntry banEntry = (*i).second; if (GetTime() < banEntry.nBanUntil) { return true; } } return false; } void CConnman::Ban(const CNetAddr &addr, const BanReason &banReason, int64_t bantimeoffset, bool sinceUnixEpoch) { CSubNet subNet(addr); Ban(subNet, banReason, bantimeoffset, sinceUnixEpoch); } void CConnman::Ban(const CSubNet &subNet, const BanReason &banReason, int64_t bantimeoffset, bool sinceUnixEpoch) { CBanEntry banEntry(GetTime()); banEntry.banReason = banReason; if (bantimeoffset <= 0) { bantimeoffset = gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME); sinceUnixEpoch = false; } banEntry.nBanUntil = (sinceUnixEpoch ? 0 : GetTime()) + bantimeoffset; { LOCK(cs_setBanned); if (setBanned[subNet].nBanUntil < banEntry.nBanUntil) { setBanned[subNet] = banEntry; setBannedIsDirty = true; } else { return; } } if (clientInterface) { clientInterface->BannedListChanged(); } { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (subNet.Match(static_cast(pnode->addr))) { pnode->fDisconnect = true; } } } if (banReason == BanReasonManuallyAdded) { // Store banlist to disk immediately if user requested ban. DumpBanlist(); } } bool CConnman::Unban(const CNetAddr &addr) { CSubNet subNet(addr); return Unban(subNet); } bool CConnman::Unban(const CSubNet &subNet) { { LOCK(cs_setBanned); if (!setBanned.erase(subNet)) { return false; } setBannedIsDirty = true; } if (clientInterface) { clientInterface->BannedListChanged(); } // Store banlist to disk immediately. DumpBanlist(); return true; } void CConnman::GetBanned(banmap_t &banMap) { LOCK(cs_setBanned); // Sweep the banlist so expired bans are not returned SweepBanned(); // Create a thread safe copy. banMap = setBanned; } void CConnman::SetBanned(const banmap_t &banMap) { LOCK(cs_setBanned); setBanned = banMap; setBannedIsDirty = true; } void CConnman::SweepBanned() { int64_t now = GetTime(); bool notifyUI = false; { LOCK(cs_setBanned); banmap_t::iterator it = setBanned.begin(); while (it != setBanned.end()) { CSubNet subNet = (*it).first; CBanEntry banEntry = (*it).second; if (now > banEntry.nBanUntil) { setBanned.erase(it++); setBannedIsDirty = true; notifyUI = true; LogPrint( BCLog::NET, "%s: Removed banned node ip/subnet from banlist.dat: %s\n", __func__, subNet.ToString()); } else { ++it; } } } // update UI if (notifyUI && clientInterface) { clientInterface->BannedListChanged(); } } bool CConnman::BannedSetIsDirty() { LOCK(cs_setBanned); return setBannedIsDirty; } void CConnman::SetBannedSetDirty(bool dirty) { // Reuse setBanned lock for the isDirty flag. LOCK(cs_setBanned); setBannedIsDirty = dirty; } bool CConnman::IsWhitelistedRange(const CNetAddr &addr) { for (const CSubNet &subnet : vWhitelistedRange) { if (subnet.Match(addr)) { return true; } } return false; } std::string CNode::GetAddrName() const { LOCK(cs_addrName); return addrName; } void CNode::MaybeSetAddrName(const std::string &addrNameIn) { LOCK(cs_addrName); if (addrName.empty()) { addrName = addrNameIn; } } CService CNode::GetAddrLocal() const { LOCK(cs_addrLocal); return addrLocal; } void CNode::SetAddrLocal(const CService &addrLocalIn) { LOCK(cs_addrLocal); if (addrLocal.IsValid()) { error("Addr local already set for node: %i. Refusing to change from %s " "to %s", id, addrLocal.ToString(), addrLocalIn.ToString()); } else { addrLocal = addrLocalIn; } } void CNode::copyStats(CNodeStats &stats) { stats.nodeid = this->GetId(); stats.nServices = nServices; stats.addr = addr; stats.addrBind = addrBind; { LOCK(cs_filter); stats.fRelayTxes = fRelayTxes; } stats.nLastSend = nLastSend; stats.nLastRecv = nLastRecv; stats.nTimeConnected = nTimeConnected; stats.nTimeOffset = nTimeOffset; stats.addrName = GetAddrName(); stats.nVersion = nVersion; { LOCK(cs_SubVer); stats.cleanSubVer = cleanSubVer; } stats.fInbound = fInbound; stats.m_manual_connection = m_manual_connection; stats.nStartingHeight = nStartingHeight; { LOCK(cs_vSend); stats.mapSendBytesPerMsgCmd = mapSendBytesPerMsgCmd; stats.nSendBytes = nSendBytes; } { LOCK(cs_vRecv); stats.mapRecvBytesPerMsgCmd = mapRecvBytesPerMsgCmd; stats.nRecvBytes = nRecvBytes; } stats.fWhitelisted = fWhitelisted; // It is common for nodes with good ping times to suddenly become lagged, // due to a new block arriving or other large transfer. Merely reporting // pingtime might fool the caller into thinking the node was still // responsive, since pingtime does not update until the ping is complete, // which might take a while. So, if a ping is taking an unusually long time // in flight, the caller can immediately detect that this is happening. int64_t nPingUsecWait = 0; if ((0 != nPingNonceSent) && (0 != nPingUsecStart)) { nPingUsecWait = GetTimeMicros() - nPingUsecStart; } // Raw ping time is in microseconds, but show it to user as whole seconds // (Bitcoin users should be well used to small numbers with many decimal // places by now :) stats.dPingTime = ((double(nPingUsecTime)) / 1e6); stats.dMinPing = ((double(nMinPingUsecTime)) / 1e6); stats.dPingWait = ((double(nPingUsecWait)) / 1e6); // Leave string empty if addrLocal invalid (not filled in yet) CService addrLocalUnlocked = GetAddrLocal(); stats.addrLocal = addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : ""; } static bool IsOversizedMessage(const Config &config, const CNetMessage &msg) { if (!msg.in_data) { // Header only, cannot be oversized. return false; } return msg.hdr.IsOversized(config); } bool CNode::ReceiveMsgBytes(const Config &config, const char *pch, uint32_t nBytes, bool &complete) { complete = false; int64_t nTimeMicros = GetTimeMicros(); LOCK(cs_vRecv); nLastRecv = nTimeMicros / 1000000; nRecvBytes += nBytes; while (nBytes > 0) { // Get current incomplete message, or create a new one. if (vRecvMsg.empty() || vRecvMsg.back().complete()) { vRecvMsg.push_back(CNetMessage(config.GetChainParams().NetMagic(), SER_NETWORK, INIT_PROTO_VERSION)); } CNetMessage &msg = vRecvMsg.back(); // Absorb network data. int handled; if (!msg.in_data) { handled = msg.readHeader(config, pch, nBytes); } else { handled = msg.readData(pch, nBytes); } if (handled < 0) { return false; } if (IsOversizedMessage(config, msg)) { LogPrint(BCLog::NET, "Oversized message from peer=%i, disconnecting\n", GetId()); return false; } pch += handled; nBytes -= handled; if (msg.complete()) { // Store received bytes per message command to prevent a memory DOS, // only allow valid commands. mapMsgCmdSize::iterator i = mapRecvBytesPerMsgCmd.find(msg.hdr.pchCommand.data()); if (i == mapRecvBytesPerMsgCmd.end()) { i = mapRecvBytesPerMsgCmd.find(NET_MESSAGE_COMMAND_OTHER); } assert(i != mapRecvBytesPerMsgCmd.end()); i->second += msg.hdr.nMessageSize + CMessageHeader::HEADER_SIZE; msg.nTime = nTimeMicros; complete = true; } } return true; } void CNode::SetSendVersion(int nVersionIn) { // Send version may only be changed in the version message, and only one // version message is allowed per session. We can therefore treat this value // as const and even atomic as long as it's only used once a version message // has been successfully processed. Any attempt to set this twice is an // error. if (nSendVersion != 0) { error("Send version already set for node: %i. Refusing to change from " "%i to %i", id, nSendVersion, nVersionIn); } else { nSendVersion = nVersionIn; } } int CNode::GetSendVersion() const { // The send version should always be explicitly set to INIT_PROTO_VERSION // rather than using this value until SetSendVersion has been called. if (nSendVersion == 0) { error("Requesting unset send version for node: %i. Using %i", id, INIT_PROTO_VERSION); return INIT_PROTO_VERSION; } return nSendVersion; } int CNetMessage::readHeader(const Config &config, const char *pch, uint32_t nBytes) { // copy data to temporary parsing buffer uint32_t nRemaining = 24 - nHdrPos; uint32_t nCopy = std::min(nRemaining, nBytes); memcpy(&hdrbuf[nHdrPos], pch, nCopy); nHdrPos += nCopy; // if header incomplete, exit if (nHdrPos < 24) { return nCopy; } // deserialize to CMessageHeader try { hdrbuf >> hdr; } catch (const std::exception &) { return -1; } // Reject oversized messages if (hdr.IsOversized(config)) { LogPrint(BCLog::NET, "Oversized header detected\n"); return -1; } // switch state to reading message data in_data = true; return nCopy; } int CNetMessage::readData(const char *pch, uint32_t nBytes) { unsigned int nRemaining = hdr.nMessageSize - nDataPos; unsigned int nCopy = std::min(nRemaining, nBytes); if (vRecv.size() < nDataPos + nCopy) { // Allocate up to 256 KiB ahead, but never more than the total message // size. vRecv.resize(std::min(hdr.nMessageSize, nDataPos + nCopy + 256 * 1024)); } hasher.Write((const uint8_t *)pch, nCopy); memcpy(&vRecv[nDataPos], pch, nCopy); nDataPos += nCopy; return nCopy; } const uint256 &CNetMessage::GetMessageHash() const { assert(complete()); if (data_hash.IsNull()) { hasher.Finalize(data_hash.begin()); } return data_hash; } // requires LOCK(cs_vSend) size_t CConnman::SocketSendData(CNode *pnode) const { AssertLockHeld(pnode->cs_vSend); size_t nSentSize = 0; size_t nMsgCount = 0; for (const auto &data : pnode->vSendMsg) { assert(data.size() > pnode->nSendOffset); int nBytes = 0; { LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) { break; } nBytes = send(pnode->hSocket, reinterpret_cast(data.data()) + pnode->nSendOffset, data.size() - pnode->nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT); } if (nBytes == 0) { // couldn't send anything at all break; } if (nBytes < 0) { // error int nErr = WSAGetLastError(); if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) { LogPrintf("socket send error %s\n", NetworkErrorString(nErr)); pnode->CloseSocketDisconnect(); } break; } assert(nBytes > 0); pnode->nLastSend = GetSystemTimeInSeconds(); pnode->nSendBytes += nBytes; pnode->nSendOffset += nBytes; nSentSize += nBytes; if (pnode->nSendOffset != data.size()) { // could not send full message; stop sending more break; } pnode->nSendOffset = 0; pnode->nSendSize -= data.size(); pnode->fPauseSend = pnode->nSendSize > nSendBufferMaxSize; nMsgCount++; } pnode->vSendMsg.erase(pnode->vSendMsg.begin(), pnode->vSendMsg.begin() + nMsgCount); if (pnode->vSendMsg.empty()) { assert(pnode->nSendOffset == 0); assert(pnode->nSendSize == 0); } return nSentSize; } struct NodeEvictionCandidate { NodeId id; int64_t nTimeConnected; int64_t nMinPingUsecTime; int64_t nLastBlockTime; int64_t nLastTXTime; bool fRelevantServices; bool fRelayTxes; bool fBloomFilter; CAddress addr; uint64_t nKeyedNetGroup; }; static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nMinPingUsecTime > b.nMinPingUsecTime; } static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nTimeConnected > b.nTimeConnected; } static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nKeyedNetGroup < b.nKeyedNetGroup; } static bool CompareNodeBlockTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { // There is a fall-through here because it is common for a node to have many // peers which have not yet relayed a block. if (a.nLastBlockTime != b.nLastBlockTime) { return a.nLastBlockTime < b.nLastBlockTime; } if (a.fRelevantServices != b.fRelevantServices) { return b.fRelevantServices; } return a.nTimeConnected > b.nTimeConnected; } static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { // There is a fall-through here because it is common for a node to have more // than a few peers that have not yet relayed txn. if (a.nLastTXTime != b.nLastTXTime) { return a.nLastTXTime < b.nLastTXTime; } if (a.fRelayTxes != b.fRelayTxes) { return b.fRelayTxes; } if (a.fBloomFilter != b.fBloomFilter) { return a.fBloomFilter; } return a.nTimeConnected > b.nTimeConnected; } //! Sort an array by the specified comparator, then erase the last K elements. template static void EraseLastKElements(std::vector &elements, Comparator comparator, size_t k) { std::sort(elements.begin(), elements.end(), comparator); size_t eraseSize = std::min(k, elements.size()); elements.erase(elements.end() - eraseSize, elements.end()); } /** * Try to find a connection to evict when the node is full. * Extreme care must be taken to avoid opening the node to attacker triggered * network partitioning. * The strategy used here is to protect a small number of peers for each of * several distinct characteristics which are difficult to forge. In order to * partition a node the attacker must be simultaneously better at all of them * than honest peers. */ bool CConnman::AttemptToEvictConnection() { std::vector vEvictionCandidates; { LOCK(cs_vNodes); for (CNode *node : vNodes) { if (node->fWhitelisted || !node->fInbound || node->fDisconnect) { continue; } NodeEvictionCandidate candidate = { node->GetId(), node->nTimeConnected, node->nMinPingUsecTime, node->nLastBlockTime, node->nLastTXTime, HasAllDesirableServiceFlags(node->nServices), node->fRelayTxes, node->pfilter != nullptr, node->addr, node->nKeyedNetGroup}; vEvictionCandidates.push_back(candidate); } } // Protect connections with certain characteristics // Deterministically select 4 peers to protect by netgroup. // An attacker cannot predict which netgroups will be protected EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4); // Protect the 8 nodes with the lowest minimum ping time. // An attacker cannot manipulate this metric without physically moving nodes // closer to the target. EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8); // Protect 4 nodes that most recently sent us transactions. // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4); // Protect 4 nodes that most recently sent us blocks. // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4); // Protect the half of the remaining nodes which have been connected the // longest. This replicates the non-eviction implicit behavior, and // precludes attacks that start later. EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, vEvictionCandidates.size() / 2); if (vEvictionCandidates.empty()) { return false; } // Identify the network group with the most connections and youngest member. // (vEvictionCandidates is already sorted by reverse connect time) uint64_t naMostConnections; unsigned int nMostConnections = 0; int64_t nMostConnectionsTime = 0; std::map> mapNetGroupNodes; for (const NodeEvictionCandidate &node : vEvictionCandidates) { std::vector &group = mapNetGroupNodes[node.nKeyedNetGroup]; group.push_back(node); int64_t grouptime = group[0].nTimeConnected; size_t group_size = group.size(); if (group_size > nMostConnections || (group_size == nMostConnections && grouptime > nMostConnectionsTime)) { nMostConnections = group_size; nMostConnectionsTime = grouptime; naMostConnections = node.nKeyedNetGroup; } } // Reduce to the network group with the most connections vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]); // Disconnect from the network group with the most connections NodeId evicted = vEvictionCandidates.front().id; LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (pnode->GetId() == evicted) { pnode->fDisconnect = true; return true; } } return false; } void CConnman::AcceptConnection(const ListenSocket &hListenSocket) { struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr *)&sockaddr, &len); CAddress addr; int nInbound = 0; int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler); if (hSocket != INVALID_SOCKET) { if (!addr.SetSockAddr((const struct sockaddr *)&sockaddr)) { LogPrintf("Warning: Unknown socket family\n"); } } bool whitelisted = hListenSocket.whitelisted || IsWhitelistedRange(addr); { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (pnode->fInbound) { nInbound++; } } } if (hSocket == INVALID_SOCKET) { int nErr = WSAGetLastError(); if (nErr != WSAEWOULDBLOCK) { LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); } return; } if (!fNetworkActive) { LogPrintf("connection from %s dropped: not accepting new connections\n", addr.ToString()); CloseSocket(hSocket); return; } if (!IsSelectableSocket(hSocket)) { LogPrintf("connection from %s dropped: non-selectable socket\n", addr.ToString()); CloseSocket(hSocket); return; } // According to the internet TCP_NODELAY is not carried into accepted // sockets on all platforms. Set it again here just to be sure. SetSocketNoDelay(hSocket); if (IsBanned(addr) && !whitelisted) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); CloseSocket(hSocket); return; } if (nInbound >= nMaxInbound) { if (!AttemptToEvictConnection()) { // No connection to evict, disconnect the new connection LogPrint(BCLog::NET, "failed to find an eviction candidate - " "connection dropped (full)\n"); CloseSocket(hSocket); return; } } NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE) .Write(id) .Finalize(); CAddress addr_bind = GetBindAddress(hSocket); CNode *pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true); pnode->AddRef(); pnode->fWhitelisted = whitelisted; m_msgproc->InitializeNode(*config, pnode); LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString()); { LOCK(cs_vNodes); vNodes.push_back(pnode); } } void CConnman::ThreadSocketHandler() { unsigned int nPrevNodeCount = 0; while (!interruptNet) { // // Disconnect nodes // { LOCK(cs_vNodes); if (!fNetworkActive) { // Disconnect any connected nodes for (CNode *pnode : vNodes) { if (!pnode->fDisconnect) { LogPrint(BCLog::NET, "Network not active, dropping peer=%d\n", pnode->GetId()); pnode->fDisconnect = true; } } } // Disconnect unused nodes std::vector vNodesCopy = vNodes; for (CNode *pnode : vNodesCopy) { if (pnode->fDisconnect) { // remove from vNodes vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); // release outbound grant (if any) pnode->grantOutbound.Release(); // close socket and cleanup pnode->CloseSocketDisconnect(); // hold in disconnected pool until all refs are released pnode->Release(); vNodesDisconnected.push_back(pnode); } } } { // Delete disconnected nodes std::list vNodesDisconnectedCopy = vNodesDisconnected; for (CNode *pnode : vNodesDisconnectedCopy) { // wait until threads are done using it if (pnode->GetRefCount() <= 0) { bool fDelete = false; { TRY_LOCK(pnode->cs_inventory, lockInv); if (lockInv) { TRY_LOCK(pnode->cs_vSend, lockSend); if (lockSend) { fDelete = true; } } } if (fDelete) { vNodesDisconnected.remove(pnode); DeleteNode(pnode); } } } } size_t vNodesSize; { LOCK(cs_vNodes); vNodesSize = vNodes.size(); } if (vNodesSize != nPrevNodeCount) { nPrevNodeCount = vNodesSize; if (clientInterface) { clientInterface->NotifyNumConnectionsChanged(nPrevNodeCount); } } // // Find which sockets have data to receive // struct timeval timeout; timeout.tv_sec = 0; // Frequency to poll pnode->vSend timeout.tv_usec = 50000; fd_set fdsetRecv; fd_set fdsetSend; fd_set fdsetError; FD_ZERO(&fdsetRecv); FD_ZERO(&fdsetSend); FD_ZERO(&fdsetError); SOCKET hSocketMax = 0; bool have_fds = false; for (const ListenSocket &hListenSocket : vhListenSocket) { FD_SET(hListenSocket.socket, &fdsetRecv); hSocketMax = std::max(hSocketMax, hListenSocket.socket); have_fds = true; } { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { // Implement the following logic: // * If there is data to send, select() for sending data. As // this only happens when optimistic write failed, we choose to // first drain the write buffer in this case before receiving // more. This avoids needlessly queueing received data, if the // remote peer is not themselves receiving data. This means // properly utilizing TCP flow control signalling. // * Otherwise, if there is space left in the receive buffer, // select() for receiving data. // * Hand off all complete messages to the processor, to be // handled without blocking here. bool select_recv = !pnode->fPauseRecv; bool select_send; { LOCK(pnode->cs_vSend); select_send = !pnode->vSendMsg.empty(); } LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) { continue; } FD_SET(pnode->hSocket, &fdsetError); hSocketMax = std::max(hSocketMax, pnode->hSocket); have_fds = true; if (select_send) { FD_SET(pnode->hSocket, &fdsetSend); continue; } if (select_recv) { FD_SET(pnode->hSocket, &fdsetRecv); } } } int nSelect = select(have_fds ? hSocketMax + 1 : 0, &fdsetRecv, &fdsetSend, &fdsetError, &timeout); if (interruptNet) { return; } if (nSelect == SOCKET_ERROR) { if (have_fds) { int nErr = WSAGetLastError(); LogPrintf("socket select error %s\n", NetworkErrorString(nErr)); for (unsigned int i = 0; i <= hSocketMax; i++) { FD_SET(i, &fdsetRecv); } } FD_ZERO(&fdsetSend); FD_ZERO(&fdsetError); if (!interruptNet.sleep_for( std::chrono::milliseconds(timeout.tv_usec / 1000))) { return; } } // // Accept new connections // for (const ListenSocket &hListenSocket : vhListenSocket) { if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv)) { AcceptConnection(hListenSocket); } } // // Service each socket // std::vector vNodesCopy; { LOCK(cs_vNodes); vNodesCopy = vNodes; for (CNode *pnode : vNodesCopy) { pnode->AddRef(); } } for (CNode *pnode : vNodesCopy) { if (interruptNet) { return; } // // Receive // bool recvSet = false; bool sendSet = false; bool errorSet = false; { LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) { continue; } recvSet = FD_ISSET(pnode->hSocket, &fdsetRecv); sendSet = FD_ISSET(pnode->hSocket, &fdsetSend); errorSet = FD_ISSET(pnode->hSocket, &fdsetError); } if (recvSet || errorSet) { // typical socket buffer is 8K-64K char pchBuf[0x10000]; int32_t nBytes = 0; { LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) { continue; } nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT); } if (nBytes > 0) { bool notify = false; if (!pnode->ReceiveMsgBytes(*config, pchBuf, nBytes, notify)) { pnode->CloseSocketDisconnect(); } RecordBytesRecv(nBytes); if (notify) { size_t nSizeAdded = 0; auto it(pnode->vRecvMsg.begin()); for (; it != pnode->vRecvMsg.end(); ++it) { if (!it->complete()) { break; } nSizeAdded += it->vRecv.size() + CMessageHeader::HEADER_SIZE; } { LOCK(pnode->cs_vProcessMsg); pnode->vProcessMsg.splice( pnode->vProcessMsg.end(), pnode->vRecvMsg, pnode->vRecvMsg.begin(), it); pnode->nProcessQueueSize += nSizeAdded; pnode->fPauseRecv = pnode->nProcessQueueSize > nReceiveFloodSize; } WakeMessageHandler(); } } else if (nBytes == 0) { // socket closed gracefully if (!pnode->fDisconnect) { LogPrint(BCLog::NET, "socket closed\n"); } pnode->CloseSocketDisconnect(); } else if (nBytes < 0) { // error int nErr = WSAGetLastError(); if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) { if (!pnode->fDisconnect) { LogPrintf("socket recv error %s\n", NetworkErrorString(nErr)); } pnode->CloseSocketDisconnect(); } } } // // Send // if (sendSet) { LOCK(pnode->cs_vSend); size_t nBytes = SocketSendData(pnode); if (nBytes) { RecordBytesSent(nBytes); } } // // Inactivity checking // int64_t nTime = GetSystemTimeInSeconds(); if (nTime - pnode->nTimeConnected > 60) { if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) { LogPrint(BCLog::NET, "socket no message in first 60 " "seconds, %d %d from %d\n", pnode->nLastRecv != 0, pnode->nLastSend != 0, pnode->GetId()); pnode->fDisconnect = true; } else if (nTime - pnode->nLastSend > TIMEOUT_INTERVAL) { LogPrintf("socket sending timeout: %is\n", nTime - pnode->nLastSend); pnode->fDisconnect = true; } else if (nTime - pnode->nLastRecv > (pnode->nVersion > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90 * 60)) { LogPrintf("socket receive timeout: %is\n", nTime - pnode->nLastRecv); pnode->fDisconnect = true; } else if (pnode->nPingNonceSent && pnode->nPingUsecStart + TIMEOUT_INTERVAL * 1000000 < GetTimeMicros()) { LogPrintf("ping timeout: %fs\n", 0.000001 * (GetTimeMicros() - pnode->nPingUsecStart)); pnode->fDisconnect = true; } else if (!pnode->fSuccessfullyConnected) { LogPrint(BCLog::NET, "version handshake timeout from %d\n", pnode->GetId()); pnode->fDisconnect = true; } } } { LOCK(cs_vNodes); for (CNode *pnode : vNodesCopy) { pnode->Release(); } } } } void CConnman::WakeMessageHandler() { { std::lock_guard lock(mutexMsgProc); fMsgProcWake = true; } condMsgProc.notify_one(); } #ifdef USE_UPNP static CThreadInterrupt g_upnp_interrupt; static std::thread g_upnp_thread; static void ThreadMapPort() { std::string port = strprintf("%u", GetListenPort()); const char *multicastif = nullptr; const char *minissdpdpath = nullptr; struct UPNPDev *devlist = nullptr; char lanaddr[64]; #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0); #elif MINIUPNPC_API_VERSION < 14 /* miniupnpc 1.6 */ int error = 0; devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); #else /* miniupnpc 1.9.20150730 */ int error = 0; devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); #endif struct UPNPUrls urls; struct IGDdatas data; int r; r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)); if (r == 1) { if (fDiscover) { char externalIPAddress[40]; r = UPNP_GetExternalIPAddress( urls.controlURL, data.first.servicetype, externalIPAddress); if (r != UPNPCOMMAND_SUCCESS) { LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r); } else { if (externalIPAddress[0]) { CNetAddr resolved; if (LookupHost(externalIPAddress, resolved, false)) { LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString().c_str()); AddLocal(resolved, LOCAL_UPNP); } } else { LogPrintf("UPnP: GetExternalIPAddress failed.\n"); } } } std::string strDesc = "Bitcoin " + FormatFullVersion(); do { #ifndef UPNPDISCOVER_SUCCESS /* miniupnpc 1.5 */ r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0); #else /* miniupnpc 1.6 */ r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); #endif if (r != UPNPCOMMAND_SUCCESS) { LogPrintf( "AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", port, port, lanaddr, r, strupnperror(r)); } else { LogPrintf("UPnP Port Mapping successful.\n"); } } while (g_upnp_interrupt.sleep_for(std::chrono::minutes(20))); r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); freeUPNPDevlist(devlist); devlist = nullptr; FreeUPNPUrls(&urls); } else { LogPrintf("No valid UPnP IGDs found\n"); freeUPNPDevlist(devlist); devlist = nullptr; if (r != 0) { FreeUPNPUrls(&urls); } } } void StartMapPort() { if (!g_upnp_thread.joinable()) { assert(!g_upnp_interrupt); g_upnp_thread = std::thread( (std::bind(&TraceThread, "upnp", &ThreadMapPort))); } } void InterruptMapPort() { if (g_upnp_thread.joinable()) { g_upnp_interrupt(); } } void StopMapPort() { if (g_upnp_thread.joinable()) { g_upnp_thread.join(); g_upnp_interrupt.reset(); } } #else void StartMapPort() { // Intentionally left blank. } void InterruptMapPort() { // Intentionally left blank. } void StopMapPort() { // Intentionally left blank. } #endif void CConnman::ThreadDNSAddressSeed() { // goal: only query DNS seeds if address need is acute. // Avoiding DNS seeds when we don't need them improves user privacy by // creating fewer identifying DNS requests, reduces trust by giving seeds // less influence on the network topology, and reduces traffic to the seeds. if ((addrman.size() > 0) && (!gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED))) { if (!interruptNet.sleep_for(std::chrono::seconds(11))) { return; } LOCK(cs_vNodes); int nRelevant = 0; for (const CNode *pnode : vNodes) { nRelevant += pnode->fSuccessfullyConnected && !pnode->fFeeler && !pnode->fOneShot && !pnode->m_manual_connection && !pnode->fInbound; } if (nRelevant >= 2) { LogPrintf("P2P peers available. Skipped DNS seeding.\n"); return; } } const std::vector &vSeeds = config->GetChainParams().DNSSeeds(); int found = 0; LogPrintf("Loading addresses from DNS seeds (could take a while)\n"); for (const std::string &seed : vSeeds) { if (interruptNet) { return; } if (HaveNameProxy()) { AddOneShot(seed); } else { std::vector vIPs; std::vector vAdd; ServiceFlags requiredServiceBits = GetDesirableServiceFlags(NODE_NONE); std::string host = strprintf("x%x.%s", requiredServiceBits, seed); CNetAddr resolveSource; if (!resolveSource.SetInternal(host)) { continue; } // Limits number of IPs learned from a DNS seed unsigned int nMaxIPs = 256; if (LookupHost(host.c_str(), vIPs, nMaxIPs, true)) { for (const CNetAddr &ip : vIPs) { int nOneDay = 24 * 3600; CAddress addr = CAddress( CService(ip, config->GetChainParams().GetDefaultPort()), requiredServiceBits); // Use a random age between 3 and 7 days old. addr.nTime = GetTime() - 3 * nOneDay - GetRand(4 * nOneDay); vAdd.push_back(addr); found++; } addrman.Add(vAdd, resolveSource); } else { // We now avoid directly using results from DNS Seeds which do // not support service bit filtering, instead using them as a // oneshot to get nodes with our desired service bits. AddOneShot(seed); } } } LogPrintf("%d addresses found from DNS seeds\n", found); } void CConnman::DumpAddresses() { int64_t nStart = GetTimeMillis(); CAddrDB adb(config->GetChainParams()); adb.Write(addrman); LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n", addrman.size(), GetTimeMillis() - nStart); } void CConnman::DumpData() { DumpAddresses(); DumpBanlist(); } void CConnman::ProcessOneShot() { std::string strDest; { LOCK(cs_vOneShots); if (vOneShots.empty()) { return; } strDest = vOneShots.front(); vOneShots.pop_front(); } CAddress addr; CSemaphoreGrant grant(*semOutbound, true); if (grant) { OpenNetworkConnection(addr, false, &grant, strDest.c_str(), true); } } bool CConnman::GetTryNewOutboundPeer() { return m_try_another_outbound_peer; } void CConnman::SetTryNewOutboundPeer(bool flag) { m_try_another_outbound_peer = flag; LogPrint(BCLog::NET, "net: setting try another outbound peer=%s\n", flag ? "true" : "false"); } // Return the number of peers we have over our outbound connection limit. // Exclude peers that are marked for disconnect, or are going to be disconnected // soon (eg one-shots and feelers). // Also exclude peers that haven't finished initial connection handshake yet (so // that we don't decide we're over our desired connection limit, and then evict // some peer that has finished the handshake). int CConnman::GetExtraOutboundCount() { int nOutbound = 0; { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (!pnode->fInbound && !pnode->m_manual_connection && !pnode->fFeeler && !pnode->fDisconnect && !pnode->fOneShot && pnode->fSuccessfullyConnected) { ++nOutbound; } } } return std::max(nOutbound - nMaxOutbound, 0); } void CConnman::ThreadOpenConnections(const std::vector connect) { // Connect to specific addresses if (!connect.empty()) { for (int64_t nLoop = 0;; nLoop++) { ProcessOneShot(); for (const std::string &strAddr : connect) { CAddress addr(CService(), NODE_NONE); OpenNetworkConnection(addr, false, nullptr, strAddr.c_str(), false, false, true); for (int i = 0; i < 10 && i < nLoop; i++) { if (!interruptNet.sleep_for( std::chrono::milliseconds(500))) { return; } } } if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) { return; } } } // Initiate network connections int64_t nStart = GetTime(); // Minimum time before next feeler connection (in microseconds). int64_t nNextFeeler = PoissonNextSend(nStart * 1000 * 1000, FEELER_INTERVAL); while (!interruptNet) { ProcessOneShot(); if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) { return; } CSemaphoreGrant grant(*semOutbound); if (interruptNet) { return; } // Add seed nodes if DNS seeds are all down (an infrastructure attack?). if (addrman.size() == 0 && (GetTime() - nStart > 60)) { static bool done = false; if (!done) { LogPrintf("Adding fixed seed nodes as DNS doesn't seem to be " "available.\n"); CNetAddr local; local.SetInternal("fixedseeds"); addrman.Add(convertSeed6(config->GetChainParams().FixedSeeds()), local); done = true; } } // // Choose an address to connect to based on most recently seen // CAddress addrConnect; // Only connect out to one peer per network group (/16 for IPv4). Do // this here so we don't have to critsect vNodes inside mapAddresses // critsect. int nOutbound = 0; std::set> setConnected; { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (!pnode->fInbound && !pnode->m_manual_connection) { // Netgroups for inbound and addnode peers are not excluded // because our goal here is to not use multiple of our // limited outbound slots on a single netgroup but inbound // and addnode peers do not use our outbound slots. Inbound // peers also have the added issue that they're attacker // controlled and could be used to prevent us from // connecting to particular hosts if we used them here. setConnected.insert(pnode->addr.GetGroup()); nOutbound++; } } } // Feeler Connections // // Design goals: // * Increase the number of connectable addresses in the tried table. // // Method: // * Choose a random address from new and attempt to connect to it if // we can connect successfully it is added to tried. // * Start attempting feeler connections only after node finishes // making outbound connections. // * Only make a feeler connection once every few minutes. // bool fFeeler = false; if (nOutbound >= nMaxOutbound && !GetTryNewOutboundPeer()) { // The current time right now (in microseconds). int64_t nTime = GetTimeMicros(); if (nTime > nNextFeeler) { nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); fFeeler = true; } else { continue; } } addrman.ResolveCollisions(); int64_t nANow = GetAdjustedTime(); int nTries = 0; while (!interruptNet) { CAddrInfo addr = addrman.SelectTriedCollision(); // SelectTriedCollision returns an invalid address if it is empty. if (!fFeeler || !addr.IsValid()) { addr = addrman.Select(fFeeler); } // if we selected an invalid address, restart if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr)) { break; } // If we didn't find an appropriate destination after trying 100 // addresses fetched from addrman, stop this loop, and let the outer // loop run again (which sleeps, adds seed nodes, recalculates // already-connected network ranges, ...) before trying new addrman // addresses. nTries++; if (nTries > 100) { break; } if (IsLimited(addr)) { continue; } // only consider very recently tried nodes after 30 failed attempts if (nANow - addr.nLastTry < 600 && nTries < 30) { continue; } // for non-feelers, require all the services we'll want, // for feelers, only require they be a full node (only because most // SPV clients don't have a good address DB available) if (!fFeeler && !HasAllDesirableServiceFlags(addr.nServices)) { continue; } if (fFeeler && !MayHaveUsefulAddressDB(addr.nServices)) { continue; } // do not allow non-default ports, unless after 50 invalid addresses // selected already. if (addr.GetPort() != config->GetChainParams().GetDefaultPort() && nTries < 50) { continue; } addrConnect = addr; break; } if (addrConnect.IsValid()) { if (fFeeler) { // Add small amount of random noise before connection to avoid // synchronization. int randsleep = GetRandInt(FEELER_SLEEP_WINDOW * 1000); if (!interruptNet.sleep_for( std::chrono::milliseconds(randsleep))) { return; } LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString()); } OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler); } } } std::vector CConnman::GetAddedNodeInfo() { std::vector ret; std::list lAddresses(0); { LOCK(cs_vAddedNodes); ret.reserve(vAddedNodes.size()); std::copy(vAddedNodes.cbegin(), vAddedNodes.cend(), std::back_inserter(lAddresses)); } // Build a map of all already connected addresses (by IP:port and by name) // to inbound/outbound and resolved CService std::map mapConnected; std::map> mapConnectedByName; { LOCK(cs_vNodes); for (const CNode *pnode : vNodes) { if (pnode->addr.IsValid()) { mapConnected[pnode->addr] = pnode->fInbound; } std::string addrName = pnode->GetAddrName(); if (!addrName.empty()) { mapConnectedByName[std::move(addrName)] = std::make_pair(pnode->fInbound, static_cast(pnode->addr)); } } } for (const std::string &strAddNode : lAddresses) { CService service( LookupNumeric(strAddNode.c_str(), Params().GetDefaultPort())); AddedNodeInfo addedNode{strAddNode, CService(), false, false}; if (service.IsValid()) { // strAddNode is an IP:port auto it = mapConnected.find(service); if (it != mapConnected.end()) { addedNode.resolvedAddress = service; addedNode.fConnected = true; addedNode.fInbound = it->second; } } else { // strAddNode is a name auto it = mapConnectedByName.find(strAddNode); if (it != mapConnectedByName.end()) { addedNode.resolvedAddress = it->second.second; addedNode.fConnected = true; addedNode.fInbound = it->second.first; } } ret.emplace_back(std::move(addedNode)); } return ret; } void CConnman::ThreadOpenAddedConnections() { while (true) { CSemaphoreGrant grant(*semAddnode); std::vector vInfo = GetAddedNodeInfo(); bool tried = false; for (const AddedNodeInfo &info : vInfo) { if (!info.fConnected) { if (!grant.TryAcquire()) { // If we've used up our semaphore and need a new one, lets // not wait here since while we are waiting the // addednodeinfo state might change. break; } tried = true; CAddress addr(CService(), NODE_NONE); OpenNetworkConnection(addr, false, &grant, info.strAddedNode.c_str(), false, false, true); if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) { return; } } } // Retry every 60 seconds if a connection was attempted, otherwise two // seconds. if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2))) { return; } } } // If successful, this moves the passed grant to the constructed node. void CConnman::OpenNetworkConnection(const CAddress &addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection) { // // Initiate outbound network connection // if (interruptNet) { return; } if (!fNetworkActive) { return; } if (!pszDest) { if (IsLocal(addrConnect) || FindNode((CNetAddr)addrConnect) || IsBanned(addrConnect) || FindNode(addrConnect.ToStringIPPort())) { return; } } else if (FindNode(std::string(pszDest))) { return; } CNode *pnode = ConnectNode(addrConnect, pszDest, fCountFailure); if (!pnode) { return; } if (grantOutbound) { grantOutbound->MoveTo(pnode->grantOutbound); } if (fOneShot) { pnode->fOneShot = true; } if (fFeeler) { pnode->fFeeler = true; } if (manual_connection) { pnode->m_manual_connection = true; } m_msgproc->InitializeNode(*config, pnode); { LOCK(cs_vNodes); vNodes.push_back(pnode); } } void CConnman::ThreadMessageHandler() { while (!flagInterruptMsgProc) { std::vector vNodesCopy; { LOCK(cs_vNodes); vNodesCopy = vNodes; for (CNode *pnode : vNodesCopy) { pnode->AddRef(); } } bool fMoreWork = false; for (CNode *pnode : vNodesCopy) { if (pnode->fDisconnect) { continue; } // Receive messages bool fMoreNodeWork = m_msgproc->ProcessMessages( *config, pnode, flagInterruptMsgProc); fMoreWork |= (fMoreNodeWork && !pnode->fPauseSend); if (flagInterruptMsgProc) { return; } // Send messages { LOCK(pnode->cs_sendProcessing); m_msgproc->SendMessages(*config, pnode, flagInterruptMsgProc); } if (flagInterruptMsgProc) { return; } } { LOCK(cs_vNodes); for (CNode *pnode : vNodesCopy) { pnode->Release(); } } WAIT_LOCK(mutexMsgProc, lock); if (!fMoreWork) { condMsgProc.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::milliseconds(100), [this] { return fMsgProcWake; }); } fMsgProcWake = false; } } bool CConnman::BindListenPort(const CService &addrBind, std::string &strError, bool fWhitelisted) { strError = ""; int nOne = 1; // Create socket for listening for incoming connections struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (!addrBind.GetSockAddr((struct sockaddr *)&sockaddr, &len)) { strError = strprintf("Error: Bind address family for %s not supported", addrBind.ToString()); LogPrintf("%s\n", strError); return false; } SOCKET hListenSocket = CreateSocket(addrBind); if (hListenSocket == INVALID_SOCKET) { strError = strprintf("Error: Couldn't open socket for incoming " "connections (socket returned error %s)", NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError); return false; } // Allow binding if the port is still in TIME_WAIT state after // the program was closed and restarted. setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int)); // Some systems don't have IPV6_V6ONLY but are always v6only; others do have // the option and enable it by default or not. Try to enable it, if // possible. if (addrBind.IsIPv6()) { #ifdef IPV6_V6ONLY setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int)); #endif #ifdef WIN32 int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED; setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (sockopt_arg_type)&nProtLevel, sizeof(int)); #endif } if (::bind(hListenSocket, (struct sockaddr *)&sockaddr, len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) { strError = strprintf(_("Unable to bind to %s on this computer. %s " "is probably already running."), addrBind.ToString(), _(PACKAGE_NAME)); } else { strError = strprintf(_("Unable to bind to %s on this computer " "(bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); } LogPrintf("%s\n", strError); CloseSocket(hListenSocket); return false; } LogPrintf("Bound to %s\n", addrBind.ToString()); // Listen for incoming connections if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) { strError = strprintf(_("Error: Listening for incoming connections " "failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError); CloseSocket(hListenSocket); return false; } vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted)); if (addrBind.IsRoutable() && fDiscover && !fWhitelisted) { AddLocal(addrBind, LOCAL_BIND); } return true; } void Discover() { if (!fDiscover) { return; } #ifdef WIN32 // Get local host IP char pszHostName[256] = ""; if (gethostname(pszHostName, sizeof(pszHostName)) != SOCKET_ERROR) { std::vector vaddr; if (LookupHost(pszHostName, vaddr, 0, true)) { for (const CNetAddr &addr : vaddr) { if (AddLocal(addr, LOCAL_IF)) { LogPrintf("%s: %s - %s\n", __func__, pszHostName, addr.ToString()); } } } } #else // Get local host ip struct ifaddrs *myaddrs; if (getifaddrs(&myaddrs) == 0) { for (struct ifaddrs *ifa = myaddrs; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr || (ifa->ifa_flags & IFF_UP) == 0 || strcmp(ifa->ifa_name, "lo") == 0 || strcmp(ifa->ifa_name, "lo0") == 0) { continue; } if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in *s4 = (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) { LogPrint(BCLog::NET, "SetNetworkActive: %s\n", active); if (fNetworkActive == active) { return; } fNetworkActive = active; uiInterface.NotifyNetworkActiveChanged(fNetworkActive); } CConnman::CConnman(const Config &configIn, uint64_t nSeed0In, uint64_t nSeed1In) : config(&configIn), nSeed0(nSeed0In), nSeed1(nSeed1In) { fNetworkActive = true; setBannedIsDirty = false; fAddressesInitialized = false; nLastNodeId = 0; nSendBufferMaxSize = 0; nReceiveFloodSize = 0; flagInterruptMsgProc = false; SetTryNewOutboundPeer(false); Options connOptions; Init(connOptions); } NodeId CConnman::GetNewNodeId() { return nLastNodeId.fetch_add(1, std::memory_order_relaxed); } bool CConnman::Bind(const CService &addr, unsigned int flags) { if (!(flags & BF_EXPLICIT) && IsLimited(addr)) { return false; } std::string strError; if (!BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) { if ((flags & BF_REPORT_ERROR) && clientInterface) { clientInterface->ThreadSafeMessageBox( strError, "", CClientUIInterface::MSG_ERROR); } return false; } return true; } bool CConnman::InitBinds(const std::vector &binds, const std::vector &whiteBinds) { bool fBound = false; for (const auto &addrBind : binds) { fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); } for (const auto &addrBind : whiteBinds) { fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST)); } if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE); fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); } return fBound; } bool CConnman::Start(CScheduler &scheduler, const Options &connOptions) { Init(connOptions); nTotalBytesRecv = 0; nTotalBytesSent = 0; nMaxOutboundTotalBytesSentInCycle = 0; nMaxOutboundCycleStartTime = 0; if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( _("Failed to listen on any port. Use -listen=0 if you want " "this."), "", CClientUIInterface::MSG_ERROR); } return false; } for (const auto &strDest : connOptions.vSeedNodes) { AddOneShot(strDest); } if (clientInterface) { clientInterface->InitMessage(_("Loading P2P addresses...")); } // Load addresses from peers.dat int64_t nStart = GetTimeMillis(); { CAddrDB adb(config->GetChainParams()); if (adb.Read(addrman)) { LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman.size(), GetTimeMillis() - nStart); } else { // Addrman can be in an inconsistent state after failure, reset it addrman.Clear(); LogPrintf("Invalid or missing peers.dat; recreating\n"); DumpAddresses(); } } if (clientInterface) { clientInterface->InitMessage(_("Loading banlist...")); } // Load addresses from banlist.dat nStart = GetTimeMillis(); CBanDB bandb(config->GetChainParams()); banmap_t banmap; if (bandb.Read(banmap)) { // thread save setter SetBanned(banmap); // no need to write down, just read data SetBannedSetDirty(false); // sweep out unused entries SweepBanned(); LogPrint(BCLog::NET, "Loaded %d banned node ips/subnets from banlist.dat %dms\n", banmap.size(), GetTimeMillis() - nStart); } else { LogPrintf("Invalid or missing banlist.dat; recreating\n"); // force write SetBannedSetDirty(true); DumpBanlist(); } uiInterface.InitMessage(_("Starting network threads...")); fAddressesInitialized = true; if (semOutbound == nullptr) { // initialize semaphore semOutbound = std::unique_ptr(new CSemaphore( std::min((nMaxOutbound + nMaxFeeler), nMaxConnections))); } if (semAddnode == nullptr) { // initialize semaphore semAddnode = std::unique_ptr(new CSemaphore(nMaxAddnode)); } // // Start threads // assert(m_msgproc); InterruptSocks5(false); interruptNet.reset(); flagInterruptMsgProc = false; { LOCK(mutexMsgProc); fMsgProcWake = false; } // Send and receive from sockets, accept connections threadSocketHandler = std::thread( &TraceThread>, "net", std::function(std::bind(&CConnman::ThreadSocketHandler, this))); if (!gArgs.GetBoolArg("-dnsseed", true)) { LogPrintf("DNS seeding disabled\n"); } else { threadDNSAddressSeed = std::thread(&TraceThread>, "dnsseed", std::function( std::bind(&CConnman::ThreadDNSAddressSeed, this))); } // Initiate outbound connections from -addnode threadOpenAddedConnections = std::thread(&TraceThread>, "addcon", std::function(std::bind( &CConnman::ThreadOpenAddedConnections, this))); if (connOptions.m_use_addrman_outgoing && !connOptions.m_specified_outgoing.empty()) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( _("Cannot provide specific connections and have addrman find " "outgoing connections at the same."), "", CClientUIInterface::MSG_ERROR); } return false; } if (connOptions.m_use_addrman_outgoing || !connOptions.m_specified_outgoing.empty()) { threadOpenConnections = std::thread(&TraceThread>, "opencon", std::function( std::bind(&CConnman::ThreadOpenConnections, this, connOptions.m_specified_outgoing))); } // Process messages threadMessageHandler = std::thread(&TraceThread>, "msghand", std::function( std::bind(&CConnman::ThreadMessageHandler, this))); // Dump network addresses - scheduler.scheduleEvery(std::bind(&CConnman::DumpData, this), - DUMP_ADDRESSES_INTERVAL * 1000); + scheduler.scheduleEvery( + [this]() { + this->DumpData(); + return true; + }, + DUMP_ADDRESSES_INTERVAL * 1000); return true; } class CNetCleanup { public: CNetCleanup() {} ~CNetCleanup() { #ifdef WIN32 // Shutdown Windows Sockets WSACleanup(); #endif } } instance_of_cnetcleanup; void CConnman::Interrupt() { { std::lock_guard lock(mutexMsgProc); flagInterruptMsgProc = true; } condMsgProc.notify_all(); interruptNet(); InterruptSocks5(true); if (semOutbound) { for (int i = 0; i < (nMaxOutbound + nMaxFeeler); i++) { semOutbound->post(); } } if (semAddnode) { for (int i = 0; i < nMaxAddnode; i++) { semAddnode->post(); } } } void CConnman::Stop() { if (threadMessageHandler.joinable()) { threadMessageHandler.join(); } if (threadOpenConnections.joinable()) { threadOpenConnections.join(); } if (threadOpenAddedConnections.joinable()) { threadOpenAddedConnections.join(); } if (threadDNSAddressSeed.joinable()) { threadDNSAddressSeed.join(); } if (threadSocketHandler.joinable()) { threadSocketHandler.join(); } if (fAddressesInitialized) { DumpData(); fAddressesInitialized = false; } // Close sockets for (CNode *pnode : vNodes) { pnode->CloseSocketDisconnect(); } for (ListenSocket &hListenSocket : vhListenSocket) { if (hListenSocket.socket != INVALID_SOCKET) { if (!CloseSocket(hListenSocket.socket)) { LogPrintf("CloseSocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); } } } // clean up some globals (to help leak detection) for (CNode *pnode : vNodes) { DeleteNode(pnode); } for (CNode *pnode : vNodesDisconnected) { DeleteNode(pnode); } vNodes.clear(); vNodesDisconnected.clear(); vhListenSocket.clear(); semOutbound.reset(); semAddnode.reset(); } void CConnman::DeleteNode(CNode *pnode) { assert(pnode); bool fUpdateConnectionTime = false; m_msgproc->FinalizeNode(*config, pnode->GetId(), fUpdateConnectionTime); if (fUpdateConnectionTime) { addrman.Connected(pnode->addr); } delete pnode; } CConnman::~CConnman() { Interrupt(); Stop(); } size_t CConnman::GetAddressCount() const { return addrman.size(); } void CConnman::SetServices(const CService &addr, ServiceFlags nServices) { addrman.SetServices(addr, nServices); } void CConnman::MarkAddressGood(const CAddress &addr) { addrman.Good(addr); } void CConnman::AddNewAddresses(const std::vector &vAddr, const CAddress &addrFrom, int64_t nTimePenalty) { addrman.Add(vAddr, addrFrom, nTimePenalty); } std::vector CConnman::GetAddresses() { return addrman.GetAddr(); } bool CConnman::AddNode(const std::string &strNode) { LOCK(cs_vAddedNodes); for (const std::string &it : vAddedNodes) { if (strNode == it) { return false; } } vAddedNodes.push_back(strNode); return true; } bool CConnman::RemoveAddedNode(const std::string &strNode) { LOCK(cs_vAddedNodes); for (std::vector::iterator it = vAddedNodes.begin(); it != vAddedNodes.end(); ++it) { if (strNode == *it) { vAddedNodes.erase(it); return true; } } return false; } size_t CConnman::GetNodeCount(NumConnections flags) { LOCK(cs_vNodes); // Shortcut if we want total if (flags == CConnman::CONNECTIONS_ALL) { return vNodes.size(); } int nNum = 0; for (const auto &pnode : vNodes) { if (flags & (pnode->fInbound ? CONNECTIONS_IN : CONNECTIONS_OUT)) { nNum++; } } return nNum; } void CConnman::GetNodeStats(std::vector &vstats) { vstats.clear(); LOCK(cs_vNodes); vstats.reserve(vNodes.size()); for (CNode *pnode : vNodes) { vstats.emplace_back(); pnode->copyStats(vstats.back()); } } bool CConnman::DisconnectNode(const std::string &strNode) { LOCK(cs_vNodes); if (CNode *pnode = FindNode(strNode)) { pnode->fDisconnect = true; return true; } return false; } bool CConnman::DisconnectNode(NodeId id) { LOCK(cs_vNodes); for (CNode *pnode : vNodes) { if (id == pnode->GetId()) { pnode->fDisconnect = true; return true; } } return false; } void CConnman::RecordBytesRecv(uint64_t bytes) { LOCK(cs_totalBytesRecv); nTotalBytesRecv += bytes; } void CConnman::RecordBytesSent(uint64_t bytes) { LOCK(cs_totalBytesSent); nTotalBytesSent += bytes; uint64_t now = GetTime(); if (nMaxOutboundCycleStartTime + nMaxOutboundTimeframe < now) { // timeframe expired, reset cycle nMaxOutboundCycleStartTime = now; nMaxOutboundTotalBytesSentInCycle = 0; } // TODO, exclude whitebind peers nMaxOutboundTotalBytesSentInCycle += bytes; } void CConnman::SetMaxOutboundTarget(uint64_t limit) { LOCK(cs_totalBytesSent); nMaxOutboundLimit = limit; } uint64_t CConnman::GetMaxOutboundTarget() { LOCK(cs_totalBytesSent); return nMaxOutboundLimit; } uint64_t CConnman::GetMaxOutboundTimeframe() { LOCK(cs_totalBytesSent); return nMaxOutboundTimeframe; } uint64_t CConnman::GetMaxOutboundTimeLeftInCycle() { LOCK(cs_totalBytesSent); if (nMaxOutboundLimit == 0) { return 0; } if (nMaxOutboundCycleStartTime == 0) { return nMaxOutboundTimeframe; } uint64_t cycleEndTime = nMaxOutboundCycleStartTime + nMaxOutboundTimeframe; uint64_t now = GetTime(); return (cycleEndTime < now) ? 0 : cycleEndTime - GetTime(); } void CConnman::SetMaxOutboundTimeframe(uint64_t timeframe) { LOCK(cs_totalBytesSent); if (nMaxOutboundTimeframe != timeframe) { // reset measure-cycle in case of changing the timeframe. nMaxOutboundCycleStartTime = GetTime(); } nMaxOutboundTimeframe = timeframe; } bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) { LOCK(cs_totalBytesSent); if (nMaxOutboundLimit == 0) { return false; } if (historicalBlockServingLimit) { // keep a large enough buffer to at least relay each block once. uint64_t timeLeftInCycle = GetMaxOutboundTimeLeftInCycle(); uint64_t buffer = timeLeftInCycle / 600 * ONE_MEGABYTE; if (buffer >= nMaxOutboundLimit || nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit - buffer) { return true; } } else if (nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit) { return true; } return false; } uint64_t CConnman::GetOutboundTargetBytesLeft() { LOCK(cs_totalBytesSent); if (nMaxOutboundLimit == 0) { return 0; } return (nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit) ? 0 : nMaxOutboundLimit - nMaxOutboundTotalBytesSentInCycle; } uint64_t CConnman::GetTotalBytesRecv() { LOCK(cs_totalBytesRecv); return nTotalBytesRecv; } uint64_t CConnman::GetTotalBytesSent() { LOCK(cs_totalBytesSent); return nTotalBytesSent; } ServiceFlags CConnman::GetLocalServices() const { return nLocalServices; } void CConnman::SetBestHeight(int height) { nBestHeight.store(height, std::memory_order_release); } int CConnman::GetBestHeight() const { return nBestHeight.load(std::memory_order_acquire); } unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn, bool fInboundIn) : nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn), addrBind(addrBindIn), fInbound(fInboundIn), nKeyedNetGroup(nKeyedNetGroupIn), addrKnown(5000, 0.001), filterInventoryKnown(50000, 0.000001), id(idIn), nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn), nMyStartingHeight(nMyStartingHeightIn), nSendVersion(0) { nServices = NODE_NONE; hSocket = hSocketIn; nRecvVersion = INIT_PROTO_VERSION; nLastSend = 0; nLastRecv = 0; nSendBytes = 0; nRecvBytes = 0; nTimeOffset = 0; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; nVersion = 0; strSubVer = ""; fWhitelisted = false; fOneShot = false; m_manual_connection = false; // set by version message fClient = false; 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 = std::unique_ptr(new CBloomFilter()); timeLastMempoolReq = 0; nLastBlockTime = 0; nLastTXTime = 0; nPingNonceSent = 0; nPingUsecStart = 0; nPingUsecTime = 0; fPingQueued = false; nMinPingUsecTime = std::numeric_limits::max(); minFeeFilter = Amount::zero(); lastSentFeeFilter = Amount::zero(); nextSendTimeFeeFilter = 0; fPauseRecv = false; fPauseSend = false; nProcessQueueSize = 0; for (const std::string &msg : getAllNetMessageTypes()) { mapRecvBytesPerMsgCmd[msg] = 0; } mapRecvBytesPerMsgCmd[NET_MESSAGE_COMMAND_OTHER] = 0; if (fLogIPs) { LogPrint(BCLog::NET, "Added connection to %s peer=%d\n", addrName, id); } else { LogPrint(BCLog::NET, "Added connection peer=%d\n", id); } } CNode::~CNode() { CloseSocket(hSocket); } void CNode::AskFor(const CInv &inv) { if (mapAskFor.size() > MAPASKFOR_MAX_SZ || setAskFor.size() > SETASKFOR_MAX_SZ) { return; } // a peer may not have multiple non-responded queue positions for a single // inv item. if (!setAskFor.insert(inv.hash).second) { return; } // We're using mapAskFor as a priority queue, the key is the earliest time // the request can be sent. int64_t nRequestTime; limitedmap::const_iterator it = mapAlreadyAskedFor.find(inv.hash); if (it != mapAlreadyAskedFor.end()) { nRequestTime = it->second; } else { nRequestTime = 0; } LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime / 1000000), id); // Make sure not to reuse time indexes to keep things in the same order int64_t nNow = GetTimeMicros() - 1000000; static int64_t nLastTime; ++nLastTime; nNow = std::max(nNow, nLastTime); nLastTime = nNow; // Each retry is 2 minutes after the last nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow); if (it != mapAlreadyAskedFor.end()) { mapAlreadyAskedFor.update(it, nRequestTime); } else { mapAlreadyAskedFor.insert(std::make_pair(inv.hash, nRequestTime)); } mapAskFor.insert(std::make_pair(nRequestTime, inv)); } bool CConnman::NodeFullyConnected(const CNode *pnode) { return pnode && pnode->fSuccessfullyConnected && !pnode->fDisconnect; } void CConnman::PushMessage(CNode *pnode, CSerializedNetMsg &&msg) { size_t nMessageSize = msg.data.size(); size_t nTotalSize = nMessageSize + CMessageHeader::HEADER_SIZE; LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", SanitizeString(msg.command.c_str()), nMessageSize, pnode->GetId()); std::vector serializedHeader; serializedHeader.reserve(CMessageHeader::HEADER_SIZE); uint256 hash = Hash(msg.data.data(), msg.data.data() + nMessageSize); CMessageHeader hdr(config->GetChainParams().NetMagic(), msg.command.c_str(), nMessageSize); memcpy(hdr.pchChecksum, hash.begin(), CMessageHeader::CHECKSUM_SIZE); CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, serializedHeader, 0, hdr}; size_t nBytesSent = 0; { LOCK(pnode->cs_vSend); bool optimisticSend(pnode->vSendMsg.empty()); // log total amount of bytes per command pnode->mapSendBytesPerMsgCmd[msg.command] += nTotalSize; pnode->nSendSize += nTotalSize; if (pnode->nSendSize > nSendBufferMaxSize) { pnode->fPauseSend = true; } pnode->vSendMsg.push_back(std::move(serializedHeader)); if (nMessageSize) { pnode->vSendMsg.push_back(std::move(msg.data)); } // If write queue empty, attempt "optimistic write" if (optimisticSend == true) { nBytesSent = SocketSendData(pnode); } } if (nBytesSent) { RecordBytesSent(nBytesSent); } } bool CConnman::ForNode(NodeId id, std::function func) { CNode *found = nullptr; LOCK(cs_vNodes); for (auto &&pnode : vNodes) { if (pnode->GetId() == id) { found = pnode; break; } } return found != nullptr && NodeFullyConnected(found) && func(found); } int64_t PoissonNextSend(int64_t nNow, int average_interval_seconds) { return nNow + int64_t(log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */) * average_interval_seconds * -1000000.0 + 0.5); } CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const { return CSipHasher(nSeed0, nSeed1).Write(id); } uint64_t CConnman::CalculateKeyedNetGroup(const CAddress &ad) const { std::vector vchNetGroup(ad.GetGroup()); return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP) .Write(vchNetGroup.data(), vchNetGroup.size()) .Finalize(); } /** * This function convert MaxBlockSize from byte to * MB with a decimal precision one digit rounded down * E.g. * 1660000 -> 1.6 * 2010000 -> 2.0 * 1000000 -> 1.0 * 230000 -> 0.2 * 50000 -> 0.0 * * NB behavior for EB<1MB not standardized yet still * the function applies the same algo used for * EB greater or equal to 1MB */ std::string getSubVersionEB(uint64_t MaxBlockSize) { // Prepare EB string we are going to add to SubVer: // 1) translate from byte to MB and convert to string // 2) limit the EB string to the first decimal digit (floored) std::stringstream ebMBs; ebMBs << (MaxBlockSize / (ONE_MEGABYTE / 10)); std::string eb = ebMBs.str(); eb.insert(eb.size() - 1, ".", 1); if (eb.substr(0, 1) == ".") { eb = "0" + eb; } return eb; } std::string userAgent(const Config &config) { // format excessive blocksize value std::string eb = getSubVersionEB(config.GetMaxBlockSize()); std::vector uacomments; uacomments.push_back("EB" + eb); // sanitize comments per BIP-0014, format user agent and check total size for (const std::string &cmt : gArgs.GetArgs("-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_processing.cpp b/src/net_processing.cpp index 34fac0c71..d9acbeb8c 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1,4260 +1,4262 @@ // 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 "blockvalidity.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 "scheduler.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 #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 GUARDED_BY(cs_main) = 0; /** * Sources of received blocks, saved to be able to send them reject messages or * ban them when processing happens afterwards. * Set mapBlockSource[hash].second to false if the node should not be punished * if the block is invalid. */ std::map> mapBlockSource GUARDED_BY(cs_main); /** * 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. * * 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 GUARDED_BY(cs_main); uint256 hashRecentRejectsChainTip GUARDED_BY(cs_main); /** * Blocks that are in flight, and that are in the queue to be downloaded. */ 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 GUARDED_BY(cs_main); /** Stack of nodes which we have set to announce using compact blocks */ std::list lNodesAnnouncingHeaderAndIDs; /** Number of preferable block download peers. */ int nPreferredDownload GUARDED_BY(cs_main) = 0; /** Number of peers from which we're downloading blocks. */ int nPeersWithValidatedDownloads GUARDED_BY(cs_main) = 0; /** Number of outbound peers with m_chain_sync.m_protect. */ int g_outbound_peers_with_protect_from_disconnect = 0; /** When our tip was last updated. */ int64_t g_last_tip_update = 0; /** Relay map. */ typedef std::map MapRelay; MapRelay mapRelay GUARDED_BY(cs_main); /** * Expiration-time ordered list of (expire time, relay map entry) pairs, * protected by cs_main). */ std::deque> vRelayExpiration GUARDED_BY(cs_main); } // namespace 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; //! When to potentially disconnect peer for stalling headers download int64_t nHeadersSyncTimeout; //! 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; /** * State used to enforce CHAIN_SYNC_TIMEOUT * Only in effect for outbound, non-manual connections, * with m_protect == false * Algorithm: if a peer's best known block has less work than our tip, set a * timeout CHAIN_SYNC_TIMEOUT seconds in the future: * - If at timeout their best known block now has more work than our tip * when the timeout was set, then either reset the timeout or clear it * (after comparing against our current tip's work) * - If at timeout their best known block still has less work than our tip * did when the timeout was set, then send a getheaders message, and set a * shorter timeout, HEADERS_RESPONSE_TIME seconds in future. If their best * known block is still behind when that new timeout is reached, disconnect. */ struct ChainSyncTimeoutState { //! A timeout used for checking whether our peer has sufficiently //! synced. int64_t m_timeout; //! A header with the work we require on our peer's chain. const CBlockIndex *m_work_header; //! After timeout is reached, set to true after sending getheaders. bool m_sent_getheaders; //! Whether this peer is protected from disconnection due to a bad/slow //! chain. bool m_protect; }; ChainSyncTimeoutState m_chain_sync; //! Time of last new block announcement int64_t m_last_block_announcement; 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; nHeadersSyncTimeout = 0; nStallingSince = 0; nDownloadingSince = 0; nBlocksInFlight = 0; nBlocksInFlightValidHeaders = 0; fPreferredDownload = false; fPreferHeaders = false; fPreferHeaderAndIDs = false; fProvidesHeaderAndIDs = false; fSupportsDesiredCmpctVersion = false; m_chain_sync = {0, nullptr, false, false}; m_last_block_announcement = 0; } }; /** Map maintaining per-node state. */ static std::map mapNodeState GUARDED_BY(cs_main); static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { std::map::iterator it = mapNodeState.find(pnode); if (it == mapNodeState.end()) { return nullptr; } return &it->second; } static void UpdatePreferredDownload(CNode *node, CNodeState *state) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { 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; } static 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(BCLog::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( BCLog::NET, "send version message: version %d, blocks=%d, us=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), nodeid); } } // 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. static bool MarkBlockAsReceived(const uint256 &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { 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; } // 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) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { 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) { if (pit) { *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. */ static void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { 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. */ static void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { 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; } } /** * When a peer sends us a valid block, instruct it to announce blocks to us * using CMPCTBLOCK if possible by adding its nodeid to the end of * lNodesAnnouncingHeaderAndIDs, and keeping that list under a certain size by * removing the first element if necessary. */ static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman *connman) { AssertLockHeld(cs_main); CNodeState *nodestate = State(nodeid); if (!nodestate) { LogPrint(BCLog::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) { AssertLockHeld(cs_main); 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) { AssertLockHeld(cs_main); 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; }); } static bool TipMayBeStale(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (g_last_tip_update == 0) { g_last_tip_update = GetTime(); } return g_last_tip_update < GetTime() - consensusParams.nPowTargetSpacing * 3 && mapBlocksInFlight.empty(); } static bool CanDirectFetch(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return chainActive.Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20; } static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { if (state->pindexBestKnownBlock && pindex == state->pindexBestKnownBlock->GetAncestor(pindex->nHeight)) { return true; } if (state->pindexBestHeaderSent && pindex == state->pindexBestHeaderSent->GetAncestor(pindex->nHeight)) { return true; } return false; } /** * Update pindexLastCommonBlock and add not-in-flight missing successors to * vBlocks, until it has at most count entries. */ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector &vBlocks, NodeId &nodeStaller, const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { 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 || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { // 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(BlockValidity::TREE)) { // We consider the chain that this peer is on invalid. return; } if (pindex->nStatus.hasData() || 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 // This function is used for testing the stale tip eviction logic, see // DoS_tests.cpp void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) { LOCK(cs_main); CNodeState *state = State(node); if (state) { state->m_last_block_announcement = time_in_seconds; } } // Returns true for outbound peers, excluding manual connections, feelers, and // one-shots. static bool IsOutboundDisconnectionCandidate(const CNode *node) { return !(node->fInbound || node->m_manual_connection || node->fFeeler || node->fOneShot); } void PeerLogicValidation::InitializeNode(const Config &config, CNode *pnode) { 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 PeerLogicValidation::FinalizeNode(const Config &config, NodeId nodeid, bool &fUpdateConnectionTime) { fUpdateConnectionTime = false; LOCK(cs_main); CNodeState *state = State(nodeid); assert(state != nullptr); if (state->fSyncStarted) { nSyncStarted--; } if (state->nMisbehavior == 0 && state->fCurrentlyConnected) { fUpdateConnectionTime = true; } for (const QueuedBlock &entry : state->vBlocksInFlight) { mapBlocksInFlight.erase(entry.hash); } EraseOrphansFor(nodeid); nPreferredDownload -= state->fPreferredDownload; nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0); assert(nPeersWithValidatedDownloads >= 0); g_outbound_peers_with_protect_from_disconnect -= state->m_chain_sync.m_protect; assert(g_outbound_peers_with_protect_from_disconnect >= 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); assert(g_outbound_peers_with_protect_from_disconnect == 0); } LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid); } 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; } ////////////////////////////////////////////////////////////////////////////// // // mapOrphanTransactions // static void AddToCompactExtraTransactions(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { size_t max_extra_txn = gArgs.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->GetId(), 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 = tx->GetTotalSize(); if (sz >= MAX_STANDARD_TX_SIZE) { LogPrint(BCLog::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(BCLog::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(BCLog::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(BCLog::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; } /** * Mark a misbehaving peer to be banned depending upon the value of `-banscore`. */ void Misbehaving(NodeId pnode, int howmuch, const std::string &reason) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { if (howmuch == 0) { return; } CNodeState *state = State(pnode); if (state == nullptr) { return; } state->nMisbehavior += howmuch; int banscore = gArgs.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, CScheduler &scheduler) : connman(connmanIn), m_stale_tip_check_time(0) { // Initialize global variables that cannot be constructed at startup. recentRejects.reset(new CRollingBloomFilter(120000, 0.000001)); const Consensus::Params &consensusParams = Params().GetConsensus(); // Stale tip checking and peer eviction are on two different timers, but we // don't want them to get out of sync due to drift in the scheduler, so we // combine them in one function and schedule at the quicker (peer-eviction) // timer. static_assert( EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL, "peer eviction timer should be less than stale tip check timer"); scheduler.scheduleEvery( - std::bind(&PeerLogicValidation::CheckForStaleTipAndEvictPeers, this, - consensusParams), + [this, &consensusParams]() { + this->CheckForStaleTipAndEvictPeers(consensusParams); + return true; + }, EXTRA_PEER_CHECK_INTERVAL * 1000); } void PeerLogicValidation::BlockConnected( const std::shared_ptr &pblock, const CBlockIndex *pindex, const std::vector &vtxConflicted) { LOCK(cs_main); std::vector vOrphanErase; for (const CTransactionRef &ptx : pblock->vtx) { const CTransaction &tx = *ptx; // 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 &orphanHash = orphanTx.GetHash(); vOrphanErase.push_back(orphanHash); } } } // Erase orphan transactions include or precluded by this block if (vOrphanErase.size()) { int nErased = 0; for (uint256 &orphanId : vOrphanErase) { nErased += EraseOrphanTx(orphanId); } LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased); } g_last_tip_update = GetTime(); } static CCriticalSection cs_most_recent_block; static std::shared_ptr most_recent_block GUARDED_BY(cs_most_recent_block); static std::shared_ptr most_recent_compact_block GUARDED_BY(cs_most_recent_block); static uint256 most_recent_block_hash GUARDED_BY(cs_most_recent_block); 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) { AssertLockHeld(cs_main); // 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(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerLogicValidation::NewPoWValidBlock", hashBlock.ToString(), pnode->GetId()); 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)) { // Don't send reject message with code 0 or an internal reject code. if (it != mapBlockSource.end() && State(it->second.first) && state.GetRejectCode() > 0 && 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->GetId()).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, CConnman *connman, const std::atomic &interruptMsgProc) { const Consensus::Params &consensusParams = config.GetChainParams().GetConsensus(); 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(BlockValidity::SCRIPTS) && mi->second->IsValid(BlockValidity::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(BlockValidity::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(BCLog::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.hasData())) { // 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)); } } inline static void SendBlockTransactions(const CBlock &block, const BlockTransactionsRequest &req, CNode *pfrom, CConnman *connman) { BlockTransactions resp(req); for (size_t i = 0; i < req.indices.size(); i++) { if (req.indices[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->GetId()); return; } resp.txn[i] = block.vtx[req.indices[i]]; } LOCK(cs_main); const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); int nSendFlags = 0; connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); } static bool ProcessHeadersMessage(const Config &config, CNode *pfrom, CConnman *connman, const std::vector &headers, bool punish_duplicate_invalid) { const CChainParams &chainparams = config.GetChainParams(); const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); size_t nCount = headers.size(); if (nCount == 0) { // Nothing interesting. Stop asking this peers for more headers. return true; } bool received_new_header = false; 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(BCLog::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->GetId(), 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(); } // If we don't have the last header, then they'll have given us // something new (if these headers are valid). if (mapBlockIndex.find(hashLastBlock) == mapBlockIndex.end()) { received_new_header = true; } } CValidationState state; CBlockHeader first_invalid_header; if (!ProcessNewBlockHeaders(config, headers, state, &pindexLast, &first_invalid_header)) { int nDoS; if (state.IsInvalid(nDoS)) { if (nDoS > 0) { LOCK(cs_main); Misbehaving(pfrom, nDoS, state.GetRejectReason()); } if (punish_duplicate_invalid && mapBlockIndex.find(first_invalid_header.GetHash()) != mapBlockIndex.end()) { // Goal: don't allow outbound peers to use up our outbound // connection slots if they are on incompatible chains. // // We ask the caller to set punish_invalid appropriately based // on the peer and the method of header delivery (compact blocks // are allowed to be invalid in some circumstances, under BIP // 152). // Here, we try to detect the narrow situation that we have a // valid block header (ie it was valid at the time the header // was received, and hence stored in mapBlockIndex) but know the // block is invalid, and that a peer has announced that same // block as being on its active chain. Disconnect the peer in // such a situation. // // Note: if the header that is invalid was not accepted to our // mapBlockIndex at all, that may also be grounds for // disconnecting the peer, as the chain they are on is likely to // be incompatible. However, there is a circumstance where that // does not hold: if the header's timestamp is more than 2 hours // ahead of our current time. In that case, the header may // become valid in the future, and we don't want to disconnect a // peer merely for serving us one too-far-ahead block header, to // prevent an attacker from splitting the network by mining a // block right at the 2 hour boundary. // // TODO: update the DoS logic (or, rather, rewrite the // DoS-interface between validation and net_processing) so that // the interface is cleaner, and so that we disconnect on all // the reasons that a peer's headers chain is incompatible with // ours (eg block->nVersion softforks, MTP violations, etc), and // not just the duplicate-invalid case. pfrom->fDisconnect = true; } return error("invalid header received"); } } { LOCK(cs_main); CNodeState *nodestate = State(pfrom->GetId()); if (nodestate->nUnconnectingHeaders > 0) { LogPrint(BCLog::NET, "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom->GetId(), nodestate->nUnconnectingHeaders); } nodestate->nUnconnectingHeaders = 0; assert(pindexLast); UpdateBlockAvailability(pfrom->GetId(), pindexLast->GetBlockHash()); // From here, pindexBestKnownBlock should be guaranteed to be non-null, // because it is set in UpdateBlockAvailability. Some nullptr checks are // still present, however, as belt-and-suspenders. if (received_new_header && pindexLast->nChainWork > chainActive.Tip()->nChainWork) { nodestate->m_last_block_announcement = GetTime(); } 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( BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom->GetId(), 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(BlockValidity::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.hasData() && !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( BCLog::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; } vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash())); MarkBlockAsInFlight(config, pfrom->GetId(), pindex->GetBlockHash(), chainparams.GetConsensus(), pindex); LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n", pindex->GetBlockHash().ToString(), pfrom->GetId()); } if (vGetData.size() > 1) { LogPrint(BCLog::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(BlockValidity::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)); } } } // If we're in IBD, we want outbound peers that will serve us a useful // chain. Disconnect peers that are on chains with insufficient work. if (IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) { // When nCount < MAX_HEADERS_RESULTS, we know we have no more // headers to fetch from this peer. if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { // This peer has too little work on their headers chain to help // us sync -- disconnect if using an outbound slot (unless // whitelisted or addnode). // Note: We compare their tip to nMinimumChainWork (rather than // chainActive.Tip()) because we won't start block download // until we have a headers chain that has at least // nMinimumChainWork, even if a peer has a chain past our tip, // as an anti-DoS measure. if (IsOutboundDisconnectionCandidate(pfrom)) { LogPrintf("Disconnecting outbound peer %d -- headers " "chain has insufficient work\n", pfrom->GetId()); pfrom->fDisconnect = true; } } } if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr) { // If this is an outbound peer, check to see if we should protect it // from the bad/lagging chain logic. if (g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= chainActive.Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom->GetId()); nodestate->m_chain_sync.m_protect = true; ++g_outbound_peers_with_protect_from_disconnect; } } } return true; } static bool ProcessMessage(const Config &config, CNode *pfrom, const std::string &strCommand, CDataStream &vRecv, int64_t nTimeReceived, CConnman *connman, const std::atomic &interruptMsgProc) { const CChainParams &chainparams = config.GetChainParams(); LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(strCommand), vRecv.size(), pfrom->GetId()); if (gArgs.IsArgSet("-dropmessagestest") && GetRand(gArgs.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 (LogAcceptCategory(BCLog::NET)) { 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(BCLog::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(BCLog::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->fInbound && !pfrom->fFeeler && !pfrom->m_manual_connection && !HasAllDesirableServiceFlags(nServices)) { LogPrint( BCLog::NET, "peer=%d does not offer the expected services " "(%08x offered, %08x expected); disconnecting\n", pfrom->GetId(), nServices, GetDesirableServiceFlags(nServices)); connman->PushMessage( pfrom, CNetMsgMaker(INIT_PROTO_VERSION) .Make(NetMsgType::REJECT, strCommand, REJECT_NONSTANDARD, strprintf("Expected to offer services %08x", GetDesirableServiceFlags(nServices)))); 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->GetId(), 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(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString()); pfrom->PushAddress(addr, insecure_rand); } else if (IsPeerAddrLocalGood(pfrom)) { addr.SetIP(addrMe); LogPrint(BCLog::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->GetId(), remoteAddr); 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; } // We only bother storing full nodes, though this may include things // which we would not make an outbound connection to, in part // because we may make feeler connections to them. if (!MayHaveUsefulAddressDB(addr.nServices)) { 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 && gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) { fBlocksOnly = false; } LOCK(cs_main); std::vector vToFetch; for (CInv &inv : vInv) { if (interruptMsgProc) { return true; } bool fAlreadyHave = AlreadyHave(inv); LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom->GetId()); 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(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->GetId()); } } else { pfrom->AddInventoryKnown(inv); if (fBlocksOnly) { LogPrint(BCLog::NET, "transaction (%s) inv sent in " "violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId()); } 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()); } LogPrint(BCLog::NET, "received getdata (%u invsz) peer=%d\n", vInv.size(), pfrom->GetId()); if (vInv.size() > 0) { LogPrint(BCLog::NET, "received getdata for: %s peer=%d\n", vInv[0].ToString(), pfrom->GetId()); } pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end()); ProcessGetData(config, pfrom, 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(BCLog::NET, "getblocks %d to %s limit %d from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), nLimit, pfrom->GetId()); for (; pindex; pindex = chainActive.Next(pindex)) { if (pindex->GetBlockHash() == hashStop) { LogPrint(BCLog::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.hasData() || pindex->nHeight <= chainActive.Tip()->nHeight - nPrunedBlocksLikelyToHave)) { LogPrint( BCLog::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(BCLog::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.hasData()) { LogPrintf("Peer %d sent us a getblocktxn for a block we don't have", pfrom->GetId()); 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(BCLog::NET, "Peer %d sent us a getblocktxn for a block > %i deep", pfrom->GetId(), MAX_BLOCKTXN_DEPTH); CInv inv; inv.type = MSG_BLOCK; inv.hash = req.blockhash; pfrom->vRecvGetData.push_back(inv); ProcessGetData(config, pfrom, 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(BCLog::NET, "Ignoring getheaders from peer=%d because " "node is in initial block download\n", pfrom->GetId()); 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(BCLog::NET, "getheaders %d to %s from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), pfrom->GetId()); 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 || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId()); 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); if (!AlreadyHave(inv) && AcceptToMemoryPool(config, mempool, state, ptx, true, &fMissingInputs)) { mempool.check(pcoinsTip.get()); RelayTransaction(tx, connman); for (size_t i = 0; i < tx.vout.size(); i++) { vWorkQueue.emplace_back(inv.hash, i); } pfrom->nLastTXTime = GetTime(); LogPrint(BCLog::MEMPOOL, "AcceptToMemoryPool: peer=%d: accepted %s " "(poolsz %u txn, %u kB)\n", pfrom->GetId(), 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)) { LogPrint(BCLog::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(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanId.ToString()); } // Has inputs but not accepted to mempool // Probably non-standard or insufficient fee/priority LogPrint(BCLog::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.get()); } } for (const 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.GetTxId())) { fRejectedParents = true; break; } } if (!fRejectedParents) { for (const CTxIn &txin : tx.vin) { // FIXME: MSG_TX should use a TxHash, not a TxId. CInv _inv(MSG_TX, txin.prevout.GetTxId()); 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), gArgs.GetArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS)); unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); if (nEvicted > 0) { LogPrint(BCLog::MEMPOOL, "mapOrphan overflow, removed %u tx\n", nEvicted); } } else { LogPrint(BCLog::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 && gArgs.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->GetId()); RelayTransaction(tx, connman); } else { LogPrintf("Not relaying invalid transaction %s from " "whitelisted peer=%d (%s)\n", tx.GetId().ToString(), pfrom->GetId(), FormatStateMessage(state)); } } } int nDoS = 0; if (state.IsInvalid(nDoS)) { LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); // Never send AcceptToMemoryPool's internal codes over P2P. if (state.GetRejectCode() > 0 && 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; bool received_new_header = false; { 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; } if (mapBlockIndex.find(cmpctblock.header.GetHash()) == mapBlockIndex.end()) { received_new_header = 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->GetId()); 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; // 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()); CNodeState *nodestate = State(pfrom->GetId()); // If this was a new header with more work than our tip, update the // peer's last block announcement time if (received_new_header && pindex->nChainWork > chainActive.Tip()->nChainWork) { nodestate->m_last_block_announcement = GetTime(); } std::map::iterator>>:: iterator blockInFlightIt = mapBlocksInFlight.find(pindex->GetBlockHash()); bool fAlreadyInFlight = blockInFlightIt != mapBlocksInFlight.end(); if (pindex->nStatus.hasData()) { // 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, 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; } // 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(BCLog::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->GetId()); return true; } else if (status == READ_STATUS_FAILED) { // Duplicate txindices, the block is now in-flight, so // just request it. std::vector vInv(1); vInv[0] = CInv(MSG_BLOCK, 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.indices.push_back(i); } } if (req.indices.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, 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. fRevertToHeaderProcessing = true; } } } // cs_main if (fProcessBLOCKTXN) { return ProcessMessage(config, pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, connman, interruptMsgProc); } if (fRevertToHeaderProcessing) { // Headers received from HB compact block peers are permitted to be // relayed before full validation (see BIP 152), so we don't want to // disconnect the peer if the header turns out to be for an invalid // block. // Note that if a peer tries to build on an invalid chain, that will // be detected and the peer will be banned. return ProcessHeadersMessage(config, pfrom, connman, {cmpctblock.header}, /*punish_duplicate_invalid=*/false); } 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; // Setting fForceProcessing to true means that we bypass some of // our anti-DoS protections in AcceptBlock, which filters // unrequested blocks that might be trying to waste our resources // (eg disk space). Because we only try to reconstruct blocks when // we're close to caught up (via the CanDirectFetch() requirement // above, combined with the behavior of not requesting blocks until // we have a chain with at least nMinimumChainWork), and we ignore // compact blocks with less work than our tip, it is safe to treat // reconstructed compact blocks as having been requested. ProcessNewBlock(config, pblock, /*fForceProcessing=*/true, &fNewBlock); if (fNewBlock) { pfrom->nLastBlockTime = GetTime(); } // hold cs_main for CBlockIndex::IsValid() LOCK(cs_main); if (pindex->IsValid(BlockValidity::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(BCLog::NET, "Peer %d sent us block transactions for block " "we weren't expecting\n", pfrom->GetId()); 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->GetId()); 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, 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) // This bypasses some anti-DoS logic in AcceptBlock (eg to prevent // disk-space attacks), but this should be safe due to the // protections in the compact block handler -- see related comment // in compact block optimistic reconstruction handling. ProcessNewBlock(config, pblock, /*fForceProcessing=*/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); } // Headers received via a HEADERS message should be valid, and reflect // the chain the peer is on. If we receive a known-invalid header, // disconnect the peer if it is using one of our outbound connection // slots. bool should_punish = !pfrom->fInbound && !pfrom->m_manual_connection; return ProcessHeadersMessage(config, pfrom, connman, headers, should_punish); } else if (strCommand == NetMsgType::BLOCK && !fImporting && !fReindex) { // Ignore blocks received while importing. std::shared_ptr pblock = std::make_shared(); vRecv >> *pblock; LogPrint(BCLog::NET, "received block %s peer=%d\n", pblock->GetHash().ToString(), pfrom->GetId()); // 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(BCLog::NET, "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom->GetId()); return true; } // Only send one GetAddr response per connection to reduce resource // waste and discourage addr stamping of INV announcements. if (pfrom->fSentAddr) { LogPrint(BCLog::NET, "Ignoring repeated \"getaddr\". peer=%d\n", pfrom->GetId()); 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(BCLog::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(BCLog::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(BCLog::NET, "pong peer=%d: %s, %x expected, %x received, %u bytes\n", pfrom->GetId(), 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); pfrom->pfilter.reset(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) { pfrom->pfilter.reset(new CBloomFilter()); } pfrom->fRelayTxes = true; } else if (strCommand == NetMsgType::FEEFILTER) { Amount newFeeFilter = Amount::zero(); vRecv >> newFeeFilter; if (MoneyRange(newFeeFilter)) { { LOCK(pfrom->cs_feeFilter); pfrom->minFeeFilter = newFeeFilter; } LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom->GetId()); } } 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(BCLog::NET, "Unknown command \"%s\" from peer=%d\n", SanitizeString(strCommand), pfrom->GetId()); } 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->m_manual_connection) { LogPrintf("Warning: not punishing manually-connected 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 PeerLogicValidation::ProcessMessages(const Config &config, CNode *pfrom, std::atomic &interruptMsgProc) { const CChainParams &chainparams = config.GetChainParams(); // // Message format // (4) message start // (12) command // (4) size // (4) checksum // (x) data // bool fMoreWork = false; if (!pfrom->vRecvGetData.empty()) { ProcessGetData(config, pfrom, 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()); // Scan for message start if (memcmp(std::begin(msg.hdr.pchMessageStart), std::begin(chainparams.NetMagic()), CMessageHeader::MESSAGE_START_SIZE) != 0) { LogPrintf("PROCESSMESSAGE: INVALID MESSAGESTART %s peer=%d\n", SanitizeString(msg.hdr.GetCommand()), pfrom->GetId()); // Make sure we ban where that come from for some time. connman->Ban(pfrom->addr, BanReasonNodeMisbehaving); pfrom->fDisconnect = true; return false; } // Read header CMessageHeader &hdr = msg.hdr; if (!hdr.IsValid(config)) { LogPrintf("PROCESSMESSAGE: ERRORS IN HEADER %s peer=%d\n", SanitizeString(hdr.GetCommand()), pfrom->GetId()); 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, 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->GetId()); } LOCK(cs_main); SendRejectsAndCheckIfBanned(pfrom, connman); return fMoreWork; } void PeerLogicValidation::ConsiderEviction(CNode *pto, int64_t time_in_seconds) { AssertLockHeld(cs_main); CNodeState &state = *State(pto->GetId()); const CNetMsgMaker msgMaker(pto->GetSendVersion()); if (!state.m_chain_sync.m_protect && IsOutboundDisconnectionCandidate(pto) && state.fSyncStarted) { // This is an outbound peer subject to disconnection if they don't // announce a block with as much work as the current tip within // CHAIN_SYNC_TIMEOUT + HEADERS_RESPONSE_TIME seconds (note: if their // chain has more work than ours, we should sync to it, unless it's // invalid, in which case we should find that out and disconnect from // them elsewhere). if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= chainActive.Tip()->nChainWork) { if (state.m_chain_sync.m_timeout != 0) { state.m_chain_sync.m_timeout = 0; state.m_chain_sync.m_work_header = nullptr; state.m_chain_sync.m_sent_getheaders = false; } } else if (state.m_chain_sync.m_timeout == 0 || (state.m_chain_sync.m_work_header != nullptr && state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= state.m_chain_sync.m_work_header->nChainWork)) { // Our best block known by this peer is behind our tip, and we're // either noticing that for the first time, OR this peer was able to // catch up to some earlier point where we checked against our tip. // Either way, set a new timeout based on current tip. state.m_chain_sync.m_timeout = time_in_seconds + CHAIN_SYNC_TIMEOUT; state.m_chain_sync.m_work_header = chainActive.Tip(); state.m_chain_sync.m_sent_getheaders = false; } else if (state.m_chain_sync.m_timeout > 0 && time_in_seconds > state.m_chain_sync.m_timeout) { // No evidence yet that our peer has synced to a chain with work // equal to that of our tip, when we first detected it was behind. // Send a single getheaders message to give the peer a chance to // update us. if (state.m_chain_sync.m_sent_getheaders) { // They've run out of time to catch up! LogPrintf( "Disconnecting outbound peer %d for old chain, best known " "block = %s\n", pto->GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : ""); pto->fDisconnect = true; } else { LogPrint( BCLog::NET, "sending getheaders to outbound peer=%d to " "verify chain work (current best known " "block:%s, benchmark blockhash: %s)\n", pto->GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "", state.m_chain_sync.m_work_header->GetBlockHash() .ToString()); connman->PushMessage( pto, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator( state.m_chain_sync.m_work_header->pprev), uint256())); state.m_chain_sync.m_sent_getheaders = true; // 2 minutes constexpr int64_t HEADERS_RESPONSE_TIME = 120; // Bump the timeout to allow a response, which could clear the // timeout (if the response shows the peer has synced), reset // the timeout (if the peer syncs to the required work but not // to our tip), or result in disconnect (if we advance to the // timeout and pindexBestKnownBlock has not sufficiently // progressed) state.m_chain_sync.m_timeout = time_in_seconds + HEADERS_RESPONSE_TIME; } } } } void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds) { // Check whether we have too many outbound peers int extra_peers = connman->GetExtraOutboundCount(); if (extra_peers <= 0) { return; } // If we have more outbound peers than we target, disconnect one. // Pick the outbound peer that least recently announced us a new block, with // ties broken by choosing the more recent connection (higher node id) NodeId worst_peer = -1; int64_t oldest_block_announcement = std::numeric_limits::max(); LOCK(cs_main); connman->ForEachNode([&](CNode *pnode) { AssertLockHeld(cs_main); // Ignore non-outbound peers, or nodes marked for disconnect already if (!IsOutboundDisconnectionCandidate(pnode) || pnode->fDisconnect) { return; } CNodeState *state = State(pnode->GetId()); if (state == nullptr) { // shouldn't be possible, but just in case return; } // Don't evict our protected peers if (state->m_chain_sync.m_protect) { return; } if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) { worst_peer = pnode->GetId(); oldest_block_announcement = state->m_last_block_announcement; } }); if (worst_peer == -1) { return; } bool disconnected = connman->ForNode(worst_peer, [&](CNode *pnode) { AssertLockHeld(cs_main); // Only disconnect a peer that has been connected to us for some // reasonable fraction of our check-frequency, to give it time for new // information to have arrived. // Also don't disconnect any peer we're trying to download a block from. CNodeState &state = *State(pnode->GetId()); if (time_in_seconds - pnode->nTimeConnected > MINIMUM_CONNECT_TIME && state.nBlocksInFlight == 0) { LogPrint(BCLog::NET, "disconnecting extra outbound peer=%d (last " "block announcement received at time %d)\n", pnode->GetId(), oldest_block_announcement); pnode->fDisconnect = true; return true; } else { LogPrint(BCLog::NET, "keeping outbound peer=%d chosen for eviction " "(connect time: %d, blocks_in_flight: %d)\n", pnode->GetId(), pnode->nTimeConnected, state.nBlocksInFlight); return false; } }); if (disconnected) { // If we disconnected an extra peer, that means we successfully // connected to at least one peer after the last time we detected a // stale tip. Don't try any more extra peers until we next detect a // stale tip, to limit the load we put on the network from these extra // connections. connman->SetTryNewOutboundPeer(false); } } void PeerLogicValidation::CheckForStaleTipAndEvictPeers( const Consensus::Params &consensusParams) { if (connman == nullptr) { return; } int64_t time_in_seconds = GetTime(); EvictExtraOutboundPeers(time_in_seconds); if (time_in_seconds <= m_stale_tip_check_time) { return; } LOCK(cs_main); // Check whether our tip is stale, and if so, allow using an extra outbound // peer. if (TipMayBeStale(consensusParams)) { LogPrintf("Potential stale tip detected, will try using extra outbound " "peer (last tip update: %d seconds ago)\n", time_in_seconds - g_last_tip_update); connman->SetTryNewOutboundPeer(true); } else if (connman->GetTryNewOutboundPeer()) { connman->SetTryNewOutboundPeer(false); } m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL; } namespace { class CompareInvMempoolOrder { CTxMemPool *mp; public: explicit 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 PeerLogicValidation::SendMessages(const Config &config, CNode *pto, std::atomic &interruptMsgProc) { const Consensus::Params &consensusParams = config.GetChainParams().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; state.nHeadersSyncTimeout = GetTimeMicros() + HEADERS_DOWNLOAD_TIMEOUT_BASE + HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER * (GetAdjustedTime() - pindexBestHeader->GetBlockTime()) / (consensusParams.nPowTargetSpacing); 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(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->GetId(), 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->GetId()); 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(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", __func__, vHeaders.front().GetHash().ToString(), pto->GetId()); 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(BCLog::NET, "%s: %u headers, range (%s, %s), to peer=%d\n", __func__, vHeaders.size(), vHeaders.front().GetHash().ToString(), vHeaders.back().GetHash().ToString(), pto->GetId()); } else { LogPrint(BCLog::NET, "%s: sending header %s to peer=%d\n", __func__, vHeaders.front().GetHash().ToString(), pto->GetId()); } 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(BCLog::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(BCLog::NET, "%s: sending inv peer=%d hash=%s\n", __func__, pto->GetId(), hashToAnnounce.ToString()); } } } pto->vBlockHashesToAnnounce.clear(); } // // Message: inventory // std::vector vInv; { LOCK(pto->cs_inventory); vInv.reserve(std::max(pto->vInventoryBlockToSend.size(), INVENTORY_BROADCAST_MAX_PER_MB * config.GetMaxBlockSize() / 1000000)); // 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 = Amount::zero(); { 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::zero() && txinfo.feeRate.GetFeePerK() < filterrate) { continue; } if (pto->pfilter && !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 = Amount::zero(); { 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_PER_MB * config.GetMaxBlockSize() / 1000000) { // 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::zero() && 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->GetId()); 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->GetId()); pto->fDisconnect = true; return true; } } // Check for headers sync timeouts if (state.fSyncStarted && state.nHeadersSyncTimeout < std::numeric_limits::max()) { // Detect whether this is a stalling initial-headers-sync peer if (pindexBestHeader->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) { if (nNow > state.nHeadersSyncTimeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { // Disconnect a (non-whitelisted) peer if it is our only sync // peer, and we have others we could be using instead. // Note: If all our peers are inbound, then we won't disconnect // our sync peer for stalling; we have bigger problems if we // can't get any outbound peers. if (!pto->fWhitelisted) { LogPrintf("Timeout downloading headers from peer=%d, " "disconnecting\n", pto->GetId()); pto->fDisconnect = true; return true; } else { LogPrintf("Timeout downloading headers from whitelisted " "peer=%d, not disconnecting\n", pto->GetId()); // Reset the headers sync state so that we have a chance to // try downloading from a different peer. // Note: this will also result in at least one more // getheaders message to be sent to this peer (eventually). state.fSyncStarted = false; nSyncStarted--; state.nHeadersSyncTimeout = 0; } } } else { // After we've caught up once, reset the timeout so we can't trigger // disconnect later. state.nHeadersSyncTimeout = std::numeric_limits::max(); } } // Check that outbound peers have reasonable chains GetTime() is used by // this anti-DoS logic so we can test this using mocktime. ConsiderEviction(pto, GetTime()); // // 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) { vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash())); MarkBlockAsInFlight(config, pto->GetId(), pindex->GetBlockHash(), consensusParams, pindex); LogPrint(BCLog::NET, "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), pindex->nHeight, pto->GetId()); } if (state.nBlocksInFlight == 0 && staller != -1) { if (State(staller)->nStallingSince == 0) { State(staller)->nStallingSince = nNow; LogPrint(BCLog::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)) { LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId()); 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 && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && !(pto->fWhitelisted && gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY))) { Amount currentFilter = mempool .GetMinFee( gArgs.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_PER_KB); 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 (gArgs.GetArg("-limitfreerelay", DEFAULT_LIMITFREERELAY) <= 0) { filterToSend = std::max(filterToSend, config.GetMinFeePerKB().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/scheduler.cpp b/src/scheduler.cpp index f819a1822..6d5024463 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -1,213 +1,217 @@ // Copyright (c) 2015-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "scheduler.h" #include "random.h" #include "reverselock.h" #include #include #include CScheduler::CScheduler() : nThreadsServicingQueue(0), stopRequested(false), stopWhenEmpty(false) {} CScheduler::~CScheduler() { assert(nThreadsServicingQueue == 0); } #if BOOST_VERSION < 105000 static boost::system_time toPosixTime(const boost::chrono::system_clock::time_point &t) { return boost::posix_time::from_time_t( boost::chrono::system_clock::to_time_t(t)); } #endif void CScheduler::serviceQueue() { boost::unique_lock lock(newTaskMutex); ++nThreadsServicingQueue; // newTaskMutex is locked throughout this loop EXCEPT when the thread is // waiting or when the user's function is called. while (!shouldStop()) { try { if (!shouldStop() && taskQueue.empty()) { reverse_lock> rlock(lock); // Use this chance to get a tiny bit more entropy RandAddSeedSleep(); } while (!shouldStop() && taskQueue.empty()) { // Wait until there is something to do. newTaskScheduled.wait(lock); } // Wait until either there is a new task, or until the time of the first item on // the queue: // wait_until needs boost 1.50 or later; older versions have timed_wait: #if BOOST_VERSION < 105000 while (!shouldStop() && !taskQueue.empty() && newTaskScheduled.timed_wait( lock, toPosixTime(taskQueue.begin()->first))) { // Keep waiting until timeout } #else // Some boost versions have a conflicting overload of wait_until // that returns void. Explicitly use a template here to avoid // hitting that overload. while (!shouldStop() && !taskQueue.empty()) { boost::chrono::system_clock::time_point timeToWaitFor = taskQueue.begin()->first; if (newTaskScheduled.wait_until<>(lock, timeToWaitFor) == boost::cv_status::timeout) { // Exit loop after timeout, it means we reached the time of // the event break; } } #endif // If there are multiple threads, the queue can empty while we're // waiting (another thread may service the task we were waiting on). - if (shouldStop() || taskQueue.empty()) continue; + if (shouldStop() || taskQueue.empty()) { + continue; + } Function f = taskQueue.begin()->second; taskQueue.erase(taskQueue.begin()); { // Unlock before calling f, so it can reschedule itself or // another task without deadlocking: reverse_lock> rlock(lock); f(); } } catch (...) { --nThreadsServicingQueue; throw; } } --nThreadsServicingQueue; newTaskScheduled.notify_one(); } void CScheduler::stop(bool drain) { { boost::unique_lock lock(newTaskMutex); - if (drain) + if (drain) { stopWhenEmpty = true; - else + } else { stopRequested = true; + } } newTaskScheduled.notify_all(); } void CScheduler::schedule(CScheduler::Function f, boost::chrono::system_clock::time_point t) { { boost::unique_lock lock(newTaskMutex); taskQueue.insert(std::make_pair(t, f)); } newTaskScheduled.notify_one(); } void CScheduler::scheduleFromNow(CScheduler::Function f, int64_t deltaMilliSeconds) { schedule(f, boost::chrono::system_clock::now() + boost::chrono::milliseconds(deltaMilliSeconds)); } -static void Repeat(CScheduler *s, CScheduler::Function f, +static void Repeat(CScheduler *s, CScheduler::Predicate p, int64_t deltaMilliSeconds) { - f(); - s->scheduleFromNow(boost::bind(&Repeat, s, f, deltaMilliSeconds), - deltaMilliSeconds); + if (p()) { + s->scheduleFromNow(boost::bind(&Repeat, s, p, deltaMilliSeconds), + deltaMilliSeconds); + } } -void CScheduler::scheduleEvery(CScheduler::Function f, +void CScheduler::scheduleEvery(CScheduler::Predicate p, int64_t deltaMilliSeconds) { - scheduleFromNow(boost::bind(&Repeat, this, f, deltaMilliSeconds), + scheduleFromNow(boost::bind(&Repeat, this, p, deltaMilliSeconds), deltaMilliSeconds); } size_t CScheduler::getQueueInfo(boost::chrono::system_clock::time_point &first, boost::chrono::system_clock::time_point &last) const { boost::unique_lock lock(newTaskMutex); size_t result = taskQueue.size(); if (!taskQueue.empty()) { first = taskQueue.begin()->first; last = taskQueue.rbegin()->first; } return result; } bool CScheduler::AreThreadsServicingQueue() const { return nThreadsServicingQueue; } void SingleThreadedSchedulerClient::MaybeScheduleProcessQueue() { { LOCK(m_cs_callbacks_pending); // Try to avoid scheduling too many copies here, but if we // accidentally have two ProcessQueue's scheduled at once its // not a big deal. if (m_are_callbacks_running) return; if (m_callbacks_pending.empty()) return; } m_pscheduler->schedule( std::bind(&SingleThreadedSchedulerClient::ProcessQueue, this)); } void SingleThreadedSchedulerClient::ProcessQueue() { std::function callback; { LOCK(m_cs_callbacks_pending); if (m_are_callbacks_running) return; if (m_callbacks_pending.empty()) return; m_are_callbacks_running = true; callback = std::move(m_callbacks_pending.front()); m_callbacks_pending.pop_front(); } // RAII the setting of fCallbacksRunning and calling // MaybeScheduleProcessQueue // to ensure both happen safely even if callback() throws. struct RAIICallbacksRunning { SingleThreadedSchedulerClient *instance; RAIICallbacksRunning(SingleThreadedSchedulerClient *_instance) : instance(_instance) {} ~RAIICallbacksRunning() { { LOCK(instance->m_cs_callbacks_pending); instance->m_are_callbacks_running = false; } instance->MaybeScheduleProcessQueue(); } } raiicallbacksrunning(this); callback(); } void SingleThreadedSchedulerClient::AddToProcessQueue( std::function func) { assert(m_pscheduler); { LOCK(m_cs_callbacks_pending); m_callbacks_pending.emplace_back(std::move(func)); } MaybeScheduleProcessQueue(); } void SingleThreadedSchedulerClient::EmptyQueue() { assert(!m_pscheduler->AreThreadsServicingQueue()); bool should_continue = true; while (should_continue) { ProcessQueue(); LOCK(m_cs_callbacks_pending); should_continue = !m_callbacks_pending.empty(); } } diff --git a/src/scheduler.h b/src/scheduler.h index 9ccdc43e5..efd4f9851 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -1,119 +1,120 @@ // Copyright (c) 2015 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_SCHEDULER_H #define BITCOIN_SCHEDULER_H #include "sync.h" // // NOTE: // boost::thread / boost::chrono should be ported to // std::thread / std::chrono when we support C++11. // #include #include #include // // Simple class for background tasks that should be run periodically or once // "after a while" // // Usage: // // CScheduler* s = new CScheduler(); // s->scheduleFromNow(doSomething, 11); // Assuming a: void doSomething() { } // s->scheduleFromNow(std::bind(Class::func, this, argument), 3); // boost::thread* t = new boost::thread(boost::bind(CScheduler::serviceQueue, // s)); // // ... then at program shutdown, clean up the thread running serviceQueue: // t->interrupt(); // t->join(); // delete t; // delete s; // Must be done after thread is interrupted/joined. // class CScheduler { public: CScheduler(); ~CScheduler(); typedef std::function Function; + typedef std::function Predicate; // Call func at/after time t void schedule(Function f, boost::chrono::system_clock::time_point t = boost::chrono::system_clock::now()); // Convenience method: call f once deltaMilliSeconds from now void scheduleFromNow(Function f, int64_t deltaMilliSeconds); // Another convenience method: call f approximately every deltaMilliSeconds // forever, starting deltaMilliSeconds from now. To be more precise: every // time f is finished, it is rescheduled to run deltaMilliSeconds later. If // you need more accurate scheduling, don't use this method. - void scheduleEvery(Function f, int64_t deltaMilliSeconds); + void scheduleEvery(Predicate p, int64_t deltaMilliSeconds); // To keep things as simple as possible, there is no unschedule. // Services the queue 'forever'. Should be run in a thread, and interrupted // using boost::interrupt_thread void serviceQueue(); // Tell any threads running serviceQueue to stop as soon as they're done // servicing whatever task they're currently servicing (drain=false) or when // there is no work left to be done (drain=true) void stop(bool drain = false); // Returns number of tasks waiting to be serviced, and first and last task // times size_t getQueueInfo(boost::chrono::system_clock::time_point &first, boost::chrono::system_clock::time_point &last) const; // Returns true if there are threads actively running in serviceQueue() bool AreThreadsServicingQueue() const; private: std::multimap taskQueue; boost::condition_variable newTaskScheduled; mutable boost::mutex newTaskMutex; int nThreadsServicingQueue; bool stopRequested; bool stopWhenEmpty; bool shouldStop() { return stopRequested || (stopWhenEmpty && taskQueue.empty()); } }; /** * Class used by CScheduler clients which may schedule multiple jobs * which are required to be run serially. Does not require such jobs * to be executed on the same thread, but no two jobs will be executed * at the same time. */ class SingleThreadedSchedulerClient { private: CScheduler *m_pscheduler; CCriticalSection m_cs_callbacks_pending; std::list> m_callbacks_pending; bool m_are_callbacks_running = false; void MaybeScheduleProcessQueue(); void ProcessQueue(); public: SingleThreadedSchedulerClient(CScheduler *pschedulerIn) : m_pscheduler(pschedulerIn) {} void AddToProcessQueue(std::function func); // Processes all remaining queue members on the calling thread, blocking // until queue is empty // Must be called after the CScheduler has no remaining processing threads! void EmptyQueue(); }; #endif diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index 0b2c61a58..ad09f9369 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -1,122 +1,177 @@ // Copyright (c) 2012-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 "random.h" #include "scheduler.h" #include "test/test_bitcoin.h" #include #include #include #include #include +#include +#include + BOOST_AUTO_TEST_SUITE(scheduler_tests) static void microTask(CScheduler &s, boost::mutex &mutex, int &counter, int delta, boost::chrono::system_clock::time_point rescheduleTime) { { boost::unique_lock lock(mutex); counter += delta; } boost::chrono::system_clock::time_point noTime = boost::chrono::system_clock::time_point::min(); if (rescheduleTime != noTime) { CScheduler::Function f = boost::bind(µTask, std::ref(s), std::ref(mutex), std::ref(counter), -delta + 1, noTime); s.schedule(f, rescheduleTime); } } static void MicroSleep(uint64_t n) { boost::this_thread::sleep_for(boost::chrono::microseconds(n)); } BOOST_AUTO_TEST_CASE(manythreads) { // Stress test: hundreds of microsecond-scheduled tasks, // serviced by 10 threads. // // So... ten shared counters, which if all the tasks execute // properly will sum to the number of tasks done. // Each task adds or subtracts a random amount from one of the // counters, and then schedules another task 0-1000 // microseconds in the future to subtract or add from // the counter -random_amount+1, so in the end the shared // counters should sum to the number of initial tasks performed. CScheduler microTasks; boost::mutex counterMutex[10]; int counter[10] = {0}; boost::random::mt19937 rng(42); boost::random::uniform_int_distribution<> zeroToNine(0, 9); boost::random::uniform_int_distribution<> randomMsec(-11, 1000); boost::random::uniform_int_distribution<> randomDelta(-1000, 1000); boost::chrono::system_clock::time_point start = boost::chrono::system_clock::now(); boost::chrono::system_clock::time_point now = start; boost::chrono::system_clock::time_point first, last; size_t nTasks = microTasks.getQueueInfo(first, last); BOOST_CHECK(nTasks == 0); for (int i = 0; i < 100; i++) { boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng)); boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); CScheduler::Function f = boost::bind( µTask, std::ref(microTasks), std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), randomDelta(rng), tReschedule); microTasks.schedule(f, t); } nTasks = microTasks.getQueueInfo(first, last); BOOST_CHECK(nTasks == 100); BOOST_CHECK(first < last); BOOST_CHECK(last > now); // As soon as these are created they will start running and servicing the // queue boost::thread_group microThreads; - for (int i = 0; i < 5; i++) + for (int i = 0; i < 5; i++) { microThreads.create_thread( boost::bind(&CScheduler::serviceQueue, µTasks)); + } MicroSleep(600); now = boost::chrono::system_clock::now(); // More threads and more tasks: - for (int i = 0; i < 5; i++) + for (int i = 0; i < 5; i++) { microThreads.create_thread( boost::bind(&CScheduler::serviceQueue, µTasks)); + } + for (int i = 0; i < 100; i++) { boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng)); boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); CScheduler::Function f = boost::bind( µTask, std::ref(microTasks), std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), randomDelta(rng), tReschedule); microTasks.schedule(f, t); } // Drain the task queue then exit threads microTasks.stop(true); - microThreads.join_all(); // ... wait until all the threads are done + // ... wait until all the threads are done + microThreads.join_all(); int counterSum = 0; for (int i = 0; i < 10; i++) { BOOST_CHECK(counter[i] != 0); counterSum += counter[i]; } BOOST_CHECK_EQUAL(counterSum, 200); } +BOOST_AUTO_TEST_CASE(schedule_every) { + CScheduler scheduler; + + boost::condition_variable cvar; + std::atomic counter{15}; + std::atomic keepRunning{true}; + + scheduler.scheduleEvery( + [&keepRunning, &cvar, &counter, &scheduler]() { + BOOST_CHECK(counter > 0); + cvar.notify_all(); + if (--counter > 0) { + return true; + } + + // We reached the end of our test, make sure nothing run again for + // 100ms. + scheduler.scheduleFromNow( + [&keepRunning, &cvar]() { + keepRunning = false; + cvar.notify_all(); + }, + 100); + + // We set the counter to some magic value to check the scheduler + // empty its queue properly after 120ms. + scheduler.scheduleFromNow([&counter]() { counter = 42; }, 120); + return false; + }, + 5); + + // Start the scheduler thread. + std::thread schedulerThread( + std::bind(&CScheduler::serviceQueue, &scheduler)); + + boost::mutex mutex; + boost::unique_lock lock(mutex); + while (keepRunning) { + cvar.wait(lock); + BOOST_CHECK(counter >= 0); + } + + BOOST_CHECK_EQUAL(counter, 0); + scheduler.stop(true); + schedulerThread.join(); + BOOST_CHECK_EQUAL(counter, 42); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f90691724..b7ac74694 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,4266 +1,4271 @@ // 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 "wallet/wallet.h" #include "chain.h" #include "checkpoints.h" #include "config.h" #include "consensus/consensus.h" #include "consensus/validation.h" #include "dstencode.h" #include "fs.h" #include "init.h" #include "key.h" #include "keystore.h" #include "net.h" #include "policy/policy.h" #include "primitives/block.h" #include "primitives/transaction.h" #include "scheduler.h" #include "script/script.h" #include "script/sighashtype.h" #include "script/sign.h" #include "timedata.h" #include "txmempool.h" #include "ui_interface.h" #include "util.h" #include "utilmoneystr.h" #include "validation.h" #include "wallet/coincontrol.h" #include "wallet/fees.h" #include "wallet/finaltx.h" #include #include std::vector vpwallets; /** Transaction fee set by the user */ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET; bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE; const char *DEFAULT_WALLET_DAT = "wallet.dat"; const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; /** * If fee estimation does not have enough data to provide estimates, use this * fee instead. Has no effect if not using fee estimation. * Override with -fallbackfee */ CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE); const uint256 CMerkleTx::ABANDON_HASH(uint256S( "0000000000000000000000000000000000000000000000000000000000000001")); /** @defgroup mapWallet * * @{ */ struct CompareValueOnly { bool operator()( const std::pair> &t1, const std::pair> &t2) const { return t1.first < t2.first; } }; std::string COutput::ToString() const { return strprintf("COutput(%s, %d, %d) [%s]", tx->GetId().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } class CAffectedKeysVisitor : public boost::static_visitor { private: const CKeyStore &keystore; std::vector &vKeys; public: CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} void Process(const CScript &script) { txnouttype type; std::vector vDest; int nRequired; if (ExtractDestinations(script, type, vDest, nRequired)) { for (const CTxDestination &dest : vDest) { boost::apply_visitor(*this, dest); } } } void operator()(const CKeyID &keyId) { if (keystore.HaveKey(keyId)) { vKeys.push_back(keyId); } } void operator()(const CScriptID &scriptId) { CScript script; if (keystore.GetCScript(scriptId, script)) { Process(script); } } void operator()(const CNoDestination &none) {} }; const CWalletTx *CWallet::GetWalletTx(const TxId &txid) const { LOCK(cs_wallet); std::map::const_iterator it = mapWallet.find(txid); if (it == mapWallet.end()) { return nullptr; } return &(it->second); } CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, bool internal) { // mapKeyMetadata AssertLockHeld(cs_wallet); // default to compressed public keys if we want 0.6.0 wallets bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); CKey secret; // Create new metadata int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); // use HD key derivation if HD was enabled during wallet creation if (IsHDEnabled()) { DeriveNewChildKey( walletdb, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); } else { secret.MakeNewKey(fCompressed); } // Compressed public keys were introduced in version 0.6.0 if (fCompressed) { SetMinVersion(FEATURE_COMPRPUBKEY); } CPubKey pubkey = secret.GetPubKey(); assert(secret.VerifyPubKey(pubkey)); mapKeyMetadata[pubkey.GetID()] = metadata; UpdateTimeFirstKey(nCreationTime); if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) { throw std::runtime_error(std::string(__func__) + ": AddKey failed"); } return pubkey; } void CWallet::DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata &metadata, CKey &secret, bool internal) { // for now we use a fixed keypath scheme of m/0'/0'/k // master key seed (256bit) CKey key; // hd master key CExtKey masterKey; // key at m/0' CExtKey accountKey; // key at m/0'/0' (external) or m/0'/1' (internal) CExtKey chainChildKey; // key at m/0'/0'/' CExtKey childKey; // try to get the master key if (!GetKey(hdChain.masterKeyID, key)) { throw std::runtime_error(std::string(__func__) + ": Master key not found"); } masterKey.SetMaster(key.begin(), key.size()); // derive m/0' // use hardened derivation (child keys >= 0x80000000 are hardened after // bip32) masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); // derive m/0'/0' (external chain) OR m/0'/1' (internal chain) assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true); accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT + (internal ? 1 : 0)); // derive child key at next index, skip keys already known to the wallet do { // always derive hardened keys // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened // child-index-range // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 if (internal) { chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'"; hdChain.nInternalChainCounter++; } else { chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'"; hdChain.nExternalChainCounter++; } } while (HaveKey(childKey.key.GetPubKey().GetID())); secret = childKey.key; metadata.hdMasterKeyID = hdChain.masterKeyID; // update the chain model in the database if (!walletdb.WriteHDChain(hdChain)) { throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); } } bool CWallet::AddKeyPubKeyWithDB(CWalletDB &walletdb, const CKey &secret, const CPubKey &pubkey) { // mapKeyMetadata AssertLockHeld(cs_wallet); // CCryptoKeyStore has no concept of wallet databases, but calls // AddCryptedKey // which is overridden below. To avoid flushes, the database handle is // tunneled through to it. bool needsDB = !pwalletdbEncryption; if (needsDB) { pwalletdbEncryption = &walletdb; } if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) { if (needsDB) { pwalletdbEncryption = nullptr; } return false; } if (needsDB) { pwalletdbEncryption = nullptr; } // Check if we need to remove from watch-only. CScript script; script = GetScriptForDestination(pubkey.GetID()); if (HaveWatchOnly(script)) { RemoveWatchOnly(script); } script = GetScriptForRawPubKey(pubkey); if (HaveWatchOnly(script)) { RemoveWatchOnly(script); } if (IsCrypted()) { return true; } return walletdb.WriteKey(pubkey, secret.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]); } bool CWallet::AddKeyPubKey(const CKey &secret, const CPubKey &pubkey) { CWalletDB walletdb(*dbw); return CWallet::AddKeyPubKeyWithDB(walletdb, secret, pubkey); } bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) { return false; } LOCK(cs_wallet); if (pwalletdbEncryption) { return pwalletdbEncryption->WriteCryptedKey( vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]); } return CWalletDB(*dbw).WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]); } bool CWallet::LoadKeyMetadata(const CTxDestination &keyID, const CKeyMetadata &meta) { // mapKeyMetadata AssertLockHeld(cs_wallet); UpdateTimeFirstKey(meta.nCreateTime); mapKeyMetadata[keyID] = meta; return true; } bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); } /** * Update wallet first key creation time. This should be called whenever keys * are added to the wallet, with the oldest key creation time. */ void CWallet::UpdateTimeFirstKey(int64_t nCreateTime) { AssertLockHeld(cs_wallet); if (nCreateTime <= 1) { // Cannot determine birthday information, so set the wallet birthday to // the beginning of time. nTimeFirstKey = 1; } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) { nTimeFirstKey = nCreateTime; } } bool CWallet::AddCScript(const CScript &redeemScript) { if (!CCryptoKeyStore::AddCScript(redeemScript)) { return false; } return CWalletDB(*dbw).WriteCScript(Hash160(redeemScript), redeemScript); } bool CWallet::LoadCScript(const CScript &redeemScript) { /** * A sanity check was added in pull #3843 to avoid adding redeemScripts that * never can be redeemed. However, old wallets may still contain these. Do * not add them to the wallet and warn. */ if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) { std::string strAddr = EncodeDestination(CScriptID(redeemScript)); LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i " "which exceeds maximum size %i thus can never be redeemed. " "Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); return true; } return CCryptoKeyStore::AddCScript(redeemScript); } bool CWallet::AddWatchOnly(const CScript &dest) { if (!CCryptoKeyStore::AddWatchOnly(dest)) { return false; } const CKeyMetadata &meta = mapKeyMetadata[CScriptID(dest)]; UpdateTimeFirstKey(meta.nCreateTime); NotifyWatchonlyChanged(true); return CWalletDB(*dbw).WriteWatchOnly(dest, meta); } bool CWallet::AddWatchOnly(const CScript &dest, int64_t nCreateTime) { mapKeyMetadata[CScriptID(dest)].nCreateTime = nCreateTime; return AddWatchOnly(dest); } bool CWallet::RemoveWatchOnly(const CScript &dest) { AssertLockHeld(cs_wallet); if (!CCryptoKeyStore::RemoveWatchOnly(dest)) { return false; } if (!HaveWatchOnly()) { NotifyWatchonlyChanged(false); } return CWalletDB(*dbw).EraseWatchOnly(dest); } bool CWallet::LoadWatchOnly(const CScript &dest) { return CCryptoKeyStore::AddWatchOnly(dest); } bool CWallet::Unlock(const SecureString &strWalletPassphrase) { CCrypter crypter; CKeyingMaterial _vMasterKey; LOCK(cs_wallet); for (const MasterKeyMap::value_type &pMasterKey : mapMasterKeys) { if (!crypter.SetKeyFromPassphrase( strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) { // try another master key continue; } if (CCryptoKeyStore::Unlock(_vMasterKey)) { return true; } } return false; } bool CWallet::ChangeWalletPassphrase( const SecureString &strOldWalletPassphrase, const SecureString &strNewWalletPassphrase) { bool fWasLocked = IsLocked(); LOCK(cs_wallet); Lock(); CCrypter crypter; CKeyingMaterial _vMasterKey; for (MasterKeyMap::value_type &pMasterKey : mapMasterKeys) { if (!crypter.SetKeyFromPassphrase( strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) { return false; } if (CCryptoKeyStore::Unlock(_vMasterKey)) { int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime))); nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / double(GetTimeMillis() - nStartTime)) / 2; if (pMasterKey.second.nDeriveIterations < 25000) { pMasterKey.second.nDeriveIterations = 25000; } LogPrintf( "Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); if (!crypter.SetKeyFromPassphrase( strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey)) { return false; } CWalletDB(*dbw).WriteMasterKey(pMasterKey.first, pMasterKey.second); if (fWasLocked) { Lock(); } return true; } } return false; } void CWallet::SetBestChain(const CBlockLocator &loc) { CWalletDB walletdb(*dbw); walletdb.WriteBestBlock(loc); } bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB *pwalletdbIn, bool fExplicit) { // nWalletVersion LOCK(cs_wallet); if (nWalletVersion >= nVersion) { return true; } // When doing an explicit upgrade, if we pass the max version permitted, // upgrade all the way. if (fExplicit && nVersion > nWalletMaxVersion) { nVersion = FEATURE_LATEST; } nWalletVersion = nVersion; if (nVersion > nWalletMaxVersion) { nWalletMaxVersion = nVersion; } CWalletDB *pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(*dbw); if (nWalletVersion > 40000) { pwalletdb->WriteMinVersion(nWalletVersion); } if (!pwalletdbIn) { delete pwalletdb; } return true; } bool CWallet::SetMaxVersion(int nVersion) { // nWalletVersion, nWalletMaxVersion LOCK(cs_wallet); // Cannot downgrade below current version if (nWalletVersion > nVersion) { return false; } nWalletMaxVersion = nVersion; return true; } std::set CWallet::GetConflicts(const TxId &txid) const { std::set result; AssertLockHeld(cs_wallet); std::map::const_iterator it = mapWallet.find(txid); if (it == mapWallet.end()) { return result; } const CWalletTx &wtx = it->second; std::pair range; for (const CTxIn &txin : wtx.tx->vin) { if (mapTxSpends.count(txin.prevout) <= 1) { // No conflict if zero or one spends. continue; } range = mapTxSpends.equal_range(txin.prevout); for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it) { result.insert(_it->second); } } return result; } bool CWallet::HasWalletSpend(const TxId &txid) const { AssertLockHeld(cs_wallet); auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0)); return (iter != mapTxSpends.end() && iter->first.GetTxId() == txid); } void CWallet::Flush(bool shutdown) { dbw->Flush(shutdown); } void CWallet::SyncMetaData( std::pair range) { // We want all the wallet transactions in range to have the same metadata as // the oldest (smallest nOrderPos). // So: find smallest nOrderPos: int nMinOrderPos = std::numeric_limits::max(); const CWalletTx *copyFrom = nullptr; for (TxSpends::iterator it = range.first; it != range.second; ++it) { const TxId &txid = it->second; int n = mapWallet[txid].nOrderPos; if (n < nMinOrderPos) { nMinOrderPos = n; copyFrom = &mapWallet[txid]; } } // Now copy data from copyFrom to rest: for (TxSpends::iterator it = range.first; it != range.second; ++it) { const TxId &txid = it->second; CWalletTx *copyTo = &mapWallet[txid]; if (copyFrom == copyTo) { continue; } if (!copyFrom->IsEquivalentTo(*copyTo)) { continue; } copyTo->mapValue = copyFrom->mapValue; copyTo->vOrderForm = copyFrom->vOrderForm; // fTimeReceivedIsTxTime not copied on purpose nTimeReceived not copied // on purpose. copyTo->nTimeSmart = copyFrom->nTimeSmart; copyTo->fFromMe = copyFrom->fFromMe; copyTo->strFromAccount = copyFrom->strFromAccount; // nOrderPos not copied on purpose cached members not copied on purpose. } } /** * Outpoint is spent if any non-conflicted transaction, spends it: */ bool CWallet::IsSpent(const TxId &txid, uint32_t n) const { const COutPoint outpoint(txid, n); std::pair range = mapTxSpends.equal_range(outpoint); for (TxSpends::const_iterator it = range.first; it != range.second; ++it) { const TxId &wtxid = it->second; std::map::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { int depth = mit->second.GetDepthInMainChain(); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) { // Spent return true; } } } return false; } void CWallet::AddToSpends(const COutPoint &outpoint, const TxId &wtxid) { mapTxSpends.insert(std::make_pair(outpoint, wtxid)); std::pair range; range = mapTxSpends.equal_range(outpoint); SyncMetaData(range); } void CWallet::AddToSpends(const TxId &wtxid) { assert(mapWallet.count(wtxid)); CWalletTx &thisTx = mapWallet[wtxid]; // Coinbases don't spend anything! if (thisTx.IsCoinBase()) { return; } for (const CTxIn &txin : thisTx.tx->vin) { AddToSpends(txin.prevout, wtxid); } } bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase) { if (IsCrypted()) { return false; } CKeyingMaterial _vMasterKey; _vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); CMasterKey kMasterKey; kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); CCrypter crypter; int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime)); nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2; if (kMasterKey.nDeriveIterations < 25000) { kMasterKey.nDeriveIterations = 25000; } LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) { return false; } if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey)) { return false; } { LOCK(cs_wallet); mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; assert(!pwalletdbEncryption); pwalletdbEncryption = new CWalletDB(*dbw); if (!pwalletdbEncryption->TxnBegin()) { delete pwalletdbEncryption; pwalletdbEncryption = nullptr; return false; } pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey); if (!EncryptKeys(_vMasterKey)) { pwalletdbEncryption->TxnAbort(); delete pwalletdbEncryption; // We now probably have half of our keys encrypted in memory, and // half not... die and let the user reload the unencrypted wallet. assert(false); } // Encryption was introduced in version 0.4.0 SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); if (!pwalletdbEncryption->TxnCommit()) { delete pwalletdbEncryption; // We now have keys encrypted in memory, but not on disk... die to // avoid confusion and let the user reload the unencrypted wallet. assert(false); } delete pwalletdbEncryption; pwalletdbEncryption = nullptr; Lock(); Unlock(strWalletPassphrase); // If we are using HD, replace the HD master key (seed) with a new one. if (IsHDEnabled()) { CKey key; CPubKey masterPubKey = GenerateNewHDMasterKey(); // preserve the old chains version to not break backward // compatibility CHDChain oldChain = GetHDChain(); if (!SetHDMasterKey(masterPubKey, &oldChain)) { return false; } } NewKeyPool(); Lock(); // Need to completely rewrite the wallet file; if we don't, bdb might // keep bits of the unencrypted private key in slack space in the // database file. dbw->Rewrite(); } NotifyStatusChanged(this); return true; } DBErrors CWallet::ReorderTransactions() { LOCK(cs_wallet); CWalletDB walletdb(*dbw); // Old wallets didn't have any defined order for transactions. Probably a // bad idea to change the output of this. // First: get all CWalletTx and CAccountingEntry into a sorted-by-time // multimap. TxItems txByTime; for (std::map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { CWalletTx *wtx = &((*it).second); txByTime.insert( std::make_pair(wtx->nTimeReceived, TxPair(wtx, nullptr))); } std::list acentries; walletdb.ListAccountCreditDebit("", acentries); for (CAccountingEntry &entry : acentries) { txByTime.insert(std::make_pair(entry.nTime, TxPair(nullptr, &entry))); } nOrderPosNext = 0; std::vector nOrderPosOffsets; for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) { CWalletTx *const pwtx = (*it).second.first; CAccountingEntry *const pacentry = (*it).second.second; int64_t &nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos; if (nOrderPos == -1) { nOrderPos = nOrderPosNext++; nOrderPosOffsets.push_back(nOrderPos); if (pwtx) { if (!walletdb.WriteTx(*pwtx)) { return DB_LOAD_FAIL; } } else if (!walletdb.WriteAccountingEntry(pacentry->nEntryNo, *pacentry)) { return DB_LOAD_FAIL; } } else { int64_t nOrderPosOff = 0; for (const int64_t &nOffsetStart : nOrderPosOffsets) { if (nOrderPos >= nOffsetStart) { ++nOrderPosOff; } } nOrderPos += nOrderPosOff; nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1); if (!nOrderPosOff) { continue; } // Since we're changing the order, write it back. if (pwtx) { if (!walletdb.WriteTx(*pwtx)) { return DB_LOAD_FAIL; } } else if (!walletdb.WriteAccountingEntry(pacentry->nEntryNo, *pacentry)) { return DB_LOAD_FAIL; } } } walletdb.WriteOrderPosNext(nOrderPosNext); return DB_LOAD_OK; } int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb) { // nOrderPosNext AssertLockHeld(cs_wallet); int64_t nRet = nOrderPosNext++; if (pwalletdb) { pwalletdb->WriteOrderPosNext(nOrderPosNext); } else { CWalletDB(*dbw).WriteOrderPosNext(nOrderPosNext); } return nRet; } bool CWallet::AccountMove(std::string strFrom, std::string strTo, const Amount nAmount, std::string strComment) { CWalletDB walletdb(*dbw); if (!walletdb.TxnBegin()) { return false; } int64_t nNow = GetAdjustedTime(); // Debit CAccountingEntry debit; debit.nOrderPos = IncOrderPosNext(&walletdb); debit.strAccount = strFrom; debit.nCreditDebit = -nAmount; debit.nTime = nNow; debit.strOtherAccount = strTo; debit.strComment = strComment; AddAccountingEntry(debit, &walletdb); // Credit CAccountingEntry credit; credit.nOrderPos = IncOrderPosNext(&walletdb); credit.strAccount = strTo; credit.nCreditDebit = nAmount; credit.nTime = nNow; credit.strOtherAccount = strFrom; credit.strComment = strComment; AddAccountingEntry(credit, &walletdb); return walletdb.TxnCommit(); } bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew) { CWalletDB walletdb(*dbw); CAccount account; walletdb.ReadAccount(strAccount, account); if (!bForceNew) { if (!account.vchPubKey.IsValid()) { bForceNew = true; } else { // Check if the current key has been used. CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID()); for (std::map::iterator it = mapWallet.begin(); it != mapWallet.end() && account.vchPubKey.IsValid(); ++it) { for (const CTxOut &txout : (*it).second.tx->vout) { if (txout.scriptPubKey == scriptPubKey) { bForceNew = true; break; } } } } } // Generate a new key if (bForceNew) { if (!GetKeyFromPool(account.vchPubKey, false)) { return false; } SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive"); walletdb.WriteAccount(strAccount, account); } pubKey = account.vchPubKey; return true; } void CWallet::MarkDirty() { LOCK(cs_wallet); for (std::pair &item : mapWallet) { item.second.MarkDirty(); } } bool CWallet::AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose) { LOCK(cs_wallet); CWalletDB walletdb(*dbw, "r+", fFlushOnClose); const TxId &txid = wtxIn.GetId(); // Inserts only if not already there, returns tx inserted or tx found. std::pair::iterator, bool> ret = mapWallet.insert(std::make_pair(txid, wtxIn)); CWalletTx &wtx = (*ret.first).second; wtx.BindWallet(this); bool fInsertedNew = ret.second; if (fInsertedNew) { wtx.nTimeReceived = GetAdjustedTime(); wtx.nOrderPos = IncOrderPosNext(&walletdb); wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); wtx.nTimeSmart = ComputeTimeSmart(wtx); AddToSpends(txid); } bool fUpdated = false; if (!fInsertedNew) { // Merge if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) { wtx.hashBlock = wtxIn.hashBlock; fUpdated = true; } // If no longer abandoned, update if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) { wtx.hashBlock = wtxIn.hashBlock; fUpdated = true; } if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex)) { wtx.nIndex = wtxIn.nIndex; fUpdated = true; } if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) { wtx.fFromMe = wtxIn.fFromMe; fUpdated = true; } } //// debug print LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetId().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); // Write to disk if ((fInsertedNew || fUpdated) && !walletdb.WriteTx(wtx)) { return false; } // Break debit/credit balance caches: wtx.MarkDirty(); // Notify UI of new or updated transaction. NotifyTransactionChanged(this, txid, fInsertedNew ? CT_NEW : CT_UPDATED); // Notify an external script when a wallet transaction comes in or is // updated. std::string strCmd = gArgs.GetArg("-walletnotify", ""); if (!strCmd.empty()) { boost::replace_all(strCmd, "%s", wtxIn.GetId().GetHex()); std::thread t(runCommand, strCmd); // Thread runs free. t.detach(); } return true; } bool CWallet::LoadToWallet(const CWalletTx &wtxIn) { const TxId &txid = wtxIn.GetId(); mapWallet[txid] = wtxIn; CWalletTx &wtx = mapWallet[txid]; wtx.BindWallet(this); wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr))); AddToSpends(txid); for (const CTxIn &txin : wtx.tx->vin) { if (mapWallet.count(txin.prevout.GetTxId())) { CWalletTx &prevtx = mapWallet[txin.prevout.GetTxId()]; if (prevtx.nIndex == -1 && !prevtx.hashUnset()) { MarkConflicted(prevtx.hashBlock, wtx.GetId()); } } } return true; } /** * Add a transaction to the wallet, or update it. pIndex and posInBlock should * be set when the transaction was known to be included in a block. When pIndex * == nullptr, then wallet state is not updated in AddToWallet, but * notifications happen and cached balances are marked dirty. * * If fUpdate is true, existing transactions will be updated. * TODO: One exception to this is that the abandoned state is cleared under the * assumption that any further notification of a transaction that was considered * abandoned is an indication that it is not safe to be considered abandoned. * Abandoned state should probably be more carefuly tracked via different * posInBlock signals or by checking mempool presence when necessary. */ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx, const CBlockIndex *pIndex, int posInBlock, bool fUpdate) { const CTransaction &tx = *ptx; AssertLockHeld(cs_wallet); if (pIndex != nullptr) { for (const CTxIn &txin : tx.vin) { std::pair range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetId()) { LogPrintf("Transaction %s (in block %s) conflicts with " "wallet transaction %s (both spend %s:%i)\n", tx.GetId().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.GetTxId().ToString(), range.first->first.GetN()); MarkConflicted(pIndex->GetBlockHash(), range.first->second); } range.first++; } } } bool fExisted = mapWallet.count(tx.GetId()) != 0; if (fExisted && !fUpdate) { return false; } if (fExisted || IsMine(tx) || IsFromMe(tx)) { /** * Check if any keys in the wallet keypool that were supposed to be * unused have appeared in a new transaction. If so, remove those keys * from the keypool. This can happen when restoring an old wallet backup * that does not contain the mostly recently created transactions from * newer versions of the wallet. */ // loop though all outputs for (const CTxOut &txout : tx.vout) { // extract addresses and check if they match with an unused keypool // key std::vector vAffected; CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); for (const CKeyID &keyid : vAffected) { std::map::const_iterator mi = m_pool_key_to_index.find(keyid); if (mi != m_pool_key_to_index.end()) { LogPrintf("%s: Detected a used keypool key, mark all " "keypool key up to this key as used\n", __func__); MarkReserveKeysAsUsed(mi->second); if (!TopUpKeyPool()) { LogPrintf( "%s: Topping up keypool failed (locked wallet)\n", __func__); } } } } CWalletTx wtx(this, ptx); // Get merkle branch if transaction was found in a block if (pIndex != nullptr) { wtx.SetMerkleBranch(pIndex, posInBlock); } return AddToWallet(wtx, false); } return false; } bool CWallet::AbandonTransaction(const TxId &txid) { LOCK2(cs_main, cs_wallet); CWalletDB walletdb(*dbw, "r+"); std::set todo; std::set done; // Can't mark abandoned if confirmed or in mempool. assert(mapWallet.count(txid)); CWalletTx &origtx = mapWallet[txid]; if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) { return false; } todo.insert(txid); while (!todo.empty()) { const TxId now = *todo.begin(); todo.erase(now); done.insert(now); assert(mapWallet.count(now)); CWalletTx &wtx = mapWallet[now]; int currentconfirm = wtx.GetDepthInMainChain(); // If the orig tx was not in block, none of its spends can be. assert(currentconfirm <= 0); // If (currentconfirm < 0) {Tx and spends are already conflicted, no // need to abandon} if (currentconfirm == 0 && !wtx.isAbandoned()) { // If the orig tx was not in block/mempool, none of its spends can // be in mempool. assert(!wtx.InMempool()); wtx.nIndex = -1; wtx.setAbandoned(); wtx.MarkDirty(); walletdb.WriteTx(wtx); NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED); // Iterate over all its outputs, and mark transactions in the wallet // that spend them abandoned too. TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) { if (!done.count(iter->second)) { todo.insert(iter->second); } iter++; } // If a transaction changes 'conflicted' state, that changes the // balance available of the outputs it spends. So force those to be // recomputed. for (const CTxIn &txin : wtx.tx->vin) { if (mapWallet.count(txin.prevout.GetTxId())) { mapWallet[txin.prevout.GetTxId()].MarkDirty(); } } } } return true; } void CWallet::MarkConflicted(const uint256 &hashBlock, const TxId &txid) { LOCK2(cs_main, cs_wallet); int conflictconfirms = 0; if (mapBlockIndex.count(hashBlock)) { CBlockIndex *pindex = mapBlockIndex[hashBlock]; if (chainActive.Contains(pindex)) { conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1); } } // If number of conflict confirms cannot be determined, this means that the // block is still unknown or not yet part of the main chain, for example // when loading the wallet during a reindex. Do nothing in that case. if (conflictconfirms >= 0) { return; } // Do not flush the wallet here for performance reasons. CWalletDB walletdb(*dbw, "r+", false); std::set todo; std::set done; todo.insert(txid); while (!todo.empty()) { const TxId now = *todo.begin(); todo.erase(now); done.insert(now); assert(mapWallet.count(now)); CWalletTx &wtx = mapWallet[now]; int currentconfirm = wtx.GetDepthInMainChain(); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. wtx.nIndex = -1; wtx.hashBlock = hashBlock; wtx.MarkDirty(); walletdb.WriteTx(wtx); // Iterate over all its outputs, and mark transactions in the wallet // that spend them conflicted too. TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) { if (!done.count(iter->second)) { todo.insert(iter->second); } iter++; } // If a transaction changes 'conflicted' state, that changes the // balance available of the outputs it spends. So force those to be // recomputed. for (const CTxIn &txin : wtx.tx->vin) { if (mapWallet.count(txin.prevout.GetTxId())) { mapWallet[txin.prevout.GetTxId()].MarkDirty(); } } } } } void CWallet::SyncTransaction(const CTransactionRef &ptx, const CBlockIndex *pindex, int posInBlock) { const CTransaction &tx = *ptx; if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, true)) { // Not one of ours return; } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be recomputed, // also: for (const CTxIn &txin : tx.vin) { if (mapWallet.count(txin.prevout.GetTxId())) { mapWallet[txin.prevout.GetTxId()].MarkDirty(); } } } void CWallet::TransactionAddedToMempool(const CTransactionRef &ptx) { LOCK2(cs_main, cs_wallet); SyncTransaction(ptx); } void CWallet::BlockConnected( const std::shared_ptr &pblock, const CBlockIndex *pindex, const std::vector &vtxConflicted) { LOCK2(cs_main, cs_wallet); // TODO: Tempoarily ensure that mempool removals are notified before // connected transactions. This shouldn't matter, but the abandoned state of // transactions in our wallet is currently cleared when we receive another // notification and there is a race condition where notification of a // connected conflict might cause an outside process to abandon a // transaction and then have it inadvertantly cleared by the notification // that the conflicted transaction was evicted. for (const CTransactionRef &ptx : vtxConflicted) { SyncTransaction(ptx); } for (size_t i = 0; i < pblock->vtx.size(); i++) { SyncTransaction(pblock->vtx[i], pindex, i); } } void CWallet::BlockDisconnected(const std::shared_ptr &pblock) { LOCK2(cs_main, cs_wallet); for (const CTransactionRef &ptx : pblock->vtx) { SyncTransaction(ptx); } } isminetype CWallet::IsMine(const CTxIn &txin) const { LOCK(cs_wallet); std::map::const_iterator mi = mapWallet.find(txin.prevout.GetTxId()); if (mi != mapWallet.end()) { const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() < prev.tx->vout.size()) { return IsMine(prev.tx->vout[txin.prevout.GetN()]); } } return ISMINE_NO; } // Note that this function doesn't distinguish between a 0-valued input, and a // not-"is mine" (according to the filter) input. Amount CWallet::GetDebit(const CTxIn &txin, const isminefilter &filter) const { LOCK(cs_wallet); std::map::const_iterator mi = mapWallet.find(txin.prevout.GetTxId()); if (mi != mapWallet.end()) { const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() < prev.tx->vout.size()) { if (IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter) { return prev.tx->vout[txin.prevout.GetN()].nValue; } } } return Amount::zero(); } isminetype CWallet::IsMine(const CTxOut &txout) const { return ::IsMine(*this, txout.scriptPubKey); } Amount CWallet::GetCredit(const CTxOut &txout, const isminefilter &filter) const { if (!MoneyRange(txout.nValue)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } return (IsMine(txout) & filter) ? txout.nValue : Amount::zero(); } bool CWallet::IsChange(const CTxOut &txout) const { // TODO: fix handling of 'change' outputs. The assumption is that any // payment to a script that is ours, but is not in the address book is // change. That assumption is likely to break when we implement // multisignature wallets that return change back into a // multi-signature-protected address; a better way of identifying which // outputs are 'the send' and which are 'the change' will need to be // implemented (maybe extend CWalletTx to remember which output, if any, was // change). if (::IsMine(*this, txout.scriptPubKey)) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address)) { return true; } LOCK(cs_wallet); if (!mapAddressBook.count(address)) { return true; } } return false; } Amount CWallet::GetChange(const CTxOut &txout) const { if (!MoneyRange(txout.nValue)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } return (IsChange(txout) ? txout.nValue : Amount::zero()); } bool CWallet::IsMine(const CTransaction &tx) const { for (const CTxOut &txout : tx.vout) { if (IsMine(txout)) { return true; } } return false; } bool CWallet::IsFromMe(const CTransaction &tx) const { return GetDebit(tx, ISMINE_ALL) > Amount::zero(); } Amount CWallet::GetDebit(const CTransaction &tx, const isminefilter &filter) const { Amount nDebit = Amount::zero(); for (const CTxIn &txin : tx.vin) { nDebit += GetDebit(txin, filter); if (!MoneyRange(nDebit)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nDebit; } bool CWallet::IsAllFromMe(const CTransaction &tx, const isminefilter &filter) const { LOCK(cs_wallet); for (const CTxIn &txin : tx.vin) { auto mi = mapWallet.find(txin.prevout.GetTxId()); if (mi == mapWallet.end()) { // Any unknown inputs can't be from us. return false; } const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() >= prev.tx->vout.size()) { // Invalid input! return false; } if (!(IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter)) { return false; } } return true; } Amount CWallet::GetCredit(const CTransaction &tx, const isminefilter &filter) const { Amount nCredit = Amount::zero(); for (const CTxOut &txout : tx.vout) { nCredit += GetCredit(txout, filter); if (!MoneyRange(nCredit)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nCredit; } Amount CWallet::GetChange(const CTransaction &tx) const { Amount nChange = Amount::zero(); for (const CTxOut &txout : tx.vout) { nChange += GetChange(txout); if (!MoneyRange(nChange)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nChange; } CPubKey CWallet::GenerateNewHDMasterKey() { CKey key; key.MakeNewKey(true); int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); // Calculate the pubkey. CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); // Set the hd keypath to "m" -> Master, refers the masterkeyid to itself. metadata.hdKeypath = "m"; metadata.hdMasterKeyID = pubkey.GetID(); LOCK(cs_wallet); // mem store the metadata mapKeyMetadata[pubkey.GetID()] = metadata; // Write the key&metadata to the database. if (!AddKeyPubKey(key, pubkey)) { throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); } return pubkey; } bool CWallet::SetHDMasterKey(const CPubKey &pubkey, CHDChain *possibleOldChain) { LOCK(cs_wallet); // Store the keyid (hash160) together with the child index counter in the // database as a hdchain object. CHDChain newHdChain; if (possibleOldChain) { // preserve the old chains version newHdChain.nVersion = possibleOldChain->nVersion; } newHdChain.masterKeyID = pubkey.GetID(); SetHDChain(newHdChain, false); return true; } bool CWallet::SetHDChain(const CHDChain &chain, bool memonly) { LOCK(cs_wallet); if (!memonly && !CWalletDB(*dbw).WriteHDChain(chain)) { throw std::runtime_error(std::string(__func__) + ": writing chain failed"); } hdChain = chain; return true; } bool CWallet::IsHDEnabled() { return !hdChain.masterKeyID.IsNull(); } int64_t CWalletTx::GetTxTime() const { int64_t n = nTimeSmart; return n ? n : nTimeReceived; } int CWalletTx::GetRequestCount() const { LOCK(pwallet->cs_wallet); // Returns -1 if it wasn't being tracked. int nRequests = -1; if (IsCoinBase()) { // Generated block. if (!hashUnset()) { std::map::const_iterator mi = pwallet->mapRequestCount.find(hashBlock); if (mi != pwallet->mapRequestCount.end()) { nRequests = (*mi).second; } } } else { // Did anyone request this transaction? std::map::const_iterator mi = pwallet->mapRequestCount.find(GetId()); if (mi != pwallet->mapRequestCount.end()) { nRequests = (*mi).second; // How about the block it's in? if (nRequests == 0 && !hashUnset()) { std::map::const_iterator _mi = pwallet->mapRequestCount.find(hashBlock); if (_mi != pwallet->mapRequestCount.end()) { nRequests = (*_mi).second; } else { // If it's in someone else's block it must have got out. nRequests = 1; } } } } return nRequests; } void CWalletTx::GetAmounts(std::list &listReceived, std::list &listSent, Amount &nFee, std::string &strSentAccount, const isminefilter &filter) const { nFee = Amount::zero(); listReceived.clear(); listSent.clear(); strSentAccount = strFromAccount; // Compute fee: Amount nDebit = GetDebit(filter); // debit>0 means we signed/sent this transaction. if (nDebit > Amount::zero()) { Amount nValueOut = tx->GetValueOut(); nFee = (nDebit - nValueOut); } // Sent/received. for (unsigned int i = 0; i < tx->vout.size(); ++i) { const CTxOut &txout = tx->vout[i]; isminetype fIsMine = pwallet->IsMine(txout); // Only need to handle txouts if AT LEAST one of these is true: // 1) they debit from us (sent) // 2) the output is to us (received) if (nDebit > Amount::zero()) { // Don't report 'change' txouts if (pwallet->IsChange(txout)) { continue; } } else if (!(fIsMine & filter)) { continue; } // In either case, we need to get the destination address. CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) { LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, " "txid %s\n", this->GetId().ToString()); address = CNoDestination(); } COutputEntry output = {address, txout.nValue, (int)i}; // If we are debited by the transaction, add the output as a "sent" // entry. if (nDebit > Amount::zero()) { listSent.push_back(output); } // If we are receiving the output, add it as a "received" entry. if (fIsMine & filter) { listReceived.push_back(output); } } } /** * Scan active chain for relevant transactions after importing keys. This should * be called whenever new keys are added to the wallet, with the oldest key * creation time. * * @return Earliest timestamp that could be successfully scanned from. Timestamp * returned will be higher than startTime if relevant blocks could not be read. */ int64_t CWallet::RescanFromTime(int64_t startTime, bool update) { AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); // Find starting block. May be null if nCreateTime is greater than the // highest blockchain timestamp, in which case there is nothing that needs // to be scanned. CBlockIndex *const startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); if (startBlock) { const CBlockIndex *const failedBlock = ScanForWalletTransactions(startBlock, nullptr, update); if (failedBlock) { return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; } } return startTime; } /** * Scan the block chain (starting in pindexStart) for transactions from or to * us. If fUpdate is true, found transactions that already exist in the wallet * will be updated. * * Returns null if scan was successful. Otherwise, if a complete rescan was not * possible (due to pruning or corruption), returns pointer to the most recent * block that could not be scanned. * * If pindexStop is not a nullptr, the scan will stop at the block-index defined * by pindexStop. */ CBlockIndex *CWallet::ScanForWalletTransactions(CBlockIndex *pindexStart, CBlockIndex *pindexStop, bool fUpdate) { int64_t nNow = GetTime(); if (pindexStop) { assert(pindexStop->nHeight >= pindexStart->nHeight); } CBlockIndex *pindex = pindexStart; CBlockIndex *ret = nullptr; LOCK2(cs_main, cs_wallet); fAbortRescan = false; fScanningWallet = true; // Show rescan progress in GUI as dialog or on splashscreen, if -rescan on // startup. ShowProgress(_("Rescanning..."), 0); double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex); double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip()); while (pindex && !fAbortRescan) { if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) { ShowProgress( _("Rescanning..."), std::max(1, std::min(99, (GuessVerificationProgress( chainParams.TxData(), pindex) - dProgressStart) / (dProgressTip - dProgressStart) * 100))); } CBlock block; if (ReadBlockFromDisk(block, pindex, GetConfig())) { for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate); } } else { ret = pindex; } if (pindex == pindexStop) { break; } pindex = chainActive.Next(pindex); if (GetTime() >= nNow + 60) { nNow = GetTime(); LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex)); } } if (pindex && fAbortRescan) { LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex)); } // Hide progress dialog in GUI. ShowProgress(_("Rescanning..."), 100); fScanningWallet = false; return ret; } void CWallet::ReacceptWalletTransactions() { // If transactions aren't being broadcasted, don't let them into local // mempool either. if (!fBroadcastTransactions) { return; } LOCK2(cs_main, cs_wallet); std::map mapSorted; // Sort pending wallet transactions based on their initial wallet insertion // order. for (std::pair &item : mapWallet) { const TxId &wtxid = item.first; CWalletTx &wtx = item.second; assert(wtx.GetId() == wtxid); int nDepth = wtx.GetDepthInMainChain(); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); } } // Try to add wallet transactions to memory pool. for (std::pair &item : mapSorted) { CWalletTx &wtx = *(item.second); LOCK(mempool.cs); CValidationState state; wtx.AcceptToMemoryPool(maxTxFee, state); } } bool CWalletTx::RelayWalletTransaction(CConnman *connman) { assert(pwallet->GetBroadcastTransactions()); if (IsCoinBase() || isAbandoned() || GetDepthInMainChain() != 0) { return false; } CValidationState state; // GetDepthInMainChain already catches known conflicts. if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) { LogPrintf("Relaying wtx %s\n", GetId().ToString()); if (connman) { CInv inv(MSG_TX, GetId()); connman->ForEachNode( [&inv](CNode *pnode) { pnode->PushInventory(inv); }); return true; } } return false; } std::set CWalletTx::GetConflicts() const { std::set result; if (pwallet != nullptr) { const TxId &txid = GetId(); result = pwallet->GetConflicts(txid); result.erase(txid); } return result; } Amount CWalletTx::GetDebit(const isminefilter &filter) const { if (tx->vin.empty()) { return Amount::zero(); } Amount debit = Amount::zero(); if (filter & ISMINE_SPENDABLE) { if (fDebitCached) { debit += nDebitCached; } else { nDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE); fDebitCached = true; debit += nDebitCached; } } if (filter & ISMINE_WATCH_ONLY) { if (fWatchDebitCached) { debit += nWatchDebitCached; } else { nWatchDebitCached = pwallet->GetDebit(*this, ISMINE_WATCH_ONLY); fWatchDebitCached = true; debit += Amount(nWatchDebitCached); } } return debit; } Amount CWalletTx::GetCredit(const isminefilter &filter) const { // Must wait until coinbase is safely deep enough in the chain before // valuing it. if (IsImmatureCoinBase()) { return Amount::zero(); } Amount credit = Amount::zero(); if (filter & ISMINE_SPENDABLE) { // GetBalance can assume transactions in mapWallet won't change. if (fCreditCached) { credit += nCreditCached; } else { nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE); fCreditCached = true; credit += nCreditCached; } } if (filter & ISMINE_WATCH_ONLY) { if (fWatchCreditCached) { credit += nWatchCreditCached; } else { nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY); fWatchCreditCached = true; credit += nWatchCreditCached; } } return credit; } Amount CWalletTx::GetImmatureCredit(bool fUseCache) const { if (IsImmatureCoinBase() && IsInMainChain()) { if (fUseCache && fImmatureCreditCached) { return nImmatureCreditCached; } nImmatureCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE); fImmatureCreditCached = true; return nImmatureCreditCached; } return Amount::zero(); } Amount CWalletTx::GetAvailableCredit(bool fUseCache) const { if (pwallet == 0) { return Amount::zero(); } // Must wait until coinbase is safely deep enough in the chain before // valuing it. if (IsImmatureCoinBase()) { return Amount::zero(); } if (fUseCache && fAvailableCreditCached) { return nAvailableCreditCached; } Amount nCredit = Amount::zero(); for (uint32_t i = 0; i < tx->vout.size(); i++) { if (!pwallet->IsSpent(GetId(), i)) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); if (!MoneyRange(nCredit)) { throw std::runtime_error( "CWalletTx::GetAvailableCredit() : value out of range"); } } } nAvailableCreditCached = nCredit; fAvailableCreditCached = true; return nCredit; } Amount CWalletTx::GetImmatureWatchOnlyCredit(const bool &fUseCache) const { if (IsImmatureCoinBase() && IsInMainChain()) { if (fUseCache && fImmatureWatchCreditCached) { return nImmatureWatchCreditCached; } nImmatureWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY); fImmatureWatchCreditCached = true; return nImmatureWatchCreditCached; } return Amount::zero(); } Amount CWalletTx::GetAvailableWatchOnlyCredit(const bool &fUseCache) const { if (pwallet == 0) { return Amount::zero(); } // Must wait until coinbase is safely deep enough in the chain before // valuing it. if (IsCoinBase() && GetBlocksToMaturity() > 0) { return Amount::zero(); } if (fUseCache && fAvailableWatchCreditCached) { return nAvailableWatchCreditCached; } Amount nCredit = Amount::zero(); for (uint32_t i = 0; i < tx->vout.size(); i++) { if (!pwallet->IsSpent(GetId(), i)) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY); if (!MoneyRange(nCredit)) { throw std::runtime_error( "CWalletTx::GetAvailableCredit() : value out of range"); } } } nAvailableWatchCreditCached = nCredit; fAvailableWatchCreditCached = true; return nCredit; } Amount CWalletTx::GetChange() const { if (fChangeCached) { return nChangeCached; } nChangeCached = pwallet->GetChange(*this); fChangeCached = true; return nChangeCached; } bool CWalletTx::InMempool() const { LOCK(mempool.cs); if (mempool.exists(GetId())) { return true; } return false; } bool CWalletTx::IsTrusted() const { // Quick answer in most cases if (!CheckFinalTx(*this)) { return false; } int nDepth = GetDepthInMainChain(); if (nDepth >= 1) { return true; } if (nDepth < 0) { return false; } // using wtx's cached debit if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) { return false; } // Don't trust unconfirmed transactions from us unless they are in the // mempool. if (!InMempool()) { return false; } // Trusted if all inputs are from us and are in the mempool: for (const CTxIn &txin : tx->vin) { // Transactions not sent by us: not trusted const CWalletTx *parent = pwallet->GetWalletTx(txin.prevout.GetTxId()); if (parent == nullptr) { return false; } const CTxOut &parentOut = parent->tx->vout[txin.prevout.GetN()]; if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) { return false; } } return true; } bool CWalletTx::IsEquivalentTo(const CWalletTx &_tx) const { CMutableTransaction tx1 = *this->tx; CMutableTransaction tx2 = *_tx.tx; for (CTxIn &in : tx1.vin) { in.scriptSig = CScript(); } for (CTxIn &in : tx2.vin) { in.scriptSig = CScript(); } return CTransaction(tx1) == CTransaction(tx2); } std::vector CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman *connman) { std::vector result; LOCK(cs_wallet); // Sort them in chronological order std::multimap mapSorted; for (std::pair &item : mapWallet) { CWalletTx &wtx = item.second; // Don't rebroadcast if newer than nTime: if (wtx.nTimeReceived > nTime) { continue; } mapSorted.insert(std::make_pair(wtx.nTimeReceived, &wtx)); } for (std::pair &item : mapSorted) { CWalletTx &wtx = *item.second; if (wtx.RelayWalletTransaction(connman)) { result.push_back(wtx.GetId()); } } return result; } void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman *connman) { // Do this infrequently and randomly to avoid giving away that these are our // transactions. if (GetTime() < nNextResend || !fBroadcastTransactions) { return; } bool fFirst = (nNextResend == 0); nNextResend = GetTime() + GetRand(30 * 60); if (fFirst) { return; } // Only do it if there's been a new block since last time if (nBestBlockTime < nLastResend) { return; } nLastResend = GetTime(); // Rebroadcast unconfirmed txes older than 5 minutes before the last block // was found: std::vector relayed = ResendWalletTransactionsBefore(nBestBlockTime - 5 * 60, connman); if (!relayed.empty()) { LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); } } /** @} */ // end of mapWallet /** * @defgroup Actions * * @{ */ Amount CWallet::GetBalance() const { LOCK2(cs_main, cs_wallet); Amount nTotal = Amount::zero(); for (const std::pair &p : mapWallet) { const CWalletTx *pcoin = &p.second; if (pcoin->IsTrusted()) { nTotal += pcoin->GetAvailableCredit(); } } return nTotal; } Amount CWallet::GetUnconfirmedBalance() const { LOCK2(cs_main, cs_wallet); Amount nTotal = Amount::zero(); for (const std::pair &p : mapWallet) { const CWalletTx *pcoin = &p.second; if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) { nTotal += pcoin->GetAvailableCredit(); } } return nTotal; } Amount CWallet::GetImmatureBalance() const { LOCK2(cs_main, cs_wallet); Amount nTotal = Amount::zero(); for (const std::pair &p : mapWallet) { const CWalletTx *pcoin = &p.second; nTotal += pcoin->GetImmatureCredit(); } return nTotal; } Amount CWallet::GetWatchOnlyBalance() const { LOCK2(cs_main, cs_wallet); Amount nTotal = Amount::zero(); for (const std::pair &p : mapWallet) { const CWalletTx *pcoin = &p.second; if (pcoin->IsTrusted()) { nTotal += pcoin->GetAvailableWatchOnlyCredit(); } } return nTotal; } Amount CWallet::GetUnconfirmedWatchOnlyBalance() const { LOCK2(cs_main, cs_wallet); Amount nTotal = Amount::zero(); for (const std::pair &p : mapWallet) { const CWalletTx *pcoin = &p.second; if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) { nTotal += pcoin->GetAvailableWatchOnlyCredit(); } } return nTotal; } Amount CWallet::GetImmatureWatchOnlyBalance() const { LOCK2(cs_main, cs_wallet); Amount nTotal = Amount::zero(); for (const std::pair &p : mapWallet) { const CWalletTx *pcoin = &p.second; nTotal += pcoin->GetImmatureWatchOnlyCredit(); } return nTotal; } // Calculate total balance in a different way from GetBalance. The biggest // difference is that GetBalance sums up all unspent TxOuts paying to the // wallet, while this sums up both spent and unspent TxOuts paying to the // wallet, and then subtracts the values of TxIns spending from the wallet. This // also has fewer restrictions on which unconfirmed transactions are considered // trusted. Amount CWallet::GetLegacyBalance(const isminefilter &filter, int minDepth, const std::string *account) const { LOCK2(cs_main, cs_wallet); Amount balance = Amount::zero(); for (const auto &entry : mapWallet) { const CWalletTx &wtx = entry.second; const int depth = wtx.GetDepthInMainChain(); if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase()) { continue; } // Loop through tx outputs and add incoming payments. For outgoing txs, // treat change outputs specially, as part of the amount debited. Amount debit = wtx.GetDebit(filter); const bool outgoing = debit > Amount::zero(); for (const CTxOut &out : wtx.tx->vout) { if (outgoing && IsChange(out)) { debit -= out.nValue; } else if (IsMine(out) & filter && depth >= minDepth && (!account || *account == GetAccountName(out.scriptPubKey))) { balance += out.nValue; } } // For outgoing txs, subtract amount debited. if (outgoing && (!account || *account == wtx.strFromAccount)) { balance -= debit; } } if (account) { balance += CWalletDB(*dbw).GetAccountCreditDebit(*account); } return balance; } void CWallet::AvailableCoins(std::vector &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const Amount nMinimumAmount, const Amount nMaximumAmount, const Amount nMinimumSumAmount, const uint64_t &nMaximumCount, const int &nMinDepth, const int &nMaxDepth) const { vCoins.clear(); Amount nTotal = Amount::zero(); LOCK2(cs_main, cs_wallet); for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const TxId &wtxid = it->first; const CWalletTx *pcoin = &(*it).second; if (!CheckFinalTx(*pcoin)) { continue; } if (pcoin->IsImmatureCoinBase()) { continue; } int nDepth = pcoin->GetDepthInMainChain(); if (nDepth < 0) { continue; } // We should not consider coins which aren't at least in our mempool. // It's possible for these to be conflicted via ancestors which we may // never be able to detect. if (nDepth == 0 && !pcoin->InMempool()) { continue; } bool safeTx = pcoin->IsTrusted(); // Bitcoin-ABC: Removed check that prevents consideration of coins from // transactions that are replacing other transactions. This check based // on pcoin->mapValue.count("replaces_txid") which was not being set // anywhere. // Similarly, we should not consider coins from transactions that have // been replaced. In the example above, we would want to prevent // creation of a transaction A' spending an output of A, because if // transaction B were initially confirmed, conflicting with A and A', we // wouldn't want to the user to create a transaction D intending to // replace A', but potentially resulting in a scenario where A, A', and // D could all be accepted (instead of just B and D, or just A and A' // like the user would want). // Bitcoin-ABC: retained this check as 'replaced_by_txid' is still set // in the wallet code. if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) { safeTx = false; } if (fOnlySafe && !safeTx) { continue; } if (nDepth < nMinDepth || nDepth > nMaxDepth) { continue; } for (uint32_t i = 0; i < pcoin->tx->vout.size(); i++) { if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount) { continue; } if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint((*it).first, i))) { continue; } if (IsLockedCoin((*it).first, i)) { continue; } if (IsSpent(wtxid, i)) { continue; } isminetype mine = IsMine(pcoin->tx->vout[i]); if (mine == ISMINE_NO) { continue; } bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO); bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO; vCoins.push_back( COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx)); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { nTotal += pcoin->tx->vout[i].nValue; if (nTotal >= nMinimumSumAmount) { return; } } // Checks the maximum number of UTXO's. if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { return; } } } } static void ApproximateBestSubset( std::vector>> vValue, const Amount nTotalLower, const Amount nTargetValue, std::vector &vfBest, Amount &nBest, int iterations = 1000) { std::vector vfIncluded; vfBest.assign(vValue.size(), true); nBest = nTotalLower; FastRandomContext insecure_rand; for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) { vfIncluded.assign(vValue.size(), false); Amount nTotal = Amount::zero(); bool fReachedTarget = false; for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) { for (size_t i = 0; i < vValue.size(); i++) { // The solver here uses a randomized algorithm, the randomness // serves no real security purpose but is just needed to prevent // degenerate behavior and it is important that the rng is fast. // We do not use a constant random sequence, because there may // be some privacy improvement by making the selection random. if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i]) { nTotal += vValue[i].first; vfIncluded[i] = true; if (nTotal >= nTargetValue) { fReachedTarget = true; if (nTotal < nBest) { nBest = nTotal; vfBest = vfIncluded; } nTotal -= vValue[i].first; vfIncluded[i] = false; } } } } } } bool CWallet::SelectCoinsMinConf( const Amount nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, std::vector vCoins, std::set> &setCoinsRet, Amount &nValueRet) const { setCoinsRet.clear(); nValueRet = Amount::zero(); // List of values less than target std::pair> coinLowestLarger; coinLowestLarger.first = MAX_MONEY; coinLowestLarger.second.first = nullptr; std::vector>> vValue; Amount nTotalLower = Amount::zero(); random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); for (const COutput &output : vCoins) { if (!output.fSpendable) { continue; } const CWalletTx *pcoin = output.tx; if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) { continue; } if (!mempool.TransactionWithinChainLimit(pcoin->GetId(), nMaxAncestors)) { continue; } int i = output.i; Amount n = pcoin->tx->vout[i].nValue; std::pair> coin = std::make_pair(n, std::make_pair(pcoin, i)); if (n == nTargetValue) { setCoinsRet.insert(coin.second); nValueRet += coin.first; return true; } if (n < nTargetValue + MIN_CHANGE) { vValue.push_back(coin); nTotalLower += n; } else if (n < coinLowestLarger.first) { coinLowestLarger = coin; } } if (nTotalLower == nTargetValue) { for (unsigned int i = 0; i < vValue.size(); ++i) { setCoinsRet.insert(vValue[i].second); nValueRet += vValue[i].first; } return true; } if (nTotalLower < nTargetValue) { if (coinLowestLarger.second.first == nullptr) { return false; } setCoinsRet.insert(coinLowestLarger.second); nValueRet += coinLowestLarger.first; return true; } // Solve subset sum by stochastic approximation std::sort(vValue.begin(), vValue.end(), CompareValueOnly()); std::reverse(vValue.begin(), vValue.end()); std::vector vfBest; Amount nBest; ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest); if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) { ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest); } // If we have a bigger coin and (either the stochastic approximation didn't // find a good solution, or the next bigger coin is closer), return the // bigger coin. if (coinLowestLarger.second.first && ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger.first <= nBest)) { setCoinsRet.insert(coinLowestLarger.second); nValueRet += coinLowestLarger.first; } else { for (unsigned int i = 0; i < vValue.size(); i++) { if (vfBest[i]) { setCoinsRet.insert(vValue[i].second); nValueRet += vValue[i].first; } } if (LogAcceptCategory(BCLog::SELECTCOINS)) { LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: "); for (size_t i = 0; i < vValue.size(); i++) { if (vfBest[i]) { LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].first)); } } LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest)); } } return true; } bool CWallet::SelectCoins( const std::vector &vAvailableCoins, const Amount nTargetValue, std::set> &setCoinsRet, Amount &nValueRet, const CCoinControl *coinControl) const { std::vector vCoins(vAvailableCoins); // coin control -> return all selected outputs (we want all selected to go // into the transaction for sure). if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) { for (const COutput &out : vCoins) { if (!out.fSpendable) { continue; } nValueRet += out.tx->tx->vout[out.i].nValue; setCoinsRet.insert(std::make_pair(out.tx, out.i)); } return (nValueRet >= nTargetValue); } // Calculate value from preset inputs and store them. std::set> setPresetCoins; Amount nValueFromPresetInputs = Amount::zero(); std::vector vPresetInputs; if (coinControl) { coinControl->ListSelected(vPresetInputs); } for (const COutPoint &outpoint : vPresetInputs) { std::map::const_iterator it = mapWallet.find(outpoint.GetTxId()); if (it == mapWallet.end()) { // TODO: Allow non-wallet inputs return false; } const CWalletTx *pcoin = &it->second; // Clearly invalid input, fail. if (pcoin->tx->vout.size() <= outpoint.GetN()) { return false; } nValueFromPresetInputs += pcoin->tx->vout[outpoint.GetN()].nValue; setPresetCoins.insert(std::make_pair(pcoin, outpoint.GetN())); } // Remove preset inputs from vCoins. for (std::vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();) { if (setPresetCoins.count(std::make_pair(it->tx, it->i))) { it = vCoins.erase(it); } else { ++it; } } size_t nMaxChainLength = std::min( gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT), gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); bool fRejectLongChains = gArgs.GetBoolArg( "-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); bool res = nTargetValue <= nValueFromPresetInputs || SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, 0, vCoins, setCoinsRet, nValueRet) || SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, 0, vCoins, setCoinsRet, nValueRet) || (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, 2, vCoins, setCoinsRet, nValueRet)) || (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::min((size_t)4, nMaxChainLength / 3), vCoins, setCoinsRet, nValueRet)) || (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength / 2, vCoins, setCoinsRet, nValueRet)) || (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength, vCoins, setCoinsRet, nValueRet)) || (bSpendZeroConfChange && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::numeric_limits::max(), vCoins, setCoinsRet, nValueRet)); // Because SelectCoinsMinConf clears the setCoinsRet, we now add the // possible inputs to the coinset. setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end()); // Add preset inputs to the total value selected. nValueRet += nValueFromPresetInputs; return res; } bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate &specificFeeRate, int &nChangePosInOut, std::string &strFailReason, bool includeWatching, bool lockUnspents, const std::set &setSubtractFeeFromOutputs, bool keepReserveKey, const CTxDestination &destChange) { std::vector vecSend; // Turn the txout set into a CRecipient vector. for (size_t idx = 0; idx < tx.vout.size(); idx++) { const CTxOut &txOut = tx.vout[idx]; CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1}; vecSend.push_back(recipient); } CCoinControl coinControl; coinControl.destChange = destChange; coinControl.fAllowOtherInputs = true; coinControl.fAllowWatchOnly = includeWatching; coinControl.fOverrideFeeRate = overrideEstimatedFeeRate; coinControl.nFeeRate = specificFeeRate; for (const CTxIn &txin : tx.vin) { coinControl.Select(txin.prevout); } CReserveKey reservekey(this); CWalletTx wtx; if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, false)) { return false; } if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); } // Copy output sizes from new transaction; they may have had the fee // subtracted from them. for (size_t idx = 0; idx < tx.vout.size(); idx++) { tx.vout[idx].nValue = wtx.tx->vout[idx].nValue; } // Add new txins (keeping original txin scriptSig/order) for (const CTxIn &txin : wtx.tx->vin) { if (!coinControl.IsSelected(txin.prevout)) { tx.vin.push_back(txin); if (lockUnspents) { LOCK2(cs_main, cs_wallet); LockCoin(txin.prevout); } } } // Optionally keep the change output key. if (keepReserveKey) { reservekey.KeepKey(); } return true; } bool CWallet::CreateTransaction(const std::vector &vecSend, CWalletTx &wtxNew, CReserveKey &reservekey, Amount &nFeeRet, int &nChangePosInOut, std::string &strFailReason, const CCoinControl *coinControl, bool sign) { Amount nValue = Amount::zero(); int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; for (const auto &recipient : vecSend) { if (nValue < Amount::zero() || recipient.nAmount < Amount::zero()) { strFailReason = _("Transaction amounts must not be negative"); return false; } nValue += recipient.nAmount; if (recipient.fSubtractFeeFromAmount) { nSubtractFeeFromAmount++; } } if (vecSend.empty()) { strFailReason = _("Transaction must have at least one recipient"); return false; } wtxNew.fTimeReceivedIsTxTime = true; wtxNew.BindWallet(this); CMutableTransaction txNew; // Discourage fee sniping. // // For a large miner the value of the transactions in the best block and the // mempool can exceed the cost of deliberately attempting to mine two blocks // to orphan the current best block. By setting nLockTime such that only the // next block can include the transaction, we discourage this practice as // the height restricted and limited blocksize gives miners considering fee // sniping fewer options for pulling off this attack. // // A simple way to think about this is from the wallet's point of view we // always want the blockchain to move forward. By setting nLockTime this way // we're basically making the statement that we only want this transaction // to appear in the next block; we don't want to potentially encourage // reorgs by allowing transactions to appear at lower heights than the next // block in forks of the best chain. // // Of course, the subsidy is high enough, and transaction volume low enough, // that fee sniping isn't a problem yet, but by implementing a fix now we // ensure code won't be written that makes assumptions about nLockTime that // preclude a fix later. txNew.nLockTime = chainActive.Height(); // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, // e.g. high-latency mix networks and some CoinJoin implementations, have // better privacy. if (GetRandInt(10) == 0) { txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100)); } assert(txNew.nLockTime <= (unsigned int)chainActive.Height()); assert(txNew.nLockTime < LOCKTIME_THRESHOLD); { std::set> setCoins; LOCK2(cs_main, cs_wallet); std::vector vAvailableCoins; AvailableCoins(vAvailableCoins, true, coinControl); nFeeRet = Amount::zero(); // Start with no fee and loop until there is enough fee. while (true) { nChangePosInOut = nChangePosRequest; txNew.vin.clear(); txNew.vout.clear(); wtxNew.fFromMe = true; bool fFirst = true; Amount nValueToSelect = nValue; if (nSubtractFeeFromAmount == 0) { nValueToSelect += nFeeRet; } double dPriority = 0; // vouts to the payees for (const auto &recipient : vecSend) { CTxOut txout(recipient.nAmount, recipient.scriptPubKey); if (recipient.fSubtractFeeFromAmount) { // Subtract fee equally from each selected recipient. txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount); // First receiver pays the remainder not divisible by output // count. if (fFirst) { fFirst = false; txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount); } } if (txout.IsDust(dustRelayFee)) { if (recipient.fSubtractFeeFromAmount && nFeeRet > Amount::zero()) { if (txout.nValue < Amount::zero()) { strFailReason = _("The transaction amount is " "too small to pay the fee"); } else { strFailReason = _("The transaction amount is too small to " "send after the fee has been deducted"); } } else { strFailReason = _("Transaction amount too small"); } return false; } txNew.vout.push_back(txout); } // Choose coins to use. Amount nValueIn = Amount::zero(); setCoins.clear(); if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl)) { strFailReason = _("Insufficient funds"); return false; } for (const auto &pcoin : setCoins) { Amount nCredit = pcoin.first->tx->vout[pcoin.second].nValue; // The coin age after the next block (depth+1) is used instead // of the current, reflecting an assumption the user would // accept a bit more delay for a chance at a free transaction. // But mempool inputs might still be in the mempool, so their // age stays 0. int age = pcoin.first->GetDepthInMainChain(); assert(age >= 0); if (age != 0) { age += 1; } dPriority += (age * nCredit) / SATOSHI; } const Amount nChange = nValueIn - nValueToSelect; if (nChange > Amount::zero()) { // Fill a vout to ourself. // TODO: pass in scriptChange instead of reservekey so change // transaction isn't always pay-to-bitcoin-address. CScript scriptChange; // Coin control: send change to custom address. if (coinControl && !boost::get(&coinControl->destChange)) { scriptChange = GetScriptForDestination(coinControl->destChange); // No coin control: send change to newly generated address. } else { // Note: We use a new key here to keep it from being obvious // which side is the change. The drawback is that by not // reusing a previous key, the change may be lost if a // backup is restored, if the backup doesn't have the new // private key for the change. If we reused the old key, it // would be possible to add code to look for and rediscover // unknown transactions that were written with keys of ours // to recover post-backup change. // Reserve a new key pair from key pool. CPubKey vchPubKey; bool ret; ret = reservekey.GetReservedKey(vchPubKey, true); if (!ret) { strFailReason = _("Keypool ran out, please call " "keypoolrefill first"); return false; } scriptChange = GetScriptForDestination(vchPubKey.GetID()); } CTxOut newTxOut(nChange, scriptChange); // We do not move dust-change to fees, because the sender would // end up paying more than requested. This would be against the // purpose of the all-inclusive feature. So instead we raise the // change and deduct from the recipient. if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(dustRelayFee)) { Amount nDust = newTxOut.GetDustThreshold(dustRelayFee) - newTxOut.nValue; // Raise change until no more dust. newTxOut.nValue += nDust; // Subtract from first recipient. for (unsigned int i = 0; i < vecSend.size(); i++) { if (vecSend[i].fSubtractFeeFromAmount) { txNew.vout[i].nValue -= nDust; if (txNew.vout[i].IsDust(dustRelayFee)) { strFailReason = _("The transaction amount is too small " "to send after the fee has been " "deducted"); return false; } break; } } } // Never create dust outputs; if we would, just add the dust to // the fee. if (newTxOut.IsDust(dustRelayFee)) { nChangePosInOut = -1; nFeeRet += nChange; reservekey.ReturnKey(); } else { if (nChangePosInOut == -1) { // Insert change txn at random position: nChangePosInOut = GetRandInt(txNew.vout.size() + 1); } else if ((unsigned int)nChangePosInOut > txNew.vout.size()) { strFailReason = _("Change index out of range"); return false; } std::vector::iterator position = txNew.vout.begin() + nChangePosInOut; txNew.vout.insert(position, newTxOut); } } else { reservekey.ReturnKey(); } // Fill vin // // Note how the sequence number is set to non-maxint so that the // nLockTime set above actually works. for (const auto &coin : setCoins) { txNew.vin.push_back( CTxIn(coin.first->GetId(), coin.second, CScript(), std::numeric_limits::max() - 1)); } // Fill in dummy signatures for fee calculation. if (!DummySignTx(txNew, setCoins)) { strFailReason = _("Signing transaction failed"); return false; } CTransaction txNewConst(txNew); unsigned int nBytes = txNewConst.GetTotalSize(); dPriority = txNewConst.ComputePriority(dPriority, nBytes); // Remove scriptSigs to eliminate the fee calculation dummy // signatures. for (auto &vin : txNew.vin) { vin.scriptSig = CScript(); } // Allow to override the default confirmation target over the // CoinControl instance. int currentConfirmationTarget = nTxConfirmTarget; if (coinControl && coinControl->nConfirmTarget > 0) { currentConfirmationTarget = coinControl->nConfirmTarget; } Amount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, mempool); if (coinControl && nFeeNeeded > Amount::zero() && coinControl->nMinimumTotalFee > nFeeNeeded) { nFeeNeeded = coinControl->nMinimumTotalFee; } if (coinControl && coinControl->fOverrideFeeRate) { nFeeNeeded = coinControl->nFeeRate.GetFeeCeiling(nBytes); } // If we made it here and we aren't even able to meet the relay fee // on the next pass, give up because we must be at the maximum // allowed fee. Amount minFee = GetConfig().GetMinFeePerKB().GetFeeCeiling(nBytes); if (nFeeNeeded < minFee) { strFailReason = _("Transaction too large for fee policy"); return false; } if (nFeeRet >= nFeeNeeded) { // Reduce fee to only the needed amount if we have change output // to increase. This prevents potential overpayment in fees if // the coins selected to meet nFeeNeeded result in a transaction // that requires less fee than the prior iteration. // TODO: The case where nSubtractFeeFromAmount > 0 remains to be // addressed because it requires returning the fee to the payees // and not the change output. // TODO: The case where there is no change output remains to be // addressed so we avoid creating too small an output. if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { Amount extraFeePaid = nFeeRet - nFeeNeeded; std::vector::iterator change_position = txNew.vout.begin() + nChangePosInOut; change_position->nValue += extraFeePaid; nFeeRet -= extraFeePaid; } // Done, enough fee included. break; } // Try to reduce change to include necessary fee. if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { Amount additionalFeeNeeded = nFeeNeeded - nFeeRet; std::vector::iterator change_position = txNew.vout.begin() + nChangePosInOut; // Only reduce change if remaining amount is still a large // enough output. if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) { change_position->nValue -= additionalFeeNeeded; nFeeRet += additionalFeeNeeded; // Done, able to increase fee from change. break; } } // Include more fee and try again. nFeeRet = nFeeNeeded; continue; } if (sign) { SigHashType sigHashType = SigHashType().withForkId(); CTransaction txNewConst(txNew); int nIn = 0; for (const auto &coin : setCoins) { const CScript &scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey; SignatureData sigdata; if (!ProduceSignature( TransactionSignatureCreator( this, &txNewConst, nIn, coin.first->tx->vout[coin.second].nValue, sigHashType), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed"); return false; } UpdateTransaction(txNew, nIn, sigdata); nIn++; } } // Embed the constructed transaction data in wtxNew. wtxNew.SetTx(MakeTransactionRef(std::move(txNew))); // Limit size. if (CTransaction(wtxNew).GetTotalSize() >= MAX_STANDARD_TX_SIZE) { strFailReason = _("Transaction too large"); return false; } } if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits. LockPoints lp; CTxMemPoolEntry entry(wtxNew.tx, Amount::zero(), 0, 0, 0, Amount::zero(), false, 0, lp); CTxMemPool::setEntries setAncestors; size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; std::string errString; if (!mempool.CalculateMemPoolAncestors( entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { strFailReason = _("Transaction has too long of a mempool chain"); return false; } } return true; } /** * Call after CreateTransaction unless you want to abort */ bool CWallet::CommitTransaction(CWalletTx &wtxNew, CReserveKey &reservekey, CConnman *connman, CValidationState &state) { LOCK2(cs_main, cs_wallet); LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); // Take key pair from key pool so it won't be used again. reservekey.KeepKey(); // Add tx to wallet, because if it has change it's also ours, otherwise just // for transaction history. AddToWallet(wtxNew); // Notify that old coins are spent. for (const CTxIn &txin : wtxNew.tx->vin) { CWalletTx &coin = mapWallet[txin.prevout.GetTxId()]; coin.BindWallet(this); NotifyTransactionChanged(this, coin.GetId(), CT_UPDATED); } // Track how many getdata requests our transaction gets. mapRequestCount[wtxNew.GetId()] = 0; if (fBroadcastTransactions) { // Broadcast if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) { LogPrintf("CommitTransaction(): Transaction cannot be " "broadcast immediately, %s\n", state.GetRejectReason()); // TODO: if we expect the failure to be long term or permanent, // instead delete wtx from the wallet and return failure. } else { wtxNew.RelayWalletTransaction(connman); } } return true; } void CWallet::ListAccountCreditDebit(const std::string &strAccount, std::list &entries) { CWalletDB walletdb(*dbw); return walletdb.ListAccountCreditDebit(strAccount, entries); } bool CWallet::AddAccountingEntry(const CAccountingEntry &acentry) { CWalletDB walletdb(*dbw); return AddAccountingEntry(acentry, &walletdb); } bool CWallet::AddAccountingEntry(const CAccountingEntry &acentry, CWalletDB *pwalletdb) { if (!pwalletdb->WriteAccountingEntry(++nAccountingEntryNumber, acentry)) { return false; } laccentries.push_back(acentry); CAccountingEntry &entry = laccentries.back(); wtxOrdered.insert(std::make_pair(entry.nOrderPos, TxPair(nullptr, &entry))); return true; } DBErrors CWallet::LoadWallet(bool &fFirstRunRet) { fFirstRunRet = false; DBErrors nLoadWalletRet = CWalletDB(*dbw, "cr+").LoadWallet(this); if (nLoadWalletRet == DB_NEED_REWRITE) { if (dbw->Rewrite("\x04pool")) { LOCK(cs_wallet); setInternalKeyPool.clear(); setExternalKeyPool.clear(); m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. } } // This wallet is in its first run if all of these are empty fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty(); if (nLoadWalletRet != DB_LOAD_OK) { return nLoadWalletRet; } uiInterface.LoadWallet(this); return DB_LOAD_OK; } DBErrors CWallet::ZapSelectTx(std::vector &txIdsIn, std::vector &txIdsOut) { AssertLockHeld(cs_wallet); // mapWallet DBErrors nZapSelectTxRet = CWalletDB(*dbw, "cr+").ZapSelectTx(txIdsIn, txIdsOut); for (const TxId &txid : txIdsOut) { mapWallet.erase(txid); } if (nZapSelectTxRet == DB_NEED_REWRITE) { if (dbw->Rewrite("\x04pool")) { setInternalKeyPool.clear(); setExternalKeyPool.clear(); m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. } } if (nZapSelectTxRet != DB_LOAD_OK) { return nZapSelectTxRet; } MarkDirty(); return DB_LOAD_OK; } DBErrors CWallet::ZapWalletTx(std::vector &vWtx) { DBErrors nZapWalletTxRet = CWalletDB(*dbw, "cr+").ZapWalletTx(vWtx); if (nZapWalletTxRet == DB_NEED_REWRITE) { if (dbw->Rewrite("\x04pool")) { LOCK(cs_wallet); setInternalKeyPool.clear(); setExternalKeyPool.clear(); m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. } } if (nZapWalletTxRet != DB_LOAD_OK) { return nZapWalletTxRet; } return DB_LOAD_OK; } bool CWallet::SetAddressBook(const CTxDestination &address, const std::string &strName, const std::string &strPurpose) { bool fUpdated = false; { // mapAddressBook LOCK(cs_wallet); std::map::iterator mi = mapAddressBook.find(address); fUpdated = mi != mapAddressBook.end(); mapAddressBook[address].name = strName; // Update purpose only if requested. if (!strPurpose.empty()) { mapAddressBook[address].purpose = strPurpose; } } NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO, strPurpose, (fUpdated ? CT_UPDATED : CT_NEW)); if (!strPurpose.empty() && !CWalletDB(*dbw).WritePurpose(address, strPurpose)) { return false; } return CWalletDB(*dbw).WriteName(address, strName); } bool CWallet::DelAddressBook(const CTxDestination &address) { { // mapAddressBook LOCK(cs_wallet); // Delete destdata tuples associated with address. for (const std::pair &item : mapAddressBook[address].destdata) { CWalletDB(*dbw).EraseDestData(address, item.first); } mapAddressBook.erase(address); } NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED); CWalletDB(*dbw).ErasePurpose(address); return CWalletDB(*dbw).EraseName(address); } const std::string &CWallet::GetAccountName(const CScript &scriptPubKey) const { CTxDestination address; if (ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) { auto mi = mapAddressBook.find(address); if (mi != mapAddressBook.end()) { return mi->second.name; } } // A scriptPubKey that doesn't have an entry in the address book is // associated with the default account (""). const static std::string DEFAULT_ACCOUNT_NAME; return DEFAULT_ACCOUNT_NAME; } /** * Mark old keypool keys as used, and generate all new keys. */ bool CWallet::NewKeyPool() { LOCK(cs_wallet); CWalletDB walletdb(*dbw); for (int64_t nIndex : setInternalKeyPool) { walletdb.ErasePool(nIndex); } setInternalKeyPool.clear(); for (int64_t nIndex : setExternalKeyPool) { walletdb.ErasePool(nIndex); } setExternalKeyPool.clear(); m_pool_key_to_index.clear(); if (!TopUpKeyPool()) { return false; } LogPrintf("CWallet::NewKeyPool rewrote keypool\n"); return true; } size_t CWallet::KeypoolCountExternalKeys() { // setExternalKeyPool AssertLockHeld(cs_wallet); return setExternalKeyPool.size(); } void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) { AssertLockHeld(cs_wallet); if (keypool.fInternal) { setInternalKeyPool.insert(nIndex); } else { setExternalKeyPool.insert(nIndex); } m_max_keypool_index = std::max(m_max_keypool_index, nIndex); m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex; // If no metadata exists yet, create a default with the pool key's // creation time. Note that this may be overwritten by actually // stored metadata for that key later, which is fine. CKeyID keyid = keypool.vchPubKey.GetID(); if (mapKeyMetadata.count(keyid) == 0) { mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); } } bool CWallet::TopUpKeyPool(unsigned int kpSize) { LOCK(cs_wallet); if (IsLocked()) { return false; } // Top up key pool unsigned int nTargetSize; if (kpSize > 0) { nTargetSize = kpSize; } else { nTargetSize = std::max( gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), 0); } // count amount of available keys (internal, external) // make sure the keypool of external and internal keys fits the user // selected target (-keypool) int64_t missingExternal = std::max( std::max(nTargetSize, 1) - setExternalKeyPool.size(), 0); int64_t missingInternal = std::max( std::max(nTargetSize, 1) - setInternalKeyPool.size(), 0); if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) { // don't create extra internal keys missingInternal = 0; } bool internal = false; CWalletDB walletdb(*dbw); for (int64_t i = missingInternal + missingExternal; i--;) { if (i < missingInternal) { internal = true; } // How in the hell did you use so many keys? assert(m_max_keypool_index < std::numeric_limits::max()); int64_t index = ++m_max_keypool_index; CPubKey pubkey(GenerateNewKey(walletdb, internal)); if (!walletdb.WritePool(index, CKeyPool(pubkey, internal))) { throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); } if (internal) { setInternalKeyPool.insert(index); } else { setExternalKeyPool.insert(index); } m_pool_key_to_index[pubkey.GetID()] = index; } if (missingInternal + missingExternal > 0) { LogPrintf( "keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size()); } return true; } void CWallet::ReserveKeyFromKeyPool(int64_t &nIndex, CKeyPool &keypool, bool fRequestedInternal) { nIndex = -1; keypool.vchPubKey = CPubKey(); LOCK(cs_wallet); if (!IsLocked()) { TopUpKeyPool(); } bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal; std::set &setKeyPool = fReturningInternal ? setInternalKeyPool : setExternalKeyPool; // Get the oldest key if (setKeyPool.empty()) { return; } CWalletDB walletdb(*dbw); auto it = setKeyPool.begin(); nIndex = *it; setKeyPool.erase(it); if (!walletdb.ReadPool(nIndex, keypool)) { throw std::runtime_error(std::string(__func__) + ": read failed"); } if (!HaveKey(keypool.vchPubKey.GetID())) { throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); } if (keypool.fInternal != fReturningInternal) { throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); } assert(keypool.vchPubKey.IsValid()); m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); LogPrintf("keypool reserve %d\n", nIndex); } void CWallet::KeepKey(int64_t nIndex) { // Remove from key pool. CWalletDB walletdb(*dbw); walletdb.ErasePool(nIndex); LogPrintf("keypool keep %d\n", nIndex); } void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey &pubkey) { // Return to key pool { LOCK(cs_wallet); if (fInternal) { setInternalKeyPool.insert(nIndex); } else { setExternalKeyPool.insert(nIndex); } m_pool_key_to_index[pubkey.GetID()] = nIndex; } LogPrintf("keypool return %d\n", nIndex); } bool CWallet::GetKeyFromPool(CPubKey &result, bool internal) { CKeyPool keypool; LOCK(cs_wallet); int64_t nIndex = 0; ReserveKeyFromKeyPool(nIndex, keypool, internal); if (nIndex == -1) { if (IsLocked()) { return false; } CWalletDB walletdb(*dbw); result = GenerateNewKey(walletdb, internal); return true; } KeepKey(nIndex); result = keypool.vchPubKey; return true; } static int64_t GetOldestKeyTimeInPool(const std::set &setKeyPool, CWalletDB &walletdb) { if (setKeyPool.empty()) { return GetTime(); } CKeyPool keypool; int64_t nIndex = *(setKeyPool.begin()); if (!walletdb.ReadPool(nIndex, keypool)) { throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); } assert(keypool.vchPubKey.IsValid()); return keypool.nTime; } int64_t CWallet::GetOldestKeyPoolTime() { LOCK(cs_wallet); CWalletDB walletdb(*dbw); // load oldest key from keypool, get time and return int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, walletdb); if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) { oldestKey = std::max( GetOldestKeyTimeInPool(setInternalKeyPool, walletdb), oldestKey); } return oldestKey; } std::map CWallet::GetAddressBalances() { std::map balances; LOCK(cs_wallet); for (std::pair walletEntry : mapWallet) { CWalletTx *pcoin = &walletEntry.second; if (!pcoin->IsTrusted()) { continue; } if (pcoin->IsImmatureCoinBase()) { continue; } int nDepth = pcoin->GetDepthInMainChain(); if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) { continue; } for (uint32_t i = 0; i < pcoin->tx->vout.size(); i++) { CTxDestination addr; if (!IsMine(pcoin->tx->vout[i])) { continue; } if (!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) { continue; } Amount n = IsSpent(walletEntry.first, i) ? Amount::zero() : pcoin->tx->vout[i].nValue; if (!balances.count(addr)) { balances[addr] = Amount::zero(); } balances[addr] += n; } } return balances; } std::set> CWallet::GetAddressGroupings() { // mapWallet AssertLockHeld(cs_wallet); std::set> groupings; std::set grouping; for (std::pair walletEntry : mapWallet) { CWalletTx *pcoin = &walletEntry.second; if (pcoin->tx->vin.size() > 0) { bool any_mine = false; // Group all input addresses with each other. for (CTxIn txin : pcoin->tx->vin) { CTxDestination address; // If this input isn't mine, ignore it. if (!IsMine(txin)) { continue; } if (!ExtractDestination(mapWallet[txin.prevout.GetTxId()] .tx->vout[txin.prevout.GetN()] .scriptPubKey, address)) { continue; } grouping.insert(address); any_mine = true; } // Group change with input addresses. if (any_mine) { for (CTxOut txout : pcoin->tx->vout) { if (IsChange(txout)) { CTxDestination txoutAddr; if (!ExtractDestination(txout.scriptPubKey, txoutAddr)) { continue; } grouping.insert(txoutAddr); } } } if (grouping.size() > 0) { groupings.insert(grouping); grouping.clear(); } } // Group lone addrs by themselves. for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) if (IsMine(pcoin->tx->vout[i])) { CTxDestination address; if (!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, address)) { continue; } grouping.insert(address); groupings.insert(grouping); grouping.clear(); } } // A set of pointers to groups of addresses. std::set *> uniqueGroupings; // Map addresses to the unique group containing it. std::map *> setmap; for (std::set _grouping : groupings) { // Make a set of all the groups hit by this new group. std::set *> hits; std::map *>::iterator it; for (CTxDestination address : _grouping) { if ((it = setmap.find(address)) != setmap.end()) { hits.insert((*it).second); } } // Merge all hit groups into a new single group and delete old groups. std::set *merged = new std::set(_grouping); for (std::set *hit : hits) { merged->insert(hit->begin(), hit->end()); uniqueGroupings.erase(hit); delete hit; } uniqueGroupings.insert(merged); // Update setmap. for (CTxDestination element : *merged) { setmap[element] = merged; } } std::set> ret; for (std::set *uniqueGrouping : uniqueGroupings) { ret.insert(*uniqueGrouping); delete uniqueGrouping; } return ret; } std::set CWallet::GetAccountAddresses(const std::string &strAccount) const { LOCK(cs_wallet); std::set result; for (const std::pair &item : mapAddressBook) { const CTxDestination &address = item.first; const std::string &strName = item.second.name; if (strName == strAccount) { result.insert(address); } } return result; } bool CReserveKey::GetReservedKey(CPubKey &pubkey, bool internal) { if (nIndex == -1) { CKeyPool keypool; pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal); if (nIndex == -1) { return false; } vchPubKey = keypool.vchPubKey; fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); pubkey = vchPubKey; return true; } void CReserveKey::KeepKey() { if (nIndex != -1) { pwallet->KeepKey(nIndex); } nIndex = -1; vchPubKey = CPubKey(); } void CReserveKey::ReturnKey() { if (nIndex != -1) { pwallet->ReturnKey(nIndex, fInternal, vchPubKey); } nIndex = -1; vchPubKey = CPubKey(); } void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) { AssertLockHeld(cs_wallet); bool internal = setInternalKeyPool.count(keypool_id); if (!internal) { assert(setExternalKeyPool.count(keypool_id)); } std::set *setKeyPool = internal ? &setInternalKeyPool : &setExternalKeyPool; auto it = setKeyPool->begin(); CWalletDB walletdb(*dbw); while (it != std::end(*setKeyPool)) { const int64_t &index = *(it); if (index > keypool_id) { // set*KeyPool is ordered break; } CKeyPool keypool; if (walletdb.ReadPool(index, keypool)) { // TODO: This should be unnecessary m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); } walletdb.ErasePool(index); it = setKeyPool->erase(it); } } bool CWallet::HasUnusedKeys(size_t min_keys) const { return setExternalKeyPool.size() >= min_keys && (setInternalKeyPool.size() >= min_keys || !CanSupportFeature(FEATURE_HD_SPLIT)); } void CWallet::GetScriptForMining(std::shared_ptr &script) { std::shared_ptr rKey = std::make_shared(this); CPubKey pubkey; if (!rKey->GetReservedKey(pubkey)) { return; } script = rKey; script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; } void CWallet::LockCoin(const COutPoint &output) { // setLockedCoins AssertLockHeld(cs_wallet); setLockedCoins.insert(output); } void CWallet::UnlockCoin(const COutPoint &output) { // setLockedCoins AssertLockHeld(cs_wallet); setLockedCoins.erase(output); } void CWallet::UnlockAllCoins() { // setLockedCoins AssertLockHeld(cs_wallet); setLockedCoins.clear(); } bool CWallet::IsLockedCoin(const TxId &txid, uint32_t n) const { // setLockedCoins AssertLockHeld(cs_wallet); COutPoint outpt(txid, n); return setLockedCoins.count(outpt) > 0; } void CWallet::ListLockedCoins(std::vector &vOutpts) { // setLockedCoins AssertLockHeld(cs_wallet); for (COutPoint outpoint : setLockedCoins) { vOutpts.push_back(outpoint); } } /** @} */ // end of Actions void CWallet::GetKeyBirthTimes( std::map &mapKeyBirth) const { // mapKeyMetadata AssertLockHeld(cs_wallet); mapKeyBirth.clear(); // Get birth times for keys with metadata. for (const auto &entry : mapKeyMetadata) { if (entry.second.nCreateTime) { mapKeyBirth[entry.first] = entry.second.nCreateTime; } } // Map in which we'll infer heights of other keys the tip can be // reorganized; use a 144-block safety margin. CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; std::map mapKeyFirstBlock; std::set setKeys; GetKeys(setKeys); for (const CKeyID &keyid : setKeys) { if (mapKeyBirth.count(keyid) == 0) { mapKeyFirstBlock[keyid] = pindexMax; } } setKeys.clear(); // If there are no such keys, we're done. if (mapKeyFirstBlock.empty()) { return; } // Find first block that affects those keys, if there are any left. std::vector vAffected; for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) { // Iterate over all wallet transactions... const CWalletTx &wtx = (*it).second; BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) { // ... which are already in a block. int nHeight = blit->second->nHeight; for (const CTxOut &txout : wtx.tx->vout) { // Iterate over all their outputs... CAffectedKeysVisitor(*this, vAffected) .Process(txout.scriptPubKey); for (const CKeyID &keyid : vAffected) { // ... and all their affected keys. std::map::iterator rit = mapKeyFirstBlock.find(keyid); if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) { rit->second = blit->second; } } vAffected.clear(); } } } // Extract block timestamps for those keys. for (const std::pair &p : mapKeyFirstBlock) { // Block times can be 2h off. mapKeyBirth[p.first] = p.second->GetBlockTime() - TIMESTAMP_WINDOW; } } /** * Compute smart timestamp for a transaction being added to the wallet. * * Logic: * - If sending a transaction, assign its timestamp to the current time. * - If receiving a transaction outside a block, assign its timestamp to the * current time. * - If receiving a block with a future timestamp, assign all its (not already * known) transactions' timestamps to the current time. * - If receiving a block with a past timestamp, before the most recent known * transaction (that we care about), assign all its (not already known) * transactions' timestamps to the same timestamp as that most-recent-known * transaction. * - If receiving a block with a past timestamp, but after the most recent known * transaction, assign all its (not already known) transactions' timestamps to * the block time. * * For more information see CWalletTx::nTimeSmart, * https://bitcointalk.org/?topic=54527, or * https://github.com/bitcoin/bitcoin/pull/1393. */ unsigned int CWallet::ComputeTimeSmart(const CWalletTx &wtx) const { unsigned int nTimeSmart = wtx.nTimeReceived; if (!wtx.hashUnset()) { if (mapBlockIndex.count(wtx.hashBlock)) { int64_t latestNow = wtx.nTimeReceived; int64_t latestEntry = 0; // Tolerate times up to the last timestamp in the wallet not more // than 5 minutes into the future int64_t latestTolerated = latestNow + 300; const TxItems &txOrdered = wtxOrdered; for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = it->second.first; if (pwtx == &wtx) { continue; } CAccountingEntry *const pacentry = it->second.second; int64_t nSmartTime; if (pwtx) { nSmartTime = pwtx->nTimeSmart; if (!nSmartTime) { nSmartTime = pwtx->nTimeReceived; } } else { nSmartTime = pacentry->nTime; } if (nSmartTime <= latestTolerated) { latestEntry = nSmartTime; if (nSmartTime > latestNow) { latestNow = nSmartTime; } break; } } int64_t blocktime = mapBlockIndex[wtx.hashBlock]->GetBlockTime(); nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { LogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetId().ToString(), wtx.hashBlock.ToString()); } } return nTimeSmart; } bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value) { if (boost::get(&dest)) { return false; } mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); return CWalletDB(*dbw).WriteDestData(dest, key, value); } bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key) { if (!mapAddressBook[dest].destdata.erase(key)) { return false; } return CWalletDB(*dbw).EraseDestData(dest, key); } bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) { mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); return true; } bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const { std::map::const_iterator i = mapAddressBook.find(dest); if (i != mapAddressBook.end()) { CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key); if (j != i->second.destdata.end()) { if (value) { *value = j->second; } return true; } } return false; } CWallet *CWallet::CreateWalletFromFile(const CChainParams &chainParams, const std::string walletFile) { // Needed to restore wallet transaction meta data after -zapwallettxes std::vector vWtx; if (gArgs.GetBoolArg("-zapwallettxes", false)) { uiInterface.InitMessage(_("Zapping all transactions from wallet...")); std::unique_ptr dbw( new CWalletDBWrapper(&bitdb, walletFile)); CWallet *tempWallet = new CWallet(chainParams, std::move(dbw)); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DB_LOAD_OK) { InitError( strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); return nullptr; } delete tempWallet; tempWallet = nullptr; } uiInterface.InitMessage(_("Loading wallet...")); int64_t nStart = GetTimeMillis(); bool fFirstRun = true; std::unique_ptr dbw( new CWalletDBWrapper(&bitdb, walletFile)); CWallet *walletInstance = new CWallet(chainParams, std::move(dbw)); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DB_LOAD_OK) { if (nLoadWalletRet == DB_CORRUPT) { InitError( strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); return nullptr; } if (nLoadWalletRet == DB_NONCRITICAL_ERROR) { InitWarning(strprintf( _("Error reading %s! All keys read correctly, but transaction " "data" " or address book entries might be missing or incorrect."), walletFile)); } else if (nLoadWalletRet == DB_TOO_NEW) { InitError(strprintf( _("Error loading %s: Wallet requires newer version of %s"), walletFile, _(PACKAGE_NAME))); return nullptr; } else if (nLoadWalletRet == DB_NEED_REWRITE) { InitError(strprintf( _("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME))); return nullptr; } else { InitError(strprintf(_("Error loading %s"), walletFile)); return nullptr; } } if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) { int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); // The -upgradewallet without argument case if (nMaxVersion == 0) { LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); nMaxVersion = CLIENT_VERSION; // permanently upgrade the wallet immediately walletInstance->SetMinVersion(FEATURE_LATEST); } else { LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); } if (nMaxVersion < walletInstance->GetVersion()) { InitError(_("Cannot downgrade wallet")); return nullptr; } walletInstance->SetMaxVersion(nMaxVersion); } if (fFirstRun) { // Create new keyUser and set as default key. if (gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !walletInstance->IsHDEnabled()) { // Ensure this wallet.dat can only be opened by clients supporting // HD with chain split. walletInstance->SetMinVersion(FEATURE_HD_SPLIT); // Generate a new master key. CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey(); if (!walletInstance->SetHDMasterKey(masterPubKey)) { throw std::runtime_error(std::string(__func__) + ": Storing master key failed"); } } // Top up the keypool if (!walletInstance->TopUpKeyPool()) { InitError(_("Unable to generate initial keys") += "\n"); return nullptr; } walletInstance->SetBestChain(chainActive.GetLocator()); } else if (gArgs.IsArgSet("-usehd")) { bool useHD = gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET); if (walletInstance->IsHDEnabled() && !useHD) { InitError(strprintf(_("Error loading %s: You can't disable HD on a " "already existing HD wallet"), walletFile)); return nullptr; } if (!walletInstance->IsHDEnabled() && useHD) { InitError(strprintf(_("Error loading %s: You can't enable HD on a " "already existing non-HD wallet"), walletFile)); return nullptr; } } LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); RegisterValidationInterface(walletInstance); // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); CBlockIndex *pindexRescan = chainActive.Genesis(); if (!gArgs.GetBoolArg("-rescan", false)) { CWalletDB walletdb(*walletInstance->dbw); CBlockLocator locator; if (walletdb.ReadBestBlock(locator)) { pindexRescan = FindForkInGlobalIndex(chainActive, locator); } } if (chainActive.Tip() && chainActive.Tip() != pindexRescan) { // We can't rescan beyond non-pruned blocks, stop and throw an error. // This might happen if a user uses a old wallet within a pruned node or // if he ran -disablewallet for a longer time, then decided to // re-enable. if (fPruneMode) { CBlockIndex *block = chainActive.Tip(); while (block && block->pprev && block->pprev->nStatus.hasData() && block->pprev->nTx > 0 && pindexRescan != block) { block = block->pprev; } if (pindexRescan != block) { InitError(_("Prune: last wallet synchronisation goes beyond " "pruned data. You need to -reindex (download the " "whole blockchain again in case of pruned node)")); return nullptr; } } uiInterface.InitMessage(_("Rescanning...")); LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); // No need to read and scan block if block was created before our wallet // birthday (as adjusted for block time variability) while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) { pindexRescan = chainActive.Next(pindexRescan); } nStart = GetTimeMillis(); walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true); LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); walletInstance->SetBestChain(chainActive.GetLocator()); walletInstance->dbw->IncrementUpdateCounter(); // Restore wallet transaction metadata after -zapwallettxes=1 if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2") { CWalletDB walletdb(*walletInstance->dbw); for (const CWalletTx &wtxOld : vWtx) { const TxId txid = wtxOld.GetId(); std::map::iterator mi = walletInstance->mapWallet.find(txid); if (mi != walletInstance->mapWallet.end()) { const CWalletTx *copyFrom = &wtxOld; CWalletTx *copyTo = &mi->second; copyTo->mapValue = copyFrom->mapValue; copyTo->vOrderForm = copyFrom->vOrderForm; copyTo->nTimeReceived = copyFrom->nTimeReceived; copyTo->nTimeSmart = copyFrom->nTimeSmart; copyTo->fFromMe = copyFrom->fFromMe; copyTo->strFromAccount = copyFrom->strFromAccount; copyTo->nOrderPos = copyFrom->nOrderPos; walletdb.WriteTx(*copyTo); } } } } walletInstance->SetBroadcastTransactions( gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); LOCK(walletInstance->cs_wallet); LogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); LogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); LogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); return walletInstance; } std::atomic CWallet::fFlushScheduled(false); void CWallet::postInitProcess(CScheduler &scheduler) { // Add wallet transactions that aren't already in a block to mempool. // Do this here as mempool requires genesis block to be loaded. ReacceptWalletTransactions(); // Run a thread to flush wallet periodically. if (!CWallet::fFlushScheduled.exchange(true)) { - scheduler.scheduleEvery(MaybeCompactWalletDB, 500); + scheduler.scheduleEvery( + []() { + MaybeCompactWalletDB(); + return true; + }, + 500); } } bool CWallet::BackupWallet(const std::string &strDest) { return dbw->Backup(strDest); } CKeyPool::CKeyPool() { nTime = GetTime(); fInternal = false; } CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) { nTime = GetTime(); vchPubKey = vchPubKeyIn; fInternal = internalIn; } CWalletKey::CWalletKey(int64_t nExpires) { nTimeCreated = (nExpires ? GetTime() : 0); nTimeExpires = nExpires; } void CMerkleTx::SetMerkleBranch(const CBlockIndex *pindex, int posInBlock) { // Update the tx's hashBlock hashBlock = pindex->GetBlockHash(); // Set the position of the transaction in the block. nIndex = posInBlock; } int CMerkleTx::GetDepthInMainChain(const CBlockIndex *&pindexRet) const { if (hashUnset()) { return 0; } AssertLockHeld(cs_main); // Find the block it claims to be in. BlockMap::iterator mi = mapBlockIndex.find(hashBlock); if (mi == mapBlockIndex.end()) { return 0; } CBlockIndex *pindex = (*mi).second; if (!pindex || !chainActive.Contains(pindex)) { return 0; } pindexRet = pindex; return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1); } int CMerkleTx::GetBlocksToMaturity() const { if (!IsCoinBase()) { return 0; } return std::max(0, (COINBASE_MATURITY + 1) - GetDepthInMainChain()); } bool CMerkleTx::IsImmatureCoinBase() const { // note GetBlocksToMaturity is 0 for non-coinbase tx return GetBlocksToMaturity() > 0; } bool CMerkleTx::AcceptToMemoryPool(const Amount nAbsurdFee, CValidationState &state) { return ::AcceptToMemoryPool(GetConfig(), mempool, state, tx, true, nullptr, false, nAbsurdFee); }