diff --git a/src/avalanche.cpp b/src/avalanche.cpp index 6a32df6ba3..df18f13e1e 100644 --- a/src/avalanche.cpp +++ b/src/avalanche.cpp @@ -1,180 +1,279 @@ // Copyright (c) 2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "avalanche.h" #include "chain.h" #include "netmessagemaker.h" #include "scheduler.h" #include "validation.h" #include bool AvalancheProcessor::addBlockToReconcile(const CBlockIndex *pindex) { return vote_records.getWriteView() ->insert(std::make_pair(pindex, VoteRecord())) .second; } static const VoteRecord * GetRecord(const RWCollection &vote_records, const CBlockIndex *pindex) { auto r = vote_records.getReadView(); auto it = r->find(pindex); if (it == r.end()) { return nullptr; } return &it->second; } bool AvalancheProcessor::isAccepted(const CBlockIndex *pindex) const { if (auto vr = GetRecord(vote_records, pindex)) { return vr->isAccepted(); } return false; } bool AvalancheProcessor::registerVotes( - const AvalancheResponse &response, + NodeId nodeid, const AvalancheResponse &response, std::vector &updates) { + RequestRecord r; + + { + // Check that the query exists. + auto w = queries.getWriteView(); + auto it = w->find(nodeid); + if (it == w.end()) { + // NB: The request may be old, so we don't increase banscore. + return false; + } + + r = std::move(it->second); + w->erase(it); + } + + // Verify that the request and the vote are consistent. + const std::vector &invs = r.GetInvs(); const std::vector &votes = response.GetVotes(); + size_t size = invs.size(); + if (votes.size() != size) { + // TODO: increase banscore for inconsistent response. + // NB: This isn't timeout but actually node misbehaving. + return false; + } + + for (size_t i = 0; i < size; i++) { + if (invs[i].hash != votes[i].GetHash()) { + // TODO: increase banscore for inconsistent response. + // NB: This isn't timeout but actually node misbehaving. + return false; + } + } std::map responseIndex; { LOCK(cs_main); for (auto &v : votes) { BlockMap::iterator mi = mapBlockIndex.find(v.GetHash()); if (mi == mapBlockIndex.end()) { // This should not happen, but just in case... continue; } responseIndex.insert(std::make_pair(mi->second, v)); } } { // Register votes. auto w = vote_records.getWriteView(); for (auto &p : responseIndex) { CBlockIndex *pindex = p.first; const AvalancheVote &v = p.second; auto it = w->find(pindex); if (it == w.end()) { // We are not voting on that item anymore. continue; } auto &vr = it->second; if (!vr.registerVote(v.IsValid())) { // This vote did not provide any extra information, move on. continue; } if (!vr.hasFinalized()) { // This item has note been finalized, so we have nothing more to // do. updates.emplace_back( pindex, vr.isAccepted() ? AvalancheBlockUpdate::Status::Accepted : AvalancheBlockUpdate::Status::Rejected); continue; } // We just finalized a vote. If it is valid, then let the caller // know. Either way, remove the item from the map. updates.emplace_back(pindex, vr.isAccepted() ? AvalancheBlockUpdate::Status::Finalized : AvalancheBlockUpdate::Status::Invalid); w->erase(it); } } + // Put the node back in the list of queriable nodes. + auto w = nodeids.getWriteView(); + w->insert(nodeid); return true; } namespace { /** * Run the avalanche event loop every 10ms. */ static int64_t AVALANCHE_TIME_STEP_MILLISECONDS = 10; /** * Maximum item that can be polled at once. */ static size_t AVALANCHE_MAX_ELEMENT_POLL = 4096; } bool AvalancheProcessor::startEventLoop(CScheduler &scheduler) { LOCK(cs_running); if (running) { // Do not start the event loop twice. return false; } running = true; // Start the event loop. scheduler.scheduleEvery( [this]() -> bool { + runEventLoop(); if (!stopRequest) { return true; } LOCK(cs_running); running = false; cond_running.notify_all(); // A stop request was made. return false; }, AVALANCHE_TIME_STEP_MILLISECONDS); return true; } bool AvalancheProcessor::stopEventLoop() { WAIT_LOCK(cs_running, lock); if (!running) { return false; } // Request avalanche to stop. stopRequest = true; // Wait for avalanche to stop. cond_running.wait(lock, [this] { return !running; }); stopRequest = false; return true; } std::vector AvalancheProcessor::getInvsForNextPoll() const { std::vector invs; auto r = vote_records.getReadView(); for (const std::pair &p : boost::adaptors::reverse(r)) { const VoteRecord &v = p.second; if (v.hasFinalized()) { // If this has finalized, we can just skip. continue; } // We don't have a decision, we need more votes. invs.emplace_back(MSG_BLOCK, p.first->GetBlockHash()); if (invs.size() >= AVALANCHE_MAX_ELEMENT_POLL) { // Make sure we do not produce more invs than specified by the // protocol. return invs; } } return invs; } + +NodeId AvalancheProcessor::getSuitableNodeToQuery() { + auto w = nodeids.getWriteView(); + if (w->empty()) { + auto r = queries.getReadView(); + + // We don't have any candidate node, so let's try to find some. + connman->ForEachNode([&w, &r](CNode *pnode) { + // If this node doesn't support avalanche, we remove. + if (!(pnode->nServices & NODE_AVALANCHE)) { + return; + } + + // if we have a request in flight for that node. + if (r->find(pnode->GetId()) != r.end()) { + return; + } + + w->insert(pnode->GetId()); + }); + } + + // We don't have any suitable candidate. + if (w->empty()) { + return -1; + } + + auto it = w.begin(); + NodeId nodeid = *it; + w->erase(it); + + return nodeid; +} + +void AvalancheProcessor::runEventLoop() { + std::vector invs = getInvsForNextPoll(); + if (invs.empty()) { + // If there are no invs to poll, we are done. + return; + } + + NodeId nodeid = getSuitableNodeToQuery(); + + /** + * If we lost contact to that node, then we remove it from nodeids, but + * never add the request to queries, which ensures bad nodes get cleaned up + * over time. + */ + connman->ForNode(nodeid, [this, &invs](CNode *pnode) { + { + // Register the query. + queries.getWriteView()->emplace( + pnode->GetId(), RequestRecord(GetAdjustedTime(), invs)); + } + + // Send the query to the node. + connman->PushMessage( + pnode, + CNetMsgMaker(pnode->GetSendVersion()) + .Make(NetMsgType::AVAPOLL, + AvalanchePoll(round++, std::move(invs)))); + return true; + }); +} diff --git a/src/avalanche.h b/src/avalanche.h index 5c8eab539d..fdf22a0683 100644 --- a/src/avalanche.h +++ b/src/avalanche.h @@ -1,210 +1,256 @@ // Copyright (c) 2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_AVALANCHE_H #define BITCOIN_AVALANCHE_H #include "blockindexworkcomparator.h" #include "net.h" #include "protocol.h" // for CInv #include "rwcollection.h" #include "serialize.h" #include "uint256.h" #include #include #include #include class Config; class CBlockIndex; class CScheduler; namespace { /** * Finalization score. */ static int AVALANCHE_FINALIZATION_SCORE = 128; } /** * Vote history. */ struct VoteRecord { private: // Historical record of votes. uint16_t votes; // confidence's LSB bit is the result. Higher bits are actual confidence // score. uint16_t confidence; /** * Return the number of bits set in an integer value. * TODO: There are compiler intrinsics to do that, but we'd need to get them * detected so this will do for now. */ static uint32_t countBits(uint32_t value) { uint32_t count = 0; while (value) { // If the value is non zero, then at least one bit is set. count++; // Clear the rightmost bit set. value &= (value - 1); } return count; } public: VoteRecord() : votes(0xaaaa), confidence(0) {} bool isAccepted() const { return confidence & 0x01; } uint16_t getConfidence() const { return confidence >> 1; } bool hasFinalized() const { return getConfidence() >= AVALANCHE_FINALIZATION_SCORE; } /** * Register a new vote for an item and update confidence accordingly. * Returns true if the acceptance or finalization state changed. */ bool registerVote(bool vote) { votes = (votes << 1) | vote; auto bits = countBits(votes & 0xff); bool yes = bits > 6; bool no = bits < 2; if (!yes && !no) { // The vote is inconclusive. return false; } if (isAccepted() == yes) { // If the vote is in agreement with our internal status, increase // confidence. confidence += 2; return getConfidence() == AVALANCHE_FINALIZATION_SCORE; } // The vote did not agree with our internal state, in that case, reset // confidence. confidence = yes; return true; } }; class AvalancheVote { uint32_t error; uint256 hash; public: AvalancheVote() : error(-1), hash() {} AvalancheVote(uint32_t errorIn, uint256 hashIn) : error(errorIn), hash(hashIn) {} const uint256 &GetHash() const { return hash; } bool IsValid() const { return error == 0; } // serialization support ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(error); READWRITE(hash); } }; class AvalancheResponse { uint32_t cooldown; std::vector votes; public: AvalancheResponse(uint32_t cooldownIn, std::vector votesIn) : cooldown(cooldownIn), votes(votesIn) {} const std::vector &GetVotes() const { return votes; } // serialization support ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(cooldown); READWRITE(votes); } }; +class AvalanchePoll { + uint32_t round; + std::vector invs; + +public: + AvalanchePoll(uint32_t roundIn, std::vector invsIn) + : round(roundIn), invs(invsIn) {} + + const std::vector &GetInvs() const { return invs; } + + // serialization support + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream &s, Operation ser_action) { + READWRITE(round); + READWRITE(invs); + } +}; + class AvalancheBlockUpdate { union { CBlockIndex *pindex; size_t raw; }; public: enum Status : uint8_t { Invalid, Rejected, Accepted, Finalized, }; AvalancheBlockUpdate(CBlockIndex *pindexIn, Status statusIn) : pindex(pindexIn) { raw |= statusIn; } Status getStatus() const { return Status(raw & 0x03); } CBlockIndex *getBlockIndex() { return reinterpret_cast(raw & -size_t(0x04)); } const CBlockIndex *getBlockIndex() const { return const_cast(this)->getBlockIndex(); } }; typedef std::map BlockVoteMap; class AvalancheProcessor { private: + CConnman *connman; + /** * Blocks to run avalanche on. */ RWCollection vote_records; + /** + * Keep track of peers and queries sent. + */ + struct RequestRecord { + private: + int64_t timestamp; + std::vector invs; + + public: + RequestRecord() : timestamp(0), invs() {} + RequestRecord(int64_t timestampIn, std::vector invIn) + : timestamp(timestampIn), invs(std::move(invIn)) {} + + int64_t GetTimestamp() const { return timestamp; } + const std::vector &GetInvs() const { return invs; } + }; + + std::atomic round; + RWCollection> nodeids; + RWCollection> queries; + /** * Start stop machinery. */ std::atomic stopRequest; bool running GUARDED_BY(cs_running); CWaitableCriticalSection cs_running; std::condition_variable cond_running; public: - AvalancheProcessor() : stopRequest(false), running(false) {} + AvalancheProcessor(CConnman *connmanIn) + : connman(connmanIn), stopRequest(false), running(false) {} ~AvalancheProcessor() { stopEventLoop(); } bool addBlockToReconcile(const CBlockIndex *pindex); bool isAccepted(const CBlockIndex *pindex) const; - bool registerVotes(const AvalancheResponse &response, + bool registerVotes(NodeId nodeid, const AvalancheResponse &response, std::vector &updates); bool startEventLoop(CScheduler &scheduler); bool stopEventLoop(); private: + void runEventLoop(); std::vector getInvsForNextPoll() const; + NodeId getSuitableNodeToQuery(); friend struct AvalancheTest; }; #endif // BITCOIN_AVALANCHE_H diff --git a/src/net.h b/src/net.h index 3792704d3f..06fe5c5639 100644 --- a/src/net.h +++ b/src/net.h @@ -1,858 +1,859 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_NET_H #define BITCOIN_NET_H #include "addrdb.h" #include "addrman.h" #include "amount.h" #include "bloom.h" #include "chainparams.h" #include "compat.h" #include "hash.h" #include "limitedmap.h" #include "netaddress.h" #include "protocol.h" #include "random.h" #include "streams.h" #include "sync.h" #include "threadinterrupt.h" #include "uint256.h" #include #include #include #include #include #include #ifndef WIN32 #include #endif class Config; class CNode; class CScheduler; /** * Time between pings automatically sent out for latency probing and keepalive * (in seconds). */ static const int PING_INTERVAL = 2 * 60; /** * Time after which to disconnect, after waiting for a ping response (or * inactivity). */ static const int TIMEOUT_INTERVAL = 20 * 60; /** Run the feeler connection loop once every 2 minutes or 120 seconds. **/ static const int FEELER_INTERVAL = 120; /** The maximum number of entries in an 'inv' protocol message */ static const unsigned int MAX_INV_SZ = 50000; /** The maximum number of new addresses to accumulate before announcing. */ static const unsigned int MAX_ADDR_TO_SEND = 1000; /** Maximum length of strSubVer in `version` message */ static const unsigned int MAX_SUBVERSION_LENGTH = 256; /** Maximum number of automatic outgoing nodes */ static const int MAX_OUTBOUND_CONNECTIONS = 8; /** Maximum number of addnode outgoing nodes */ static const int MAX_ADDNODE_CONNECTIONS = 8; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ #ifdef USE_UPNP static const bool DEFAULT_UPNP = USE_UPNP; #else static const bool DEFAULT_UPNP = false; #endif /** The maximum number of entries in mapAskFor */ static const size_t MAPASKFOR_MAX_SZ = MAX_INV_SZ; /** The maximum number of entries in setAskFor (larger due to getdata latency)*/ static const size_t SETASKFOR_MAX_SZ = 2 * MAX_INV_SZ; /** The maximum number of peer connections to maintain. */ static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125; /** The default for -maxuploadtarget. 0 = Unlimited */ static const uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0; /** The default timeframe for -maxuploadtarget. 1 day. */ static const uint64_t MAX_UPLOAD_TIMEFRAME = 60 * 60 * 24; /** Default for blocks only*/ static const bool DEFAULT_BLOCKSONLY = false; static const bool DEFAULT_FORCEDNSSEED = false; static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000; static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000; // Default 24-hour ban. // NOTE: When adjusting this, update rpcnet:setban's help ("24h") static const unsigned int DEFAULT_MISBEHAVING_BANTIME = 60 * 60 * 24; typedef int64_t NodeId; struct AddedNodeInfo { std::string strAddedNode; CService resolvedAddress; bool fConnected; bool fInbound; }; class CNodeStats; class CClientUIInterface; struct CSerializedNetMsg { CSerializedNetMsg() = default; CSerializedNetMsg(CSerializedNetMsg &&) = default; CSerializedNetMsg &operator=(CSerializedNetMsg &&) = default; // No copying, only moves. CSerializedNetMsg(const CSerializedNetMsg &msg) = delete; CSerializedNetMsg &operator=(const CSerializedNetMsg &) = delete; std::vector data; std::string command; }; class NetEventsInterface; class CConnman { public: enum NumConnections { CONNECTIONS_NONE = 0, CONNECTIONS_IN = (1U << 0), CONNECTIONS_OUT = (1U << 1), CONNECTIONS_ALL = (CONNECTIONS_IN | CONNECTIONS_OUT), }; struct Options { ServiceFlags nLocalServices = NODE_NONE; int nMaxConnections = 0; int nMaxOutbound = 0; int nMaxAddnode = 0; int nMaxFeeler = 0; int nBestHeight = 0; CClientUIInterface *uiInterface = nullptr; NetEventsInterface *m_msgproc = nullptr; unsigned int nSendBufferMaxSize = 0; unsigned int nReceiveFloodSize = 0; uint64_t nMaxOutboundTimeframe = 0; uint64_t nMaxOutboundLimit = 0; std::vector vSeedNodes; std::vector vWhitelistedRange; std::vector vBinds, vWhiteBinds; bool m_use_addrman_outgoing = true; std::vector m_specified_outgoing; std::vector m_added_nodes; }; void Init(const Options &connOptions) { nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; nMaxOutbound = std::min(connOptions.nMaxOutbound, connOptions.nMaxConnections); nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; m_msgproc = connOptions.m_msgproc; nSendBufferMaxSize = connOptions.nSendBufferMaxSize; nReceiveFloodSize = connOptions.nReceiveFloodSize; nMaxOutboundTimeframe = connOptions.nMaxOutboundTimeframe; nMaxOutboundLimit = connOptions.nMaxOutboundLimit; vWhitelistedRange = connOptions.vWhitelistedRange; vAddedNodes = connOptions.m_added_nodes; } CConnman(const Config &configIn, uint64_t seed0, uint64_t seed1); ~CConnman(); bool Start(CScheduler &scheduler, const Options &options); void Stop(); void Interrupt(); bool GetNetworkActive() const { return fNetworkActive; }; void SetNetworkActive(bool active); void OpenNetworkConnection(const CAddress &addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false); bool CheckIncomingNonce(uint64_t nonce); bool ForNode(NodeId id, std::function func); void PushMessage(CNode *pnode, CSerializedNetMsg &&msg); template void ForEachNode(Callable &&func) { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { func(node); } } }; template void ForEachNode(Callable &&func) const { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { func(node); } } }; template void ForEachNodeThen(Callable &&pre, CallableAfter &&post) { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { pre(node); } } post(); }; template void ForEachNodeThen(Callable &&pre, CallableAfter &&post) const { LOCK(cs_vNodes); for (auto &&node : vNodes) { if (NodeFullyConnected(node)) { pre(node); } } post(); }; // Addrman functions size_t GetAddressCount() const; void SetServices(const CService &addr, ServiceFlags nServices); void MarkAddressGood(const CAddress &addr); void AddNewAddresses(const std::vector &vAddr, const CAddress &addrFrom, int64_t nTimePenalty = 0); std::vector GetAddresses(); // Denial-of-service detection/prevention. The idea is to detect peers that // are behaving badly and disconnect/ban them, but do it in a // one-coding-mistake-won't-shatter-the-entire-network way. // IMPORTANT: There should be nothing I can give a node that it will forward // on that will make that node's peers drop it. If there is, an attacker can // isolate a node and/or try to split the network. Dropping a node for // sending stuff that is invalid now but might be valid in a later version // is also dangerous, because it can cause a network split between nodes // running old code and nodes running new code. void Ban(const CNetAddr &netAddr, const BanReason &reason, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false); void Ban(const CSubNet &subNet, const BanReason &reason, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false); // Needed for unit testing. void ClearBanned(); bool IsBanned(CNetAddr ip); bool IsBanned(CSubNet subnet); bool Unban(const CNetAddr &ip); bool Unban(const CSubNet &ip); void GetBanned(banmap_t &banmap); void SetBanned(const banmap_t &banmap); // This allows temporarily exceeding nMaxOutbound, with the goal of finding // a peer that is better than all our current peers. void SetTryNewOutboundPeer(bool flag); bool GetTryNewOutboundPeer(); // Return the number of outbound peers we have in excess of our target (eg, // if we previously called SetTryNewOutboundPeer(true), and have since set // to false, we may have extra peers that we wish to disconnect). This may // return a value less than (num_outbound_connections - num_outbound_slots) // in cases where some outbound connections are not yet fully connected, or // not yet fully disconnected. int GetExtraOutboundCount(); bool AddNode(const std::string &node); bool RemoveAddedNode(const std::string &node); std::vector GetAddedNodeInfo(); size_t GetNodeCount(NumConnections num); void GetNodeStats(std::vector &vstats); bool DisconnectNode(const std::string &node); bool DisconnectNode(NodeId id); ServiceFlags GetLocalServices() const; //! set the max outbound target in bytes. void SetMaxOutboundTarget(uint64_t limit); uint64_t GetMaxOutboundTarget(); //! set the timeframe for the max outbound target. void SetMaxOutboundTimeframe(uint64_t timeframe); uint64_t GetMaxOutboundTimeframe(); //! check if the outbound target is reached. // If param historicalBlockServingLimit is set true, the function will // response true if the limit for serving historical blocks has been // reached. bool OutboundTargetReached(bool historicalBlockServingLimit); //! response the bytes left in the current max outbound cycle // in case of no limit, it will always response 0 uint64_t GetOutboundTargetBytesLeft(); //! response the time in second left in the current max outbound cycle // in case of no limit, it will always response 0 uint64_t GetMaxOutboundTimeLeftInCycle(); uint64_t GetTotalBytesRecv(); uint64_t GetTotalBytesSent(); void SetBestHeight(int height); int GetBestHeight() const; /** Get a unique deterministic randomizer. */ CSipHasher GetDeterministicRandomizer(uint64_t id) const; unsigned int GetReceiveFloodSize() const; void WakeMessageHandler(); private: struct ListenSocket { SOCKET socket; bool whitelisted; ListenSocket(SOCKET socket_, bool whitelisted_) : socket(socket_), whitelisted(whitelisted_) {} }; bool BindListenPort(const CService &bindAddr, std::string &strError, bool fWhitelisted = false); bool Bind(const CService &addr, unsigned int flags); bool InitBinds(const std::vector &binds, const std::vector &whiteBinds); void ThreadOpenAddedConnections(); void AddOneShot(const std::string &strDest); void ProcessOneShot(); void ThreadOpenConnections(std::vector connect); void ThreadMessageHandler(); void AcceptConnection(const ListenSocket &hListenSocket); void ThreadSocketHandler(); void ThreadDNSAddressSeed(); uint64_t CalculateKeyedNetGroup(const CAddress &ad) const; CNode *FindNode(const CNetAddr &ip); CNode *FindNode(const CSubNet &subNet); CNode *FindNode(const std::string &addrName); CNode *FindNode(const CService &addr); bool AttemptToEvictConnection(); CNode *ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure); bool IsWhitelistedRange(const CNetAddr &addr); void DeleteNode(CNode *pnode); NodeId GetNewNodeId(); size_t SocketSendData(CNode *pnode) const; //! check is the banlist has unwritten changes bool BannedSetIsDirty(); //! set the "dirty" flag for the banlist void SetBannedSetDirty(bool dirty = true); //! clean unused entries (if bantime has expired) void SweepBanned(); void DumpAddresses(); void DumpData(); void DumpBanlist(); // Network stats void RecordBytesRecv(uint64_t bytes); void RecordBytesSent(uint64_t bytes); // Whether the node should be passed out in ForEach* callbacks static bool NodeFullyConnected(const CNode *pnode); const Config *config; // Network usage totals CCriticalSection cs_totalBytesRecv; CCriticalSection cs_totalBytesSent; uint64_t nTotalBytesRecv; uint64_t nTotalBytesSent; // outbound limit & stats uint64_t nMaxOutboundTotalBytesSentInCycle; uint64_t nMaxOutboundCycleStartTime; uint64_t nMaxOutboundLimit; uint64_t nMaxOutboundTimeframe; // Whitelisted ranges. Any node connecting from these is automatically // whitelisted (as well as those connecting to whitelisted binds). std::vector vWhitelistedRange; unsigned int nSendBufferMaxSize; unsigned int nReceiveFloodSize; std::vector vhListenSocket; std::atomic fNetworkActive; banmap_t setBanned; CCriticalSection cs_setBanned; bool setBannedIsDirty; bool fAddressesInitialized; CAddrMan addrman; std::deque vOneShots; CCriticalSection cs_vOneShots; std::vector vAddedNodes; CCriticalSection cs_vAddedNodes; std::vector vNodes; std::list vNodesDisconnected; mutable CCriticalSection cs_vNodes; std::atomic nLastNodeId; /** Services this instance offers */ ServiceFlags nLocalServices; std::unique_ptr semOutbound; std::unique_ptr semAddnode; int nMaxConnections; int nMaxOutbound; int nMaxAddnode; int nMaxFeeler; std::atomic nBestHeight; CClientUIInterface *clientInterface; NetEventsInterface *m_msgproc; /** SipHasher seeds for deterministic randomness */ const uint64_t nSeed0, nSeed1; /** flag for waking the message processor. */ bool fMsgProcWake; std::condition_variable condMsgProc; CWaitableCriticalSection mutexMsgProc; std::atomic flagInterruptMsgProc; CThreadInterrupt interruptNet; std::thread threadDNSAddressSeed; std::thread threadSocketHandler; std::thread threadOpenAddedConnections; std::thread threadOpenConnections; std::thread threadMessageHandler; /** * Flag for deciding to connect to an extra outbound peer, in excess of * nMaxOutbound. * This takes the place of a feeler connection. */ std::atomic_bool m_try_another_outbound_peer; friend struct CConnmanTest; }; + extern std::unique_ptr g_connman; void Discover(); void StartMapPort(); void InterruptMapPort(); void StopMapPort(); unsigned short GetListenPort(); bool BindListenPort(const CService &bindAddr, std::string &strError, bool fWhitelisted = false); /** * Interface for message handling */ class NetEventsInterface { public: virtual bool ProcessMessages(const Config &config, CNode *pnode, std::atomic &interrupt) = 0; virtual bool SendMessages(const Config &config, CNode *pnode, std::atomic &interrupt) = 0; virtual void InitializeNode(const Config &config, CNode *pnode) = 0; virtual void FinalizeNode(const Config &config, NodeId id, bool &update_connection_time) = 0; protected: /** * Protected destructor so that instances can only be deleted by derived * classes. If that restriction is no longer desired, this should be made * public and virtual. */ ~NetEventsInterface() = default; }; enum { // unknown LOCAL_NONE, // address a local interface listens on LOCAL_IF, // address explicit bound to LOCAL_BIND, // address reported by UPnP LOCAL_UPNP, // address explicitly specified (-externalip=) LOCAL_MANUAL, LOCAL_MAX }; bool IsPeerAddrLocalGood(CNode *pnode); void AdvertiseLocal(CNode *pnode); void SetLimited(enum Network net, bool fLimited = true); bool IsLimited(enum Network net); bool IsLimited(const CNetAddr &addr); bool AddLocal(const CService &addr, int nScore = LOCAL_NONE); bool AddLocal(const CNetAddr &addr, int nScore = LOCAL_NONE); void RemoveLocal(const CService &addr); bool SeenLocal(const CService &addr); bool IsLocal(const CService &addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr); bool IsReachable(enum Network net); bool IsReachable(const CNetAddr &addr); CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices); extern bool fDiscover; extern bool fListen; extern bool fRelayTxes; extern limitedmap mapAlreadyAskedFor; struct LocalServiceInfo { int nScore; int nPort; }; extern CCriticalSection cs_mapLocalHost; extern std::map mapLocalHost; // Command, total bytes typedef std::map mapMsgCmdSize; class CNodeStats { public: NodeId nodeid; ServiceFlags nServices; bool fRelayTxes; int64_t nLastSend; int64_t nLastRecv; int64_t nTimeConnected; int64_t nTimeOffset; std::string addrName; int nVersion; std::string cleanSubVer; bool fInbound; bool m_manual_connection; int nStartingHeight; uint64_t nSendBytes; mapMsgCmdSize mapSendBytesPerMsgCmd; uint64_t nRecvBytes; mapMsgCmdSize mapRecvBytesPerMsgCmd; bool fWhitelisted; double dPingTime; double dPingWait; double dMinPing; // Our address, as reported by the peer std::string addrLocal; // Address of this peer CAddress addr; // Bind address of our side of the connection CAddress addrBind; }; class CNetMessage { private: mutable CHash256 hasher; mutable uint256 data_hash; public: // Parsing header (false) or data (true) bool in_data; // Partially received header. CDataStream hdrbuf; // Complete header. CMessageHeader hdr; uint32_t nHdrPos; // Received message data. CDataStream vRecv; uint32_t nDataPos; // Time (in microseconds) of message receipt. int64_t nTime; CNetMessage(const CMessageHeader::MessageMagic &pchMessageStartIn, int nTypeIn, int nVersionIn) : hdrbuf(nTypeIn, nVersionIn), hdr(pchMessageStartIn), vRecv(nTypeIn, nVersionIn) { hdrbuf.resize(24); in_data = false; nHdrPos = 0; nDataPos = 0; nTime = 0; } bool complete() const { if (!in_data) { return false; } return (hdr.nMessageSize == nDataPos); } const uint256 &GetMessageHash() const; void SetVersion(int nVersionIn) { hdrbuf.SetVersion(nVersionIn); vRecv.SetVersion(nVersionIn); } int readHeader(const Config &config, const char *pch, uint32_t nBytes); int readData(const char *pch, uint32_t nBytes); }; /** Information about a peer */ class CNode { friend class CConnman; public: // socket std::atomic nServices; SOCKET hSocket; // Total size of all vSendMsg entries. size_t nSendSize; // Offset inside the first vSendMsg already sent. size_t nSendOffset; uint64_t nSendBytes; std::deque> vSendMsg; CCriticalSection cs_vSend; CCriticalSection cs_hSocket; CCriticalSection cs_vRecv; CCriticalSection cs_vProcessMsg; std::list vProcessMsg; size_t nProcessQueueSize; CCriticalSection cs_sendProcessing; std::deque vRecvGetData; uint64_t nRecvBytes; std::atomic nRecvVersion; std::atomic nLastSend; std::atomic nLastRecv; const int64_t nTimeConnected; std::atomic nTimeOffset; // Address of this peer const CAddress addr; // Bind address of our side of the connection const CAddress addrBind; std::atomic nVersion; // strSubVer is whatever byte array we read from the wire. However, this // field is intended to be printed out, displayed to humans in various forms // and so on. So we sanitize it and store the sanitized version in // cleanSubVer. The original should be used when dealing with the network or // wire types and the cleaned string used when displayed or logged. std::string strSubVer, cleanSubVer; // Used for both cleanSubVer and strSubVer. CCriticalSection cs_SubVer; // This peer can bypass DoS banning. bool fWhitelisted; // If true this node is being used as a short lived feeler. bool fFeeler; bool fOneShot; bool m_manual_connection; bool fClient; const bool fInbound; std::atomic_bool fSuccessfullyConnected; std::atomic_bool fDisconnect; // We use fRelayTxes for two purposes - // a) it allows us to not relay tx invs before receiving the peer's version // message. // b) the peer may tell us in its version message that we should not relay // tx invs unless it loads a bloom filter. // protected by cs_filter bool fRelayTxes; bool fSentAddr; CSemaphoreGrant grantOutbound; CCriticalSection cs_filter; std::unique_ptr pfilter; std::atomic nRefCount; const uint64_t nKeyedNetGroup; std::atomic_bool fPauseRecv; std::atomic_bool fPauseSend; protected: mapMsgCmdSize mapSendBytesPerMsgCmd; mapMsgCmdSize mapRecvBytesPerMsgCmd; public: uint256 hashContinue; std::atomic nStartingHeight; // flood relay std::vector vAddrToSend; CRollingBloomFilter addrKnown; bool fGetAddr; std::set setKnown; int64_t nNextAddrSend; int64_t nNextLocalAddrSend; // Inventory based relay. CRollingBloomFilter filterInventoryKnown; // Set of transaction ids we still have to announce. They are sorted by the // mempool before relay, so the order is not important. std::set setInventoryTxToSend; // List of block ids we still have announce. There is no final sorting // before sending, as they are always sent immediately and in the order // requested. std::vector vInventoryBlockToSend; CCriticalSection cs_inventory; std::set setAskFor; std::multimap mapAskFor; int64_t nNextInvSend; // Used for headers announcements - unfiltered blocks to relay. Also // protected by cs_inventory. std::vector vBlockHashesToAnnounce; // Used for BIP35 mempool sending, also protected by cs_inventory. bool fSendMempool; // Last time a "MEMPOOL" request was serviced. std::atomic timeLastMempoolReq; // Block and TXN accept times std::atomic nLastBlockTime; std::atomic nLastTXTime; // Ping time measurement: // The pong reply we're expecting, or 0 if no pong expected. std::atomic nPingNonceSent; // Time (in usec) the last ping was sent, or 0 if no ping was ever sent. std::atomic nPingUsecStart; // Last measured round-trip time. std::atomic nPingUsecTime; // Best measured round-trip time. std::atomic nMinPingUsecTime; // Whether a ping is requested. std::atomic fPingQueued; // Minimum fee rate with which to filter inv's to this node Amount minFeeFilter; CCriticalSection cs_feeFilter; Amount lastSentFeeFilter; int64_t nextSendTimeFeeFilter; CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false); ~CNode(); private: CNode(const CNode &); void operator=(const CNode &); const NodeId id; const uint64_t nLocalHostNonce; // Services offered to this peer const ServiceFlags nLocalServices; const int nMyStartingHeight; int nSendVersion; // Used only by SocketHandler thread. std::list vRecvMsg; mutable CCriticalSection cs_addrName; std::string addrName; // Our address, as reported by the peer CService addrLocal; mutable CCriticalSection cs_addrLocal; public: NodeId GetId() const { return id; } uint64_t GetLocalNonce() const { return nLocalHostNonce; } int GetMyStartingHeight() const { return nMyStartingHeight; } int GetRefCount() const { assert(nRefCount >= 0); return nRefCount; } bool ReceiveMsgBytes(const Config &config, const char *pch, uint32_t nBytes, bool &complete); void SetRecvVersion(int nVersionIn) { nRecvVersion = nVersionIn; } int GetRecvVersion() const { return nRecvVersion; } void SetSendVersion(int nVersionIn); int GetSendVersion() const; CService GetAddrLocal() const; //! May not be called more than once void SetAddrLocal(const CService &addrLocalIn); CNode *AddRef() { nRefCount++; return this; } void Release() { nRefCount--; } void AddAddressKnown(const CAddress &_addr) { addrKnown.insert(_addr.GetKey()); } void PushAddress(const CAddress &_addr, FastRandomContext &insecure_rand) { // Known checking here is only to save space from duplicates. // SendMessages will filter it again for knowns that were added // after addresses were pushed. if (_addr.IsValid() && !addrKnown.contains(_addr.GetKey())) { if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) { vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] = _addr; } else { vAddrToSend.push_back(_addr); } } } void AddInventoryKnown(const CInv &inv) { LOCK(cs_inventory); filterInventoryKnown.insert(inv.hash); } void PushInventory(const CInv &inv) { LOCK(cs_inventory); if (inv.type == MSG_TX) { if (!filterInventoryKnown.contains(inv.hash)) { setInventoryTxToSend.insert(inv.hash); } } else if (inv.type == MSG_BLOCK) { vInventoryBlockToSend.push_back(inv.hash); } } void PushBlockHash(const uint256 &hash) { LOCK(cs_inventory); vBlockHashesToAnnounce.push_back(hash); } void AskFor(const CInv &inv); void CloseSocketDisconnect(); void copyStats(CNodeStats &stats); ServiceFlags GetLocalServices() const { return nLocalServices; } std::string GetAddrName() const; //! Sets the addrName only if it was not previously set void MaybeSetAddrName(const std::string &addrNameIn); }; /** * Return a timestamp in the future (in microseconds) for exponentially * distributed events. */ int64_t PoissonNextSend(int64_t nNow, int average_interval_seconds); std::string getSubVersionEB(uint64_t MaxBlockSize); std::string userAgent(const Config &config); #endif // BITCOIN_NET_H diff --git a/src/protocol.cpp b/src/protocol.cpp index 1268b78b89..00b40fdf8c 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -1,223 +1,225 @@ // 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 "protocol.h" #include "chainparams.h" #include "config.h" #include "util.h" #include "utilstrencodings.h" #ifndef WIN32 #include #endif namespace NetMsgType { const char *VERSION = "version"; const char *VERACK = "verack"; const char *ADDR = "addr"; const char *INV = "inv"; const char *GETDATA = "getdata"; const char *MERKLEBLOCK = "merkleblock"; const char *GETBLOCKS = "getblocks"; const char *GETHEADERS = "getheaders"; const char *TX = "tx"; const char *HEADERS = "headers"; const char *BLOCK = "block"; const char *GETADDR = "getaddr"; const char *MEMPOOL = "mempool"; const char *PING = "ping"; const char *PONG = "pong"; const char *NOTFOUND = "notfound"; const char *FILTERLOAD = "filterload"; const char *FILTERADD = "filteradd"; const char *FILTERCLEAR = "filterclear"; const char *REJECT = "reject"; const char *SENDHEADERS = "sendheaders"; const char *FEEFILTER = "feefilter"; const char *SENDCMPCT = "sendcmpct"; const char *CMPCTBLOCK = "cmpctblock"; const char *GETBLOCKTXN = "getblocktxn"; const char *BLOCKTXN = "blocktxn"; +const char *AVAPOLL = "avapoll"; +const char *AVARESPONSE = "avaresponse"; bool IsBlockLike(const std::string &strCommand) { return strCommand == NetMsgType::BLOCK || strCommand == NetMsgType::CMPCTBLOCK || strCommand == NetMsgType::BLOCKTXN; } }; // namespace NetMsgType /** * All known message types. Keep this in the same order as the list of messages * above and in protocol.h. */ static const std::string allNetMessageTypes[] = { NetMsgType::VERSION, NetMsgType::VERACK, NetMsgType::ADDR, NetMsgType::INV, NetMsgType::GETDATA, NetMsgType::MERKLEBLOCK, NetMsgType::GETBLOCKS, NetMsgType::GETHEADERS, NetMsgType::TX, NetMsgType::HEADERS, NetMsgType::BLOCK, NetMsgType::GETADDR, NetMsgType::MEMPOOL, NetMsgType::PING, NetMsgType::PONG, NetMsgType::NOTFOUND, NetMsgType::FILTERLOAD, NetMsgType::FILTERADD, NetMsgType::FILTERCLEAR, NetMsgType::REJECT, NetMsgType::SENDHEADERS, NetMsgType::FEEFILTER, NetMsgType::SENDCMPCT, NetMsgType::CMPCTBLOCK, NetMsgType::GETBLOCKTXN, NetMsgType::BLOCKTXN, }; static const std::vector allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes + ARRAYLEN(allNetMessageTypes)); CMessageHeader::CMessageHeader(const MessageMagic &pchMessageStartIn) { memcpy(std::begin(pchMessageStart), std::begin(pchMessageStartIn), MESSAGE_START_SIZE); memset(pchCommand.data(), 0, sizeof(pchCommand)); nMessageSize = -1; memset(pchChecksum, 0, CHECKSUM_SIZE); } CMessageHeader::CMessageHeader(const MessageMagic &pchMessageStartIn, const char *pszCommand, unsigned int nMessageSizeIn) { memcpy(std::begin(pchMessageStart), std::begin(pchMessageStartIn), MESSAGE_START_SIZE); memset(pchCommand.data(), 0, sizeof(pchCommand)); strncpy(pchCommand.data(), pszCommand, COMMAND_SIZE); nMessageSize = nMessageSizeIn; memset(pchChecksum, 0, CHECKSUM_SIZE); } std::string CMessageHeader::GetCommand() const { // return std::string(pchCommand.begin(), pchCommand.end()); return std::string(pchCommand.data(), pchCommand.data() + strnlen(pchCommand.data(), COMMAND_SIZE)); } static bool CheckHeaderMagicAndCommand(const CMessageHeader &header, const CMessageHeader::MessageMagic &magic) { // Check start string if (memcmp(std::begin(header.pchMessageStart), std::begin(magic), CMessageHeader::MESSAGE_START_SIZE) != 0) { return false; } // Check the command string for errors for (const char *p1 = header.pchCommand.data(); p1 < header.pchCommand.data() + CMessageHeader::COMMAND_SIZE; p1++) { if (*p1 == 0) { // Must be all zeros after the first zero for (; p1 < header.pchCommand.data() + CMessageHeader::COMMAND_SIZE; p1++) { if (*p1 != 0) { return false; } } } else if (*p1 < ' ' || *p1 > 0x7E) { return false; } } return true; } bool CMessageHeader::IsValid(const Config &config) const { // Check start string if (!CheckHeaderMagicAndCommand(*this, config.GetChainParams().NetMagic())) { return false; } // Message size if (IsOversized(config)) { LogPrintf("CMessageHeader::IsValid(): (%s, %u bytes) is oversized\n", GetCommand(), nMessageSize); return false; } return true; } /** * This is a transition method in order to stay compatible with older code that * do not use the config. It assumes message will not get too large. This cannot * be used for any piece of code that will download blocks as blocks may be * bigger than the permitted size. Idealy, code that uses this function should * be migrated toward using the config. */ bool CMessageHeader::IsValidWithoutConfig(const MessageMagic &magic) const { // Check start string if (!CheckHeaderMagicAndCommand(*this, magic)) { return false; } // Message size if (nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) { LogPrintf( "CMessageHeader::IsValidForSeeder(): (%s, %u bytes) is oversized\n", GetCommand(), nMessageSize); return false; } return true; } bool CMessageHeader::IsOversized(const Config &config) const { // If the message doesn't not contain a block content, check against // MAX_PROTOCOL_MESSAGE_LENGTH. if (nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH && !NetMsgType::IsBlockLike(GetCommand())) { return true; } // Scale the maximum accepted size with the block size. if (nMessageSize > 2 * config.GetMaxBlockSize()) { return true; } return false; } CAddress::CAddress() : CService() { Init(); } CAddress::CAddress(CService ipIn, ServiceFlags nServicesIn) : CService(ipIn) { Init(); nServices = nServicesIn; } void CAddress::Init() { nServices = NODE_NONE; nTime = 100000000; } std::string CInv::GetCommand() const { std::string cmd; switch (GetKind()) { case MSG_TX: return cmd.append(NetMsgType::TX); case MSG_BLOCK: return cmd.append(NetMsgType::BLOCK); case MSG_FILTERED_BLOCK: return cmd.append(NetMsgType::MERKLEBLOCK); case MSG_CMPCT_BLOCK: return cmd.append(NetMsgType::CMPCTBLOCK); default: throw std::out_of_range( strprintf("CInv::GetCommand(): type=%d unknown type", type)); } } std::string CInv::ToString() const { try { return strprintf("%s %s", GetCommand(), hash.ToString()); } catch (const std::out_of_range &) { return strprintf("0x%08x %s", type, hash.ToString()); } } const std::vector &getAllNetMessageTypes() { return allNetMessageTypesVec; } diff --git a/src/protocol.h b/src/protocol.h index 1fec0244b3..ad2dfc2f57 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -1,444 +1,458 @@ // 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. #ifndef __cplusplus #error This header can only be compiled as C++. #endif #ifndef BITCOIN_PROTOCOL_H #define BITCOIN_PROTOCOL_H #include "netaddress.h" #include "serialize.h" #include "uint256.h" #include "version.h" #include #include #include class Config; /** * Maximum length of incoming protocol messages (Currently 2MB). * NB: Messages propagating block content are not subject to this limit. */ static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 2 * 1024 * 1024; /** * Message header. * (4) message start. * (12) command. * (4) size. * (4) checksum. */ class CMessageHeader { public: enum { MESSAGE_START_SIZE = 4, COMMAND_SIZE = 12, MESSAGE_SIZE_SIZE = 4, CHECKSUM_SIZE = 4, MESSAGE_SIZE_OFFSET = MESSAGE_START_SIZE + COMMAND_SIZE, CHECKSUM_OFFSET = MESSAGE_SIZE_OFFSET + MESSAGE_SIZE_SIZE, HEADER_SIZE = MESSAGE_START_SIZE + COMMAND_SIZE + MESSAGE_SIZE_SIZE + CHECKSUM_SIZE }; typedef std::array MessageMagic; CMessageHeader(const MessageMagic &pchMessageStartIn); CMessageHeader(const MessageMagic &pchMessageStartIn, const char *pszCommand, unsigned int nMessageSizeIn); std::string GetCommand() const; bool IsValid(const Config &config) const; bool IsValidWithoutConfig(const MessageMagic &magic) const; bool IsOversized(const Config &config) const; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(FLATDATA(pchMessageStart)); READWRITE(FLATDATA(pchCommand)); READWRITE(nMessageSize); READWRITE(FLATDATA(pchChecksum)); } MessageMagic pchMessageStart; std::array pchCommand; uint32_t nMessageSize; uint8_t pchChecksum[CHECKSUM_SIZE]; }; /** * Bitcoin protocol message types. When adding new message types, don't forget * to update allNetMessageTypes in protocol.cpp. */ namespace NetMsgType { /** * The version message provides information about the transmitting node to the * receiving node at the beginning of a connection. * @see https://bitcoin.org/en/developer-reference#version */ extern const char *VERSION; /** * The verack message acknowledges a previously-received version message, * informing the connecting node that it can begin to send other messages. * @see https://bitcoin.org/en/developer-reference#verack */ extern const char *VERACK; /** * The addr (IP address) message relays connection information for peers on the * network. * @see https://bitcoin.org/en/developer-reference#addr */ extern const char *ADDR; /** * The inv message (inventory message) transmits one or more inventories of * objects known to the transmitting peer. * @see https://bitcoin.org/en/developer-reference#inv */ extern const char *INV; /** * The getdata message requests one or more data objects from another node. * @see https://bitcoin.org/en/developer-reference#getdata */ extern const char *GETDATA; /** * The merkleblock message is a reply to a getdata message which requested a * block using the inventory type MSG_MERKLEBLOCK. * @since protocol version 70001 as described by BIP37. * @see https://bitcoin.org/en/developer-reference#merkleblock */ extern const char *MERKLEBLOCK; /** * The getblocks message requests an inv message that provides block header * hashes starting from a particular point in the block chain. * @see https://bitcoin.org/en/developer-reference#getblocks */ extern const char *GETBLOCKS; /** * The getheaders message requests a headers message that provides block * headers starting from a particular point in the block chain. * @since protocol version 31800. * @see https://bitcoin.org/en/developer-reference#getheaders */ extern const char *GETHEADERS; /** * The tx message transmits a single transaction. * @see https://bitcoin.org/en/developer-reference#tx */ extern const char *TX; /** * The headers message sends one or more block headers to a node which * previously requested certain headers with a getheaders message. * @since protocol version 31800. * @see https://bitcoin.org/en/developer-reference#headers */ extern const char *HEADERS; /** * The block message transmits a single serialized block. * @see https://bitcoin.org/en/developer-reference#block */ extern const char *BLOCK; /** * The getaddr message requests an addr message from the receiving node, * preferably one with lots of IP addresses of other receiving nodes. * @see https://bitcoin.org/en/developer-reference#getaddr */ extern const char *GETADDR; /** * The mempool message requests the TXIDs of transactions that the receiving * node has verified as valid but which have not yet appeared in a block. * @since protocol version 60002. * @see https://bitcoin.org/en/developer-reference#mempool */ extern const char *MEMPOOL; /** * The ping message is sent periodically to help confirm that the receiving * peer is still connected. * @see https://bitcoin.org/en/developer-reference#ping */ extern const char *PING; /** * The pong message replies to a ping message, proving to the pinging node that * the ponging node is still alive. * @since protocol version 60001 as described by BIP31. * @see https://bitcoin.org/en/developer-reference#pong */ extern const char *PONG; /** * The notfound message is a reply to a getdata message which requested an * object the receiving node does not have available for relay. * @ince protocol version 70001. * @see https://bitcoin.org/en/developer-reference#notfound */ extern const char *NOTFOUND; /** * The filterload message tells the receiving peer to filter all relayed * transactions and requested merkle blocks through the provided filter. * @since protocol version 70001 as described by BIP37. * Only available with service bit NODE_BLOOM since protocol version * 70011 as described by BIP111. * @see https://bitcoin.org/en/developer-reference#filterload */ extern const char *FILTERLOAD; /** * The filteradd message tells the receiving peer to add a single element to a * previously-set bloom filter, such as a new public key. * @since protocol version 70001 as described by BIP37. * Only available with service bit NODE_BLOOM since protocol version * 70011 as described by BIP111. * @see https://bitcoin.org/en/developer-reference#filteradd */ extern const char *FILTERADD; /** * The filterclear message tells the receiving peer to remove a previously-set * bloom filter. * @since protocol version 70001 as described by BIP37. * Only available with service bit NODE_BLOOM since protocol version * 70011 as described by BIP111. * @see https://bitcoin.org/en/developer-reference#filterclear */ extern const char *FILTERCLEAR; /** * The reject message informs the receiving node that one of its previous * messages has been rejected. * @since protocol version 70002 as described by BIP61. * @see https://bitcoin.org/en/developer-reference#reject */ extern const char *REJECT; /** * Indicates that a node prefers to receive new block announcements via a * "headers" message rather than an "inv". * @since protocol version 70012 as described by BIP130. * @see https://bitcoin.org/en/developer-reference#sendheaders */ extern const char *SENDHEADERS; /** * The feefilter message tells the receiving peer not to inv us any txs * which do not meet the specified min fee rate. * @since protocol version 70013 as described by BIP133 */ extern const char *FEEFILTER; /** * Contains a 1-byte bool and 8-byte LE version number. * Indicates that a node is willing to provide blocks via "cmpctblock" messages. * May indicate that a node prefers to receive new block announcements via a * "cmpctblock" message rather than an "inv", depending on message contents. * @since protocol version 70014 as described by BIP 152 */ extern const char *SENDCMPCT; /** * Contains a CBlockHeaderAndShortTxIDs object - providing a header and * list of "short txids". * @since protocol version 70014 as described by BIP 152 */ extern const char *CMPCTBLOCK; /** * Contains a BlockTransactionsRequest * Peer should respond with "blocktxn" message. * @since protocol version 70014 as described by BIP 152 */ extern const char *GETBLOCKTXN; /** * Contains a BlockTransactions. * Sent in response to a "getblocktxn" message. * @since protocol version 70014 as described by BIP 152 */ extern const char *BLOCKTXN; +/** + * Contains an AvalanchePoll. + * Peer should respond with "avaresponse" message. + */ +extern const char *AVAPOLL; +/** + * Contains an AvalancheResponse. + * Sent in response to a "avapoll" message. + */ +extern const char *AVARESPONSE; /** * Indicate if the message is used to transmit the content of a block. * These messages can be significantly larger than usual messages and therefore * may need to be processed differently. */ bool IsBlockLike(const std::string &strCommand); }; // namespace NetMsgType /* Get a vector of all valid message types (see above) */ const std::vector &getAllNetMessageTypes(); /** * nServices flags. */ enum ServiceFlags : uint64_t { // Nothing NODE_NONE = 0, // NODE_NETWORK means that the node is capable of serving the block chain. // It is currently set by all Bitcoin ABC nodes, and is unset by SPV clients // or other peers that just want network services but don't provide them. NODE_NETWORK = (1 << 0), // NODE_GETUTXO means the node is capable of responding to the getutxo // protocol request. Bitcoin ABC does not support this but a patch set // called Bitcoin XT does. See BIP 64 for details on how this is // implemented. NODE_GETUTXO = (1 << 1), // NODE_BLOOM means the node is capable and willing to handle bloom-filtered // connections. Bitcoin ABC nodes used to support this by default, without // advertising this bit, but no longer do as of protocol version 70011 (= // NO_BLOOM_VERSION) NODE_BLOOM = (1 << 2), // NODE_XTHIN means the node supports Xtreme Thinblocks. If this is turned // off then the node will not service nor make xthin requests. NODE_XTHIN = (1 << 4), // NODE_BITCOIN_CASH means the node supports Bitcoin Cash and the // associated consensus rule changes. // This service bit is intended to be used prior until some time after the // UAHF activation when the Bitcoin Cash network has adequately separated. // TODO: remove (free up) the NODE_BITCOIN_CASH service bit once no longer // needed. NODE_BITCOIN_CASH = (1 << 5), // Bits 24-31 are reserved for temporary experiments. Just pick a bit that // isn't getting used, or one not being used much, and notify the // bitcoin-development mailing list. Remember that service bits are just // unauthenticated advertisements, so your code must be robust against // collisions and other cases where nodes may be advertising a service they // do not actually support. Other service bits should be allocated via the // BIP process. + + // NODE_AVALANCHE means the node supports Bitcoin Cash's avalanche + // preconsensus mechanism. + NODE_AVALANCHE = (1 << 24), }; /** * Gets the set of service flags which are "desirable" for a given peer. * * These are the flags which are required for a peer to support for them * to be "interesting" to us, ie for us to wish to use one of our few * outbound connection slots for or for us to wish to prioritize keeping * their connection around. * * Relevant service flags may be peer- and state-specific in that the * version of the peer may determine which flags are required (eg in the * case of NODE_NETWORK_LIMITED where we seek out NODE_NETWORK peers * unless they set NODE_NETWORK_LIMITED and we are out of IBD, in which * case NODE_NETWORK_LIMITED suffices). * * Thus, generally, avoid calling with peerServices == NODE_NONE, unless * state-specific flags must absolutely be avoided. When called with * peerServices == NODE_NONE, the returned desirable service flags are * guaranteed to not change dependant on state - ie they are suitable for * use when describing peers which we know to be desirable, but for which * we do not have a confirmed set of service flags. * * If the NODE_NONE return value is changed, contrib/seeds/makeseeds.py * should be updated appropriately to filter for the same nodes. */ static ServiceFlags GetDesirableServiceFlags(ServiceFlags services) { return ServiceFlags(NODE_NETWORK); } /** * A shortcut for (services & GetDesirableServiceFlags(services)) * == GetDesirableServiceFlags(services), ie determines whether the given * set of service flags are sufficient for a peer to be "relevant". */ static inline bool HasAllDesirableServiceFlags(ServiceFlags services) { return !(GetDesirableServiceFlags(services) & (~services)); } /** * Checks if a peer with the given service flags may be capable of having a * robust address-storage DB. Currently an alias for checking NODE_NETWORK. */ static inline bool MayHaveUsefulAddressDB(ServiceFlags services) { return services & NODE_NETWORK; } /** * A CService with information about it as peer. */ class CAddress : public CService { public: CAddress(); explicit CAddress(CService ipIn, ServiceFlags nServicesIn); void Init(); ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { if (ser_action.ForRead()) Init(); int nVersion = s.GetVersion(); if (s.GetType() & SER_DISK) READWRITE(nVersion); if ((s.GetType() & SER_DISK) || (nVersion >= CADDR_TIME_VERSION && !(s.GetType() & SER_GETHASH))) READWRITE(nTime); uint64_t nServicesInt = nServices; READWRITE(nServicesInt); nServices = (ServiceFlags)nServicesInt; READWRITE(*(CService *)this); } // TODO: make private (improves encapsulation) public: ServiceFlags nServices; // disk and network only unsigned int nTime; }; /** getdata message type flags */ const uint32_t MSG_TYPE_MASK = 0xffffffff >> 3; /** getdata / inv message types. * These numbers are defined by the protocol. When adding a new value, be sure * to mention it in the respective BIP. */ enum GetDataMsg { UNDEFINED = 0, MSG_TX = 1, MSG_BLOCK = 2, // The following can only occur in getdata. Invs always use TX or BLOCK. //!< Defined in BIP37 MSG_FILTERED_BLOCK = 3, //!< Defined in BIP152 MSG_CMPCT_BLOCK = 4, }; /** inv message data */ class CInv { public: // TODO: make private (improves encapsulation) uint32_t type; uint256 hash; public: CInv() : type(0), hash() {} CInv(uint32_t typeIn, const uint256 &hashIn) : type(typeIn), hash(hashIn) {} ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(type); READWRITE(hash); } friend bool operator<(const CInv &a, const CInv &b) { return a.type < b.type || (a.type == b.type && a.hash < b.hash); } std::string GetCommand() const; std::string ToString() const; uint32_t GetKind() const { return type & MSG_TYPE_MASK; } bool IsTx() const { auto k = GetKind(); return k == MSG_TX; } bool IsSomeBlock() const { auto k = GetKind(); return k == MSG_BLOCK || k == MSG_FILTERED_BLOCK || k == MSG_CMPCT_BLOCK; } }; #endif // BITCOIN_PROTOCOL_H diff --git a/src/test/avalanche_tests.cpp b/src/test/avalanche_tests.cpp index 291cf0484e..8521086e85 100644 --- a/src/test/avalanche_tests.cpp +++ b/src/test/avalanche_tests.cpp @@ -1,352 +1,552 @@ // Copyright (c) 2010 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "avalanche.h" +#include "config.h" +#include "net_processing.h" // For PeerLogicValidation #include "test/test_bitcoin.h" #include struct AvalancheTest { + static void runEventLoop(AvalancheProcessor &p) { p.runEventLoop(); } + static std::vector getInvsForNextPoll(const AvalancheProcessor &p) { return p.getInvsForNextPoll(); } + + static NodeId getSuitableNodeToQuery(AvalancheProcessor &p) { + return p.getSuitableNodeToQuery(); + } }; BOOST_FIXTURE_TEST_SUITE(avalanche_tests, TestChain100Setup) #define REGISTER_VOTE_AND_CHECK(vr, vote, state, finalized, confidence) \ vr.registerVote(vote); \ BOOST_CHECK_EQUAL(vr.isAccepted(), state); \ BOOST_CHECK_EQUAL(vr.hasFinalized(), finalized); \ BOOST_CHECK_EQUAL(vr.getConfidence(), confidence); BOOST_AUTO_TEST_CASE(vote_record) { VoteRecord vr; // Check initial state. BOOST_CHECK_EQUAL(vr.isAccepted(), false); BOOST_CHECK_EQUAL(vr.hasFinalized(), false); BOOST_CHECK_EQUAL(vr.getConfidence(), 0); // We register one vote for, which keep things at 4/4. REGISTER_VOTE_AND_CHECK(vr, true, false, false, 0); // One more and we are at 5/3. REGISTER_VOTE_AND_CHECK(vr, true, false, false, 0); // One more and we are at 5/3. REGISTER_VOTE_AND_CHECK(vr, true, false, false, 0); // One more and we are at 6/2. REGISTER_VOTE_AND_CHECK(vr, true, false, false, 0); // One more and we are at 6/2. REGISTER_VOTE_AND_CHECK(vr, true, false, false, 0); // Next vote will flip state, and confidence will increase as long as we // vote yes. for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) { REGISTER_VOTE_AND_CHECK(vr, true, true, false, i); } // The next vote will finalize the decision. REGISTER_VOTE_AND_CHECK(vr, false, true, true, AVALANCHE_FINALIZATION_SCORE); // Now that we have two no votes, confidence stop increasing. for (int i = 0; i < 5; i++) { REGISTER_VOTE_AND_CHECK(vr, false, true, true, AVALANCHE_FINALIZATION_SCORE); } // Next vote will flip state, and confidence will increase as long as we // vote no. for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) { REGISTER_VOTE_AND_CHECK(vr, false, false, false, i); } // The next vote will finalize the decision. REGISTER_VOTE_AND_CHECK(vr, true, false, true, AVALANCHE_FINALIZATION_SCORE); } BOOST_AUTO_TEST_CASE(block_update) { CBlockIndex index; CBlockIndex *pindex = &index; std::set status{ AvalancheBlockUpdate::Status::Invalid, AvalancheBlockUpdate::Status::Rejected, AvalancheBlockUpdate::Status::Accepted, AvalancheBlockUpdate::Status::Finalized, }; for (auto s : status) { AvalancheBlockUpdate abu(pindex, s); BOOST_CHECK(abu.getBlockIndex() == pindex); BOOST_CHECK_EQUAL(abu.getStatus(), s); } } +CService ip(uint32_t i) { + struct in_addr s; + s.s_addr = i; + return CService(CNetAddr(s), Params().GetDefaultPort()); +} + +std::unique_ptr ConnectNode(const Config &config, ServiceFlags nServices, + PeerLogicValidation &peerLogic) { + static NodeId id = 0; + + CAddress addr(ip(GetRandInt(0xffffffff)), NODE_NONE); + std::unique_ptr nodeptr(new CNode(id++, ServiceFlags(NODE_NETWORK), + 0, INVALID_SOCKET, addr, 0, 0, + CAddress(), "", + /*fInboundIn=*/false)); + CNode &node = *nodeptr; + node.SetSendVersion(PROTOCOL_VERSION); + node.nServices = nServices; + peerLogic.InitializeNode(config, &node); + node.nVersion = 1; + node.fSuccessfullyConnected = true; + + CConnmanTest::AddNode(node); + return nodeptr; +} + BOOST_AUTO_TEST_CASE(block_register) { - AvalancheProcessor p; + AvalancheProcessor p(g_connman.get()); std::vector updates; CBlock block = CreateAndProcessBlock({}, CScript()); const uint256 blockHash = block.GetHash(); const CBlockIndex *pindex = mapBlockIndex[blockHash]; + const Config &config = GetConfig(); + + // Create a node that supports avalanche. + auto avanode = ConnectNode(config, NODE_AVALANCHE, *peerLogic); + NodeId nodeid = avanode->GetId(); + // Querying for random block returns false. BOOST_CHECK(!p.isAccepted(pindex)); // Add a new block. Check it is added to the polls. BOOST_CHECK(p.addBlockToReconcile(pindex)); auto invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 1); BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); BOOST_CHECK(invs[0].hash == blockHash); // Newly added blocks are also considered rejected. BOOST_CHECK(!p.isAccepted(pindex)); // Let's vote for this block a few times. AvalancheResponse resp{0, {AvalancheVote(0, blockHash)}}; for (int i = 0; i < 5; i++) { - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(nodeid, resp, updates)); BOOST_CHECK(!p.isAccepted(pindex)); BOOST_CHECK_EQUAL(updates.size(), 0); } // Now the state will flip. - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(nodeid, resp, updates)); BOOST_CHECK(p.isAccepted(pindex)); BOOST_CHECK_EQUAL(updates.size(), 1); BOOST_CHECK(updates[0].getBlockIndex() == pindex); BOOST_CHECK_EQUAL(updates[0].getStatus(), AvalancheBlockUpdate::Status::Accepted); updates = {}; // Now it is accepted, but we can vote for it numerous times. for (int i = 1; i < AVALANCHE_FINALIZATION_SCORE; i++) { - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(nodeid, resp, updates)); BOOST_CHECK(p.isAccepted(pindex)); BOOST_CHECK_EQUAL(updates.size(), 0); } // As long as it is not finalized, we poll. invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 1); BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); BOOST_CHECK(invs[0].hash == blockHash); // Now finalize the decision. - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(nodeid, resp, updates)); BOOST_CHECK_EQUAL(updates.size(), 1); BOOST_CHECK(updates[0].getBlockIndex() == pindex); BOOST_CHECK_EQUAL(updates[0].getStatus(), AvalancheBlockUpdate::Status::Finalized); updates = {}; // Once the decision is finalized, there is no poll for it. invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 0); // Now let's undo this and finalize rejection. BOOST_CHECK(p.addBlockToReconcile(pindex)); invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 1); BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); BOOST_CHECK(invs[0].hash == blockHash); // Only 3 here as we don't need to flip state. resp = {0, {AvalancheVote(1, blockHash)}}; for (int i = 0; i < 3; i++) { - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(nodeid, resp, updates)); BOOST_CHECK(!p.isAccepted(pindex)); BOOST_CHECK_EQUAL(updates.size(), 0); } // Now it is rejected, but we can vote for it numerous times. for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) { - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(nodeid, resp, updates)); BOOST_CHECK(!p.isAccepted(pindex)); BOOST_CHECK_EQUAL(updates.size(), 0); } // As long as it is not finalized, we poll. invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 1); BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); BOOST_CHECK(invs[0].hash == blockHash); // Now finalize the decision. - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(nodeid, resp, updates)); BOOST_CHECK(!p.isAccepted(pindex)); BOOST_CHECK_EQUAL(updates.size(), 1); BOOST_CHECK(updates[0].getBlockIndex() == pindex); BOOST_CHECK_EQUAL(updates[0].getStatus(), AvalancheBlockUpdate::Status::Invalid); updates = {}; // Once the decision is finalized, there is no poll for it. invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 0); // Adding the block twice does nothing. BOOST_CHECK(p.addBlockToReconcile(pindex)); BOOST_CHECK(!p.addBlockToReconcile(pindex)); BOOST_CHECK(!p.isAccepted(pindex)); + + CConnmanTest::ClearNodes(); } BOOST_AUTO_TEST_CASE(multi_block_register) { - AvalancheProcessor p; + AvalancheProcessor p(g_connman.get()); CBlockIndex indexA, indexB; std::vector updates; + const Config &config = GetConfig(); + + // Create a node that supports avalanche. + auto node0 = ConnectNode(config, NODE_AVALANCHE, *peerLogic); + auto node1 = ConnectNode(config, NODE_AVALANCHE, *peerLogic); + // Make sure the block has a hash. CBlock blockA = CreateAndProcessBlock({}, CScript()); const uint256 blockHashA = blockA.GetHash(); const CBlockIndex *pindexA = mapBlockIndex[blockHashA]; CBlock blockB = CreateAndProcessBlock({}, CScript()); const uint256 blockHashB = blockB.GetHash(); const CBlockIndex *pindexB = mapBlockIndex[blockHashB]; // Querying for random block returns false. BOOST_CHECK(!p.isAccepted(pindexA)); BOOST_CHECK(!p.isAccepted(pindexB)); // Start voting on block A. BOOST_CHECK(p.addBlockToReconcile(pindexA)); auto invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 1); BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); BOOST_CHECK(invs[0].hash == blockHashA); - AvalancheResponse resp{ - 0, {AvalancheVote(0, blockHashA), AvalancheVote(0, blockHashB)}}; - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(node0->GetId(), + {0, {AvalancheVote(0, blockHashA)}}, updates)); BOOST_CHECK_EQUAL(updates.size(), 0); // Start voting on block B after one vote. + AvalancheResponse resp{ + 0, {AvalancheVote(0, blockHashB), AvalancheVote(0, blockHashA)}}; BOOST_CHECK(p.addBlockToReconcile(pindexB)); invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 2); // Ensure B comes before A because it has accumulated more PoW. BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); BOOST_CHECK(invs[0].hash == blockHashB); BOOST_CHECK_EQUAL(invs[1].type, MSG_BLOCK); BOOST_CHECK(invs[1].hash == blockHashA); - // Let's vote for this block a few times. + // Let's vote for these blocks a few times. for (int i = 0; i < 4; i++) { - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(node0->GetId(), resp, updates)); BOOST_CHECK_EQUAL(updates.size(), 0); } // Now the state will flip for A. - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(node0->GetId(), resp, updates)); BOOST_CHECK_EQUAL(updates.size(), 1); BOOST_CHECK(updates[0].getBlockIndex() == pindexA); BOOST_CHECK_EQUAL(updates[0].getStatus(), AvalancheBlockUpdate::Status::Accepted); updates = {}; // And then for B. - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(node0->GetId(), resp, updates)); BOOST_CHECK_EQUAL(updates.size(), 1); BOOST_CHECK(updates[0].getBlockIndex() == pindexB); BOOST_CHECK_EQUAL(updates[0].getStatus(), AvalancheBlockUpdate::Status::Accepted); updates = {}; // Now it is rejected, but we can vote for it numerous times. for (int i = 2; i < AVALANCHE_FINALIZATION_SCORE; i++) { - p.registerVotes(resp, updates); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(node0->GetId(), resp, updates)); BOOST_CHECK_EQUAL(updates.size(), 0); } + // Running two iterration of the event loop so that vote gets triggerd on A + // and B. + AvalancheTest::runEventLoop(p); + AvalancheTest::runEventLoop(p); + // Next vote will finalize block A. - p.registerVotes(resp, updates); + BOOST_CHECK(p.registerVotes(node1->GetId(), resp, updates)); BOOST_CHECK_EQUAL(updates.size(), 1); BOOST_CHECK(updates[0].getBlockIndex() == pindexA); BOOST_CHECK_EQUAL(updates[0].getStatus(), AvalancheBlockUpdate::Status::Finalized); updates = {}; // We do not vote on A anymore. invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 1); BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); BOOST_CHECK(invs[0].hash == blockHashB); // Next vote will finalize block B. - p.registerVotes(resp, updates); + BOOST_CHECK(p.registerVotes(node0->GetId(), resp, updates)); BOOST_CHECK_EQUAL(updates.size(), 1); BOOST_CHECK(updates[0].getBlockIndex() == pindexB); BOOST_CHECK_EQUAL(updates[0].getStatus(), AvalancheBlockUpdate::Status::Finalized); updates = {}; // There is nothing left to vote on. invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 0); + + CConnmanTest::ClearNodes(); +} + +BOOST_AUTO_TEST_CASE(poll_and_response) { + AvalancheProcessor p(g_connman.get()); + + std::vector updates; + + CBlock block = CreateAndProcessBlock({}, CScript()); + const uint256 blockHash = block.GetHash(); + const CBlockIndex *pindex = mapBlockIndex[blockHash]; + + const Config &config = GetConfig(); + + // There is no node to query. + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), -1); + + // Create a node that supports avalanche and one that doesn't. + auto oldnode = ConnectNode(config, NODE_NONE, *peerLogic); + auto avanode = ConnectNode(config, NODE_AVALANCHE, *peerLogic); + NodeId avanodeid = avanode->GetId(); + + // It returns the avalanche peer. + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); + + // Register a block and check it is added to the list of elements to poll. + BOOST_CHECK(p.addBlockToReconcile(pindex)); + auto invs = AvalancheTest::getInvsForNextPoll(p); + BOOST_CHECK_EQUAL(invs.size(), 1); + BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); + BOOST_CHECK(invs[0].hash == blockHash); + + // Trigger a poll on avanode. + AvalancheTest::runEventLoop(p); + + // There is no more suitable peer available, so return nothing. + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), -1); + + // Respond to the request. + AvalancheResponse resp = {0, {AvalancheVote(0, blockHash)}}; + BOOST_CHECK(p.registerVotes(avanode->GetId(), resp, updates)); + BOOST_CHECK_EQUAL(updates.size(), 0); + + // Now that avanode fullfilled his request, it is added back to the list of + // queriable nodes. + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); + + // Sending a response when not polled fails. + BOOST_CHECK(!p.registerVotes(avanode->GetId(), resp, updates)); + BOOST_CHECK_EQUAL(updates.size(), 0); + + // Trigger a poll on avanode. + AvalancheTest::runEventLoop(p); + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), -1); + + // Sending responses that do not match the request also fails. + // 1. Too many results. + resp = {0, {AvalancheVote(0, blockHash), AvalancheVote(0, blockHash)}}; + AvalancheTest::runEventLoop(p); + BOOST_CHECK(!p.registerVotes(avanode->GetId(), resp, updates)); + BOOST_CHECK_EQUAL(updates.size(), 0); + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); + + // 2. Not enough results. + resp = {0, {}}; + AvalancheTest::runEventLoop(p); + BOOST_CHECK(!p.registerVotes(avanode->GetId(), resp, updates)); + BOOST_CHECK_EQUAL(updates.size(), 0); + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); + + // 3. Do not match the poll. + resp = {0, {AvalancheVote()}}; + AvalancheTest::runEventLoop(p); + BOOST_CHECK(!p.registerVotes(avanode->GetId(), resp, updates)); + BOOST_CHECK_EQUAL(updates.size(), 0); + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); + + // Proper response gets processed and avanode is available again. + resp = {0, {AvalancheVote(0, blockHash)}}; + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(avanode->GetId(), resp, updates)); + BOOST_CHECK_EQUAL(updates.size(), 0); + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); + + // Making request for invalid nodes do not work. + BOOST_CHECK(!p.registerVotes(avanode->GetId() + 1234, resp, updates)); + BOOST_CHECK_EQUAL(updates.size(), 0); + + // Out of order response are rejected. + CBlock block2 = CreateAndProcessBlock({}, CScript()); + const uint256 blockHash2 = block2.GetHash(); + const CBlockIndex *pindex2 = mapBlockIndex[blockHash2]; + BOOST_CHECK(p.addBlockToReconcile(pindex2)); + + resp = {0, {AvalancheVote(0, blockHash), AvalancheVote(0, blockHash2)}}; + AvalancheTest::runEventLoop(p); + BOOST_CHECK(!p.registerVotes(avanode->GetId(), resp, updates)); + BOOST_CHECK_EQUAL(updates.size(), 0); + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); + + CConnmanTest::ClearNodes(); } BOOST_AUTO_TEST_CASE(event_loop) { - AvalancheProcessor p; + AvalancheProcessor p(g_connman.get()); CScheduler s; + CBlock block = CreateAndProcessBlock({}, CScript()); + const uint256 blockHash = block.GetHash(); + const CBlockIndex *pindex = mapBlockIndex[blockHash]; + // Starting the event loop. BOOST_CHECK(p.startEventLoop(s)); // There is one task planned in the next hour (our event loop). boost::chrono::system_clock::time_point start, stop; BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1); // Starting twice doesn't start it twice. BOOST_CHECK(!p.startEventLoop(s)); // Start the scheduler thread. std::thread schedulerThread(std::bind(&CScheduler::serviceQueue, &s)); + // Create a node and a block to query. + const Config &config = GetConfig(); + + // Create a node that supports avalanche. + auto avanode = ConnectNode(config, NODE_AVALANCHE, *peerLogic); + NodeId nodeid = avanode->GetId(); + + // There is no query in flight at the moment. + BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), nodeid); + + // Add a new block. Check it is added to the polls. + BOOST_CHECK(p.addBlockToReconcile(pindex)); + + bool hasQueried = false; + for (int i = 0; i < 1000; i++) { + // Technically, this is a race condition, but this should do just fine + // as we wait up to 1s for an event that should take 10ms. + boost::this_thread::sleep_for(boost::chrono::milliseconds(1)); + if (AvalancheTest::getSuitableNodeToQuery(p) == -1) { + hasQueried = true; + break; + } + } + + BOOST_CHECK(hasQueried); + // Stop event loop. BOOST_CHECK(p.stopEventLoop()); // We don't have any task scheduled anymore. BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0); // Can't stop the event loop twice. BOOST_CHECK(!p.stopEventLoop()); // Wait for the scheduler to stop. s.stop(true); schedulerThread.join(); + + CConnmanTest::ClearNodes(); } BOOST_AUTO_TEST_CASE(destructor) { CScheduler s; boost::chrono::system_clock::time_point start, stop; // Start the scheduler thread. std::thread schedulerThread(std::bind(&CScheduler::serviceQueue, &s)); { - AvalancheProcessor p; + AvalancheProcessor p(g_connman.get()); BOOST_CHECK(p.startEventLoop(s)); BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1); } // Now that avalanche is destroyed, there is no more scheduled tasks. BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0); // Wait for the scheduler to stop. s.stop(true); schedulerThread.join(); } BOOST_AUTO_TEST_SUITE_END()