Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Net: Fix for "fake stake" attack #94

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/init.cpp
Expand Up @@ -516,7 +516,9 @@ void SetupServerArgs()
gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::RPC);
gArgs.AddArg("-rpcworkqueue=<n>", 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=<n>", strprintf(_("Use header spam filter (default: %u)"), DEFAULT_HEADER_SPAM_FILTER), false, OptionsCategory::NODE_RELAY);
gArgs.AddArg("-headerspamfiltermaxsize=<n>", 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=<n>", 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
Expand Down
112 changes: 108 additions & 4 deletions src/net_processing.cpp
Expand Up @@ -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<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
Expand Down Expand Up @@ -270,6 +357,8 @@ struct CNodeState {

ChainSyncTimeoutState m_chain_sync;

CNodeHeaders headers;

//! Time of last new block announcement
int64_t m_last_block_announcement;

Expand Down Expand Up @@ -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<CBlockHeader>& 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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions src/net_processing.h
Expand Up @@ -9,6 +9,7 @@
#include <net.h>
#include <validationinterface.h>
#include <consensus/params.h>
#include <consensus/consensus.h>

/** Default for -maxorphantx, maximum number of orphan transactions kept in memory */
static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100;
Expand All @@ -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;
Expand Down
8 changes: 7 additions & 1 deletion src/validation.cpp
Expand Up @@ -3581,11 +3581,12 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState&
}

// Exposed wrapper for AcceptBlockHeader
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex, CBlockHeader *first_invalid)
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& 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)) {
Expand All @@ -3594,6 +3595,11 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidatio
}
if (ppindex) {
*ppindex = pindex;
if(bFirst && pindexFirst)
{
*pindexFirst = pindex;
bFirst = false;
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/validation.h
Expand Up @@ -243,7 +243,7 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<cons
* @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers
* @param[out] first_invalid First header that fails validation, if one exists
*/
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex = nullptr, CBlockHeader* first_invalid = nullptr) LOCKS_EXCLUDED(cs_main);
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& 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);
Expand Down