Skip to content

Commit 210a22d

Browse files
Merge pull request #335 from aguycalled/header-spam-protection
Header spam protection
2 parents eb6a1a2 + fa8880e commit 210a22d

File tree

3 files changed

+136
-3
lines changed

3 files changed

+136
-3
lines changed

src/init.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,9 @@ std::string HelpMessage(HelpMessageMode mode)
540540
strUsage += HelpMessageOpt("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE));
541541
strUsage += HelpMessageOpt("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT));
542542
}
543+
strUsage += HelpMessageOpt("-headerspamfilter=<n>", strprintf(_("Use header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER));
544+
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));
545+
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));
543546

544547
return strUsage;
545548
}

src/main.cpp

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,89 @@ struct CBlockReject {
275275
uint256 hashBlock;
276276
};
277277

278+
class CNodeHeaders
279+
{
280+
public:
281+
CNodeHeaders():
282+
maxSize(0),
283+
maxAvg(0)
284+
{
285+
maxSize = GetArg("-headerspamfiltermaxsize", DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE);
286+
maxAvg = GetArg("-headerspamfiltermaxavg", DEFAULT_HEADER_SPAM_FILTER_MAX_AVG);
287+
}
288+
289+
bool addHeaders(int nBegin, int nEnd)
290+
{
291+
292+
if(nBegin > 0 && nEnd > 0 && maxSize && maxAvg)
293+
{
294+
295+
for(int point = nBegin; point<= nEnd; point++)
296+
{
297+
addPoint(point);
298+
}
299+
300+
return true;
301+
}
302+
303+
return false;
304+
}
305+
306+
bool updateState(CValidationState& state, bool ret)
307+
{
308+
// No headers
309+
size_t size = points.size();
310+
if(size == 0)
311+
return ret;
312+
313+
// Compute the number of the received headers
314+
size_t nHeaders = 0;
315+
for(auto point : points)
316+
{
317+
nHeaders += point.second;
318+
}
319+
320+
// Compute the average value per height
321+
double nAvgValue = (double)nHeaders / size;
322+
323+
// Ban the node if try to spam
324+
bool banNode = (nAvgValue >= 1.5 * maxAvg && size >= maxAvg) ||
325+
(nAvgValue >= maxAvg && nHeaders >= maxSize) ||
326+
(nHeaders >= maxSize * 3);
327+
if(banNode)
328+
{
329+
// Clear the points and ban the node
330+
points.clear();
331+
return state.DoS(100, false, REJECT_INVALID, "header-spam", false, "ban node for sending spam");
332+
}
333+
334+
return ret;
335+
}
336+
337+
private:
338+
void addPoint(int height)
339+
{
340+
// Erace the last element in the list
341+
if(points.size() == maxSize)
342+
{
343+
points.erase(points.begin());
344+
}
345+
346+
// Add the point to the list
347+
int occurrence = 0;
348+
auto mi = points.find(height);
349+
if (mi != points.end())
350+
occurrence = (*mi).second;
351+
occurrence++;
352+
points[height] = occurrence;
353+
}
354+
355+
private:
356+
std::map<int,int> points;
357+
size_t maxSize;
358+
size_t maxAvg;
359+
};
360+
278361
/**
279362
* Maintain validation-specific state about nodes, protected by cs_main, instead
280363
* by CNode's own locks. This simplifies asynchronous operation, where
@@ -324,6 +407,8 @@ struct CNodeState {
324407
//! Whether this peer can give us witnesses
325408
bool fHaveWitness;
326409

410+
CNodeHeaders headers;
411+
327412
CNodeState() {
328413
fCurrentlyConnected = false;
329414
nMisbehavior = 0;
@@ -7194,25 +7279,63 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
71947279
return true;
71957280
}
71967281

7282+
bool ret = true;
7283+
bool bFirst = true;
7284+
string strError = "";
7285+
7286+
int nFirst = 0;
7287+
int nLast = 0;
7288+
71977289
CBlockIndex *pindexLast = NULL;
7290+
71987291
BOOST_FOREACH(const CBlock& header, headers) {
71997292
CValidationState state;
72007293
if (pindexLast != NULL && header.hashPrevBlock != pindexLast->GetBlockHash()) {
72017294
Misbehaving(pfrom->GetId(), 20);
7202-
return error("non-continuous headers sequence");
7295+
ret = false;
7296+
strError = "non-continuous headers sequence";
7297+
break;
72037298
}
72047299
CBlockHeader pblockheader = CBlockHeader(header);
72057300
if (!AcceptBlockHeader(pblockheader, state, chainparams, &pindexLast)) {
7206-
72077301
int nDoS;
72087302
if (state.IsInvalid(nDoS)) {
72097303
if (nDoS > 0)
72107304
Misbehaving(pfrom->GetId(), nDoS);
7211-
return error("invalid header received");
7305+
ret = false;
7306+
strError = "invalid header received";
7307+
break;
72127308
}
72137309
}
7310+
if (pindexLast) {
7311+
nLast = pindexLast->nHeight;
7312+
if (bFirst){
7313+
nFirst = pindexLast->nHeight;
7314+
bFirst = false;
7315+
}
7316+
}
7317+
}
7318+
7319+
if(GetBoolArg("-headerspamfilter", DEFAULT_HEADER_SPAM_FILTER))
7320+
{
7321+
LOCK(cs_main);
7322+
CValidationState state;
7323+
CNodeState *nodestate = State(pfrom->GetId());
7324+
nodestate->headers.addHeaders(nFirst, nLast);
7325+
int nDoS;
7326+
ret = nodestate->headers.updateState(state, ret);
7327+
if (state.IsInvalid(nDoS)) {
7328+
if (nDoS > 0)
7329+
Misbehaving(pfrom->GetId(), nDoS);
7330+
ret = false;
7331+
strError = strError!="" ? strError + " / ": "";
7332+
strError = "header spam protection";
7333+
}
72147334
}
72157335

7336+
if (!ret)
7337+
return error(strError.c_str());
7338+
72167339
if (nodestate->nUnconnectingHeaders > 0) {
72177340
LogPrint("net", "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom->id, nodestate->nUnconnectingHeaders);
72187341
}

src/main.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ static const bool DEFAULT_ENABLE_REPLACEMENT = true;
155155
/** Default for using fee filter */
156156
static const bool DEFAULT_FEEFILTER = true;
157157

158+
/** Default for -headerspamfilter, use header spam filter */
159+
static const bool DEFAULT_HEADER_SPAM_FILTER = true;
160+
/** Default for -headerspamfiltermaxsize, maximum size of the list of indexes in the header spam filter */
161+
static const unsigned int DEFAULT_HEADER_SPAM_FILTER_MAX_SIZE = COINBASE_MATURITY;
162+
/** Default for -headerspamfiltermaxavg, maximum average size of an index occurrence in the header spam filter */
163+
static const unsigned int DEFAULT_HEADER_SPAM_FILTER_MAX_AVG = 10;
164+
158165
/** Maximum number of headers to announce when relaying blocks with headers message.*/
159166
static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8;
160167

0 commit comments

Comments
 (0)