diff --git a/src/init.cpp b/src/init.cpp index 4b386748040a..ddf34df71ec0 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -516,7 +516,9 @@ void SetupServerArgs() gArgs.AddArg("-rpcuser=", "Username for JSON-RPC connections", false, OptionsCategory::RPC); gArgs.AddArg("-rpcworkqueue=", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), true, OptionsCategory::RPC); gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", false, OptionsCategory::RPC); - + gArgs.AddArg("-headerspamfilter=", strprintf(_("Use header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-headerspamfiltermaxsize=", strprintf(_("Maximum size of the list of indexes in the header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-headerspamfiltermaxavg=", strprintf(_("Maximum average size of an index occurrence in the header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER_MAX_AVG), false, OptionsCategory::NODE_RELAY); #if HAVE_DECL_DAEMON gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS); #else diff --git a/src/net_processing.cpp b/src/net_processing.cpp index a4b0fdd6cc06..83ea3c38f2f8 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -181,6 +181,93 @@ struct CBlockReject { uint256 hashBlock; }; +class CNodeHeaders +{ +public: + CNodeHeaders(): + maxSize(0), + maxAvg(0) + { + maxSize = gArgs.GetArg("-headerspamfiltermaxsize", DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE); + maxAvg = gArgs.GetArg("-headerspamfiltermaxavg", DEFAULT_HEADER_SPAM_FILTER_MAX_AVG); + } + + bool addHeaders(const CBlockIndex *pindexFirst, const CBlockIndex *pindexLast) + { + if(pindexFirst && pindexLast && maxSize && maxAvg) + { + // Get the begin block index + int nBegin = pindexFirst->nHeight; + + // Get the end block index + int nEnd = pindexLast->nHeight; + + for(int point = nBegin; point<= nEnd; point++) + { + addPoint(point); + } + + return true; + } + + return false; + } + + bool updateState(CValidationState& state, bool ret) + { + // No headers + size_t size = points.size(); + if(size == 0) + return ret; + + // Compute the number of the received headers + size_t nHeaders = 0; + for(auto point : points) + { + nHeaders += point.second; + } + + // Compute the average value per height + double nAvgValue = (double)nHeaders / size; + + // Ban the node if try to spam + bool banNode = (nAvgValue >= 1.5 * maxAvg && size >= maxAvg) || + (nAvgValue >= maxAvg && nHeaders >= maxSize) || + (nHeaders >= maxSize * 3); + if(banNode) + { + // Clear the points and ban the node + points.clear(); + return state.DoS(100, false, REJECT_INVALID, "header-spam", false, "ban node for sending spam"); + } + + return ret; + } + +private: + void addPoint(int height) + { + // Erace the last element in the list + if(points.size() == maxSize) + { + points.erase(points.begin()); + } + + // Add the point to the list + int occurrence = 0; + auto mi = points.find(height); + if (mi != points.end()) + occurrence = (*mi).second; + occurrence++; + points[height] = occurrence; + } + +private: + std::map points; + size_t maxSize; + size_t maxAvg; +}; + /** * Maintain validation-specific state about nodes, protected by cs_main, instead * by CNode's own locks. This simplifies asynchronous operation, where @@ -270,6 +357,8 @@ struct CNodeState { ChainSyncTimeoutState m_chain_sync; + CNodeHeaders headers; + //! Time of last new block announcement int64_t m_last_block_announcement; @@ -310,7 +399,22 @@ static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return &it->second; } -static void UpdatePreferredDownload(CNode* node, CNodeState* state) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool ProcessNetBlockHeaders(CNode* pfrom, const std::vector& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr, CBlockHeader* first_invalid = nullptr) +{ + const CBlockIndex* pindexFirst = nullptr; + bool ret = ProcessNewBlockHeaders(block, state, chainparams, ppindex, first_invalid, &pindexFirst); + if(gArgs.GetBoolArg("-headerspamfilter", DEFAULT_HEADER_SPAM_FILTER)) + { + LOCK(cs_main); + CNodeState *nodestate = State(pfrom->GetId()); + const CBlockIndex *pindexLast = ppindex == nullptr ? nullptr : *ppindex; + nodestate->headers.addHeaders(pindexFirst, pindexLast); + return nodestate->headers.updateState(state, ret); + } + return ret; +} + +void UpdatePreferredDownload(CNode* node, CNodeState* state) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { nPreferredDownload -= state->fPreferredDownload; @@ -515,7 +619,7 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec // 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) { + if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork <= chainActive.Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { // This peer has nothing interesting. return; } @@ -1409,7 +1513,7 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve CValidationState state; CBlockHeader first_invalid_header; - if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast, &first_invalid_header)) { + if (!ProcessNetBlockHeaders(pfrom, headers, state, chainparams, &pindexLast, &first_invalid_header)) { int nDoS; if (state.IsInvalid(nDoS)) { LOCK(cs_main); @@ -2411,7 +2515,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr const CBlockIndex *pindex = nullptr; CValidationState state; - if (!ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) { + if (!ProcessNetBlockHeaders(pfrom, {cmpctblock.header}, state, chainparams, &pindex)) { int nDoS; if (state.IsInvalid(nDoS)) { if (nDoS > 0) { diff --git a/src/net_processing.h b/src/net_processing.h index 7caefc80cab5..8252b4dd3ac8 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -9,6 +9,7 @@ #include #include #include +#include /** Default for -maxorphantx, maximum number of orphan transactions kept in memory */ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100; @@ -17,6 +18,13 @@ static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100; /** Default for BIP61 (sending reject messages) */ static constexpr bool DEFAULT_ENABLE_BIP61 = true; +/** Default for -headerspamfilter, use header spam filter */ +static const bool DEFAULT_HEADER_SPAM_FILTER = true; +/** Default for -headerspamfiltermaxsize, maximum size of the list of indexes in the header spam filter */ +static const unsigned int DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE = COINBASE_MATURITY; +/** Default for -headerspamfiltermaxavg, maximum average size of an index occurrence in the header spam filter */ +static const unsigned int DEFAULT_HEADER_SPAM_FILTER_MAX_AVG = 10; + class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface { private: CConnman* const connman; diff --git a/src/validation.cpp b/src/validation.cpp index b6f8d4e0045c..79125249539d 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3581,11 +3581,12 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& } // Exposed wrapper for AcceptBlockHeader -bool ProcessNewBlockHeaders(const std::vector& headers, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex, CBlockHeader *first_invalid) +bool ProcessNewBlockHeaders(const std::vector& headers, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex, CBlockHeader *first_invalid, const CBlockIndex** pindexFirst) { if (first_invalid != nullptr) first_invalid->SetNull(); { LOCK(cs_main); + bool bFirst = true; for (const CBlockHeader& header : headers) { CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast if (!g_chainstate.AcceptBlockHeader(header, state, chainparams, &pindex)) { @@ -3594,6 +3595,11 @@ bool ProcessNewBlockHeaders(const std::vector& headers, CValidatio } if (ppindex) { *ppindex = pindex; + if(bFirst && pindexFirst) + { + *pindexFirst = pindex; + bFirst = false; + } } } } diff --git a/src/validation.h b/src/validation.h index 6dab0a24318e..2d267c0fdb16 100644 --- a/src/validation.h +++ b/src/validation.h @@ -243,7 +243,7 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr, CBlockHeader* first_invalid = nullptr) LOCKS_EXCLUDED(cs_main); +bool ProcessNewBlockHeaders(const std::vector& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr, CBlockHeader* first_invalid = nullptr, const CBlockIndex** pindexFirst = nullptr) LOCKS_EXCLUDED(cs_main); /** Check whether enough disk space is available for an incoming block */ bool CheckDiskSpace(uint64_t nAdditionalBytes = 0, bool blocks_dir = false);