diff --git a/src/init.cpp b/src/init.cpp index 12631c1f0..bcebca0fd 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -474,6 +474,9 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-rpcworkqueue=", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE)); strUsage += HelpMessageOpt("-rpcservertimeout=", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT)); } + strUsage += HelpMessageOpt("-headerspamfilter=", strprintf(_("Use header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER)); + strUsage += HelpMessageOpt("-headerspamfiltermaxsize=", strprintf(_("Maximum size of the list of indexes in the header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE)); + strUsage += HelpMessageOpt("-headerspamfiltermaxavg=", strprintf(_("Maximum average size of an index occurrence in the header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER_MAX_AVG)); #ifdef ENABLE_WALLET strUsage += HelpMessageGroup(_("Staking options:")); diff --git a/src/main.cpp b/src/main.cpp index 3bb531a1b..8c62a055f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +// Header spam protection by Qtum +// Copyright (c) 2016-2019 The Qtum developers +// Copyright (c) 2017-2019 The Navcoin developers + #include "main.h" #include "addrman.h" @@ -267,6 +271,89 @@ struct CBlockReject { uint256 hashBlock; }; +class CNodeHeaders +{ +public: + CNodeHeaders(): + maxSize(0), + maxAvg(0) + { + maxSize = GetArg("-headerspamfiltermaxsize", DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE); + maxAvg = GetArg("-headerspamfiltermaxavg", DEFAULT_HEADER_SPAM_FILTER_MAX_AVG); + } + + bool addHeaders(int nBegin, int nEnd) + { + + if(nBegin > 0 && nEnd > 0 && maxSize && maxAvg) + { + + 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 @@ -316,6 +403,7 @@ struct CNodeState { //! Whether this peer will send us cmpctblocks if we request them bool fProvidesHeaderAndIDs; */ + CNodeHeaders headers; CNodeState() { fCurrentlyConnected = false; @@ -6009,12 +6097,22 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, return true; } + bool ret = true; + bool bFirst = true; + string strError = ""; + + int nFirst = 0; + int nLast = 0; + CBlockIndex *pindexLast = NULL; + BOOST_FOREACH(const CBlockHeader& header, headers) { CValidationState state; if (pindexLast != NULL && header.hashPrevBlock != pindexLast->GetBlockHash()) { Misbehaving(pfrom->GetId(), 20); - return error("non-continuous headers sequence"); + ret = false; + strError = "non-continuous headers sequence"; + break; } // ToDo: enable header check for PoW blocks if (!AcceptBlockHeader(header, state, chainparams, &pindexLast)) { @@ -6022,11 +6120,42 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, if (state.IsInvalid(nDoS)) { if (nDoS > 0) Misbehaving(pfrom->GetId(), nDoS); - return error("invalid header received"); + ret = false; + strError = "invalid header received"; + break; + } + } + + if (pindexLast) { + nLast = pindexLast->nHeight; + if (bFirst){ + nFirst = pindexLast->nHeight; + bFirst = false; } } } + // Do not activate spam filter during IBD + if (GetBoolArg("-headerspamfilter", DEFAULT_HEADER_SPAM_FILTER) && !IsInitialBlockDownload()) + { + LOCK(cs_main); + CValidationState state; + CNodeState *nodestate = State(pfrom->GetId()); + nodestate->headers.addHeaders(nFirst, nLast); + int nDoS; + ret = nodestate->headers.updateState(state, ret); + if (state.IsInvalid(nDoS)) { + if (nDoS > 0) + Misbehaving(pfrom->GetId(), nDoS); + ret = false; + strError = strError!="" ? strError + " / ": ""; + strError = "header spam protection"; + } + } + + if (!ret) + return error(strError.c_str()); + if (nodestate->nUnconnectingHeaders > 0) { LogPrint("net", "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom->id, nodestate->nUnconnectingHeaders); } diff --git a/src/main.h b/src/main.h index 6ea2fc6b5..5bffee5a5 100644 --- a/src/main.h +++ b/src/main.h @@ -3,6 +3,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +// Header spam protection by Qtum +// Copyright (c) 2016-2019 The Qtum developers + #ifndef BITCOIN_MAIN_H #define BITCOIN_MAIN_H @@ -139,6 +142,13 @@ static const bool DEFAULT_TESTSAFEMODE = false; /** Default for using fee filter */ static const bool DEFAULT_FEEFILTER = 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 = 500; +/** 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; + /** Maximum number of headers to announce when relaying blocks with headers message.*/ static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;