Add header spam protection
This commit is contained in:
@@ -474,6 +474,9 @@ std::string HelpMessage(HelpMessageMode mode)
|
||||
strUsage += HelpMessageOpt("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE));
|
||||
strUsage += HelpMessageOpt("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT));
|
||||
}
|
||||
strUsage += HelpMessageOpt("-headerspamfilter=<n>", strprintf(_("Use header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER));
|
||||
strUsage += HelpMessageOpt("-headerspamfiltermaxsize=<n>", strprintf(_("Maximum size of the list of indexes in the header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE));
|
||||
strUsage += HelpMessageOpt("-headerspamfiltermaxavg=<n>", 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:"));
|
||||
|
||||
133
src/main.cpp
133
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<int,int> 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);
|
||||
}
|
||||
|
||||
10
src/main.h
10
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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user