Skip to content

Commit

Permalink
Merge pull request dashpay#68 from cevap/invalid_blocks_storage_and_b…
Browse files Browse the repository at this point in the history
…locks_DoS_spam_filter

[bug][maintenance][docs] Invalid blocks storage and blocks do s spam filter
  • Loading branch information
ioncoincore authored Jan 26, 2019
2 parents dc05035 + 3cd429d commit 3fbf860
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 10 deletions.
5 changes: 5 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,11 @@ 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("-blockspamfilter=<n>", strprintf(_("Use block spam filter (default: %u)"), DEFAULT_BLOCK_SPAM_FILTER));
strUsage += HelpMessageOpt("-blockspamfiltermaxsize=<n>", strprintf(_("Maximum size of the list of indexes in the block spam filter (default: %u)"), DEFAULT_BLOCK_SPAM_FILTER_MAX_SIZE));
strUsage += HelpMessageOpt("-blockspamfiltermaxavg=<n>", strprintf(_("Maximum average size of an index occurrence in the block spam filter (default: %u)"), DEFAULT_BLOCK_SPAM_FILTER_MAX_AVG));

return strUsage;
}

Expand Down
266 changes: 257 additions & 9 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,84 @@ struct CBlockReject {
uint256 hashBlock;
};

/**

class CNodeBlocks
{
public:
CNodeBlocks():
maxSize(0),
maxAvg(0)
{
maxSize = GetArg("-blockspamfiltermaxsize", DEFAULT_BLOCK_SPAM_FILTER_MAX_SIZE);
maxAvg = GetArg("-blockspamfiltermaxavg", DEFAULT_BLOCK_SPAM_FILTER_MAX_AVG);
}

bool onBlockReceived(int nHeight) {
if(nHeight > 0 && maxSize && maxAvg) {
addPoint(nHeight);
return true;
}
return false;
}

bool updateState(CValidationState& state, bool ret)
{
// No Blocks
size_t size = points.size();
if(size == 0)
return ret;

// Compute the number of the received blocks
size_t nBlocks = 0;
for(auto point : points)
{
nBlocks += point.second;
}

// Compute the average value per height
double nAvgValue = (double)nBlocks / size;

// Ban the node if try to spam
bool banNode = (nAvgValue >= 1.5 * maxAvg && size >= maxAvg) ||
(nAvgValue >= maxAvg && nBlocks >= maxSize) ||
(nBlocks >= maxSize * 3);
if(banNode)
{
// Clear the points and ban the node
points.clear();
return state.DoS(100, error("block-spam ban node for sending spam"));
}

return ret;
}

private:
void addPoint(int height)
{
// Remove 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
* processing of incoming data is done after the ProcessMessage call returns,
Expand Down Expand Up @@ -234,6 +311,8 @@ struct CNodeState {
//! Whether we consider this a preferred download peer.
bool fPreferredDownload;

CNodeBlocks nodeBlocks;

CNodeState()
{
fCurrentlyConnected = false;
Expand Down Expand Up @@ -915,6 +994,21 @@ bool ContextualCheckZerocoinMint(const CTransaction& tx, const PublicCoin& coin,
}

bool ContextualCheckZerocoinSpend(const CTransaction& tx, const CoinSpend& spend, CBlockIndex* pindex, const uint256& hashBlock)
{
if(!ContextualCheckZerocoinSpendNoSerialCheck(tx, spend, pindex, hashBlock)){
return false;
}

//Reject serial's that are already in the blockchain
int nHeightTx = 0;
if (IsSerialInBlockchain(spend.getCoinSerialNumber(), nHeightTx))
return error("%s : xION spend with serial %s is already in block %d\n", __func__,
spend.getCoinSerialNumber().GetHex(), nHeightTx);

return true;
}

bool ContextualCheckZerocoinSpendNoSerialCheck(const CTransaction& tx, const CoinSpend& spend, CBlockIndex* pindex, const uint256& hashBlock)
{
//Check to see if the xION is properly signed
if (pindex->nHeight >= Params().Zerocoin_Block_V2_Start()) {
Expand All @@ -930,12 +1024,6 @@ bool ContextualCheckZerocoinSpend(const CTransaction& tx, const CoinSpend& spend
}
}

//Reject serial's that are already in the blockchain
int nHeightTx = 0;
if (IsSerialInBlockchain(spend.getCoinSerialNumber(), nHeightTx))
return error("%s : xION spend with serial %s is already in block %d\n", __func__,
spend.getCoinSerialNumber().GetHex(), nHeightTx);

//Reject serial's that are not in the acceptable value range
bool fUseV1Params = spend.getVersion() < libzerocoin::PrivateCoin::PUBKEY_VERSION;
if (pindex->nHeight > Params().Zerocoin_Block_EnforceSerialRange() &&
Expand Down Expand Up @@ -4423,7 +4511,9 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex,
if (block.GetHash() != Params().HashGenesisBlock() && !CheckWork(block, pindexPrev))
return false;

bool isPoS = false;
if (block.IsProofOfStake()) {
isPoS = true;
uint256 hashProofOfStake = 0;
unique_ptr<CStakeInput> stake;

Expand Down Expand Up @@ -4460,6 +4550,147 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex,
}

int nHeight = pindex->nHeight;
int splitHeight = -1;

if (isPoS) {
LOCK(cs_main);

const bool isBlockFromFork = pindexPrev != nullptr && !chainActive.Contains(pindexPrev);
CTransaction &stakeTxIn = block.vtx[1];

// Check validity of the coinStake.
if(!stakeTxIn.IsCoinStake())
return error("%s: no coin stake on vtx pos 1", __func__);


// Check whether is a fork or not
if (isBlockFromFork) {

// Start at the block we're adding on to
CBlockIndex *prev = pindexPrev;

// Inputs
std::vector<CTxIn> ionInputs;
std::vector<CTxIn> xIONInputs;

for (CTxIn stakeIn : stakeTxIn.vin) {
if(stakeIn.scriptSig.IsZerocoinSpend()){
xIONInputs.push_back(stakeIn);
}else{
ionInputs.push_back(stakeIn);
}
}
const bool hasIONInputs = !ionInputs.empty();
const bool hasXIONInputs = !xIONInputs.empty();
vector<CBigNum> vBlockSerials;
CBlock bl;
// Go backwards on the forked chain up to the split
do {
if(!ReadBlockFromDisk(bl, prev))
// Previous block not on disk
return error("%s: previous block %s not on disk", __func__, prev->GetBlockHash().GetHex());


// Loop through every input from said block
for (CTransaction t : bl.vtx) {
for (CTxIn in: t.vin) {
// Loop through every input of the staking tx
for (CTxIn stakeIn : ionInputs) {
// if it's already spent
// First regular staking check
if(hasIONInputs) {
if (stakeIn.prevout == in.prevout) {
// reject the block
return state.DoS(100,
error("%s: input already spent on a previous block",
__func__));
}
}
// Second, if there is zPoS staking then store the serials for later check
if(hasXIONInputs) {
if(in.scriptSig.IsZerocoinSpend()){
CoinSpend spend = TxInToZerocoinSpend(in);
vBlockSerials.push_back(spend.getCoinSerialNumber());
}
}
}
}
}

prev = prev->pprev;

} while (!chainActive.Contains(prev));

// Split height
splitHeight = prev->nHeight;

// Now that this loop if completed. Check if we have xION inputs.
if(hasXIONInputs){

for (CTxIn xIonInput : xIONInputs) {
CoinSpend spend = TxInToZerocoinSpend(xIonInput);

// First check if the serials were not already spent on the forked blocks.
CBigNum coinSerial = spend.getCoinSerialNumber();
for(CBigNum serial : vBlockSerials){
if(serial == coinSerial){
return state.DoS(100,
error("%s: serial double spent on fork",
__func__));
}
}
// Now check if the serial exists before the chain split.
int nHeightTx = 0;
if (IsSerialInBlockchain(spend.getCoinSerialNumber(), nHeightTx)){
// if the height is nHeightTx > chainSplit means that the spent occurred after the chain split
if(nHeightTx <= splitHeight){
return state.DoS(100,
error("%s: serial double spent on main chain",
__func__));
}
}

if (!ContextualCheckZerocoinSpendNoSerialCheck(stakeTxIn, spend, pindex, 0))
return state.DoS(100,error("%s: ContextualCheckZerocoinSpend failed for tx %s", __func__,
stakeTxIn.GetHash().GetHex()), REJECT_INVALID, "bad-txns-invalid-xion");

// Now only the ZKP left..
// As the spend maturity is 200, the acc value must be accumulated, otherwise it's not ready to be spent
CBigNum bnAccumulatorValue = 0;
if (!zerocoinDB->ReadAccumulatorValue(spend.getAccumulatorChecksum(), bnAccumulatorValue)) {
return state.DoS(100, error("%s: stake zerocoinspend not ready to be spent", __func__));
}

Accumulator accumulator(Params().Zerocoin_Params(chainActive.Height() < Params().Zerocoin_Block_V2_Start()),
spend.getDenomination(), bnAccumulatorValue);

//Check that the coinspend is valid
if(!spend.Verify(accumulator))
return state.DoS(100, error("%s: zerocoin spend did not verify", __func__));

}
}

}


// If the stake is not a zPoS then let's check if the inputs were spent on the main chain
const CCoinsViewCache coins(pcoinsTip);
if(!stakeTxIn.IsZerocoinSpend()) {
for (CTxIn in: stakeTxIn.vin) {
const CCoins* coin = coins.AccessCoins(in.prevout.hash);
if(coin && !coin->IsAvailable(in.prevout.n)){
// If this is not available get the height of the spent and validate it with the forked height
// Check if this occurred before the chain split
if(!(isBlockFromFork && coin->nHeight > splitHeight)){
// Coins not available
return error("%s: coin stake inputs already spent in main chain", __func__);
}
}
}
}

}

// Write block to history file
try {
Expand Down Expand Up @@ -4592,8 +4823,25 @@ bool ProcessNewBlock(CValidationState& state, CNode* pfrom, CBlock* pblock, CDis
mapBlockSource[pindex->GetBlockHash ()] = pfrom->GetId ();
}
CheckBlockIndex ();
if (!ret)
return error ("%s : AcceptBlock FAILED", __func__);
if (!ret) {
// Check spamming
if(GetBoolArg("-blockspamfilter", DEFAULT_BLOCK_SPAM_FILTER)) {
CNodeState *nodestate = State(pfrom->GetId());
nodestate->nodeBlocks.onBlockReceived(pindex->nHeight);
bool nodeStatus = true;
// UpdateState will return false if the node is attacking us or update the score and return true.
nodeStatus = nodestate->nodeBlocks.updateState(state, nodeStatus);
int nDoS = 0;
if (state.IsInvalid(nDoS)) {
if (nDoS > 0)
Misbehaving(pfrom->GetId(), nDoS);
nodeStatus = false;
}
if(!nodeStatus)
return error("%s : AcceptBlock FAILED - block spam protection", __func__);
}
return error("%s : AcceptBlock FAILED", __func__);
}
}

if (!ActivateBestChain(state, pblock, checked))
Expand Down
10 changes: 9 additions & 1 deletion src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111;
/** Enable bloom filter */
static const bool DEFAULT_PEERBLOOMFILTERS = true;

/** Default for -blockspamfilter, use header spam filter */
static const bool DEFAULT_BLOCK_SPAM_FILTER = true;
/** Default for -blockspamfiltermaxsize, maximum size of the list of indexes in the block spam filter */
static const unsigned int DEFAULT_BLOCK_SPAM_FILTER_MAX_SIZE = COINBASE_MATURITY;
/** Default for -blockspamfiltermaxavg, maximum average size of an index occurrence in the block spam filter */
static const unsigned int DEFAULT_BLOCK_SPAM_FILTER_MAX_AVG = 10;

struct BlockHasher {
size_t operator()(const uint256& hash) const { return hash.GetLow64(); }
};
Expand Down Expand Up @@ -326,7 +333,8 @@ void UpdateCoins(const CTransaction& tx, CValidationState& state, CCoinsViewCach
bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state);
bool CheckZerocoinMint(const uint256& txHash, const CTxOut& txout, CValidationState& state, bool fCheckOnly = false);
bool CheckZerocoinSpend(const CTransaction& tx, bool fVerifySignature, CValidationState& state);
bool ContextualCheckZerocoinSpend(const CTransaction& tx, const libzerocoin::CoinSpend& spend, CBlockIndex* pindex);
bool ContextualCheckZerocoinSpend(const CTransaction& tx, const libzerocoin::CoinSpend& spend, CBlockIndex* pindex, const uint256& hashBlock);
bool ContextualCheckZerocoinSpendNoSerialCheck(const CTransaction& tx, const libzerocoin::CoinSpend& spend, CBlockIndex* pindex, const uint256& hashBlock);
bool IsTransactionInChain(const uint256& txId, int& nHeightTx, CTransaction& tx);
bool IsTransactionInChain(const uint256& txId, int& nHeightTx);
bool IsBlockHashInChain(const uint256& hashBlock);
Expand Down

0 comments on commit 3fbf860

Please sign in to comment.