From b898522aba1cbd787a7908f7fa5a9cf1d0aa0560 Mon Sep 17 00:00:00 2001 From: Damon Bryant Date: Wed, 30 Jan 2019 23:48:39 -0600 Subject: [PATCH 1/2] Proposed resolution to issue, Fake Stake #12 --- src/main.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index c5f4672..8b714a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,6 +50,7 @@ using namespace std; */ CCriticalSection cs_main; +CCriticalSection cs_mapstake; BlockMap mapBlockIndex; map mapProofOfStake; @@ -2301,6 +2302,13 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex if (coins->vout.size() < out.n + 1) coins->vout.resize(out.n + 1); coins->vout[out.n] = undo.txout; + + { + LOCK(cs_mapstake); + + // erase the spent input + mapStakeSpent.erase(out); + } } } } @@ -2534,6 +2542,27 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!pblocktree->WriteTxIndex(vPos)) return state.Abort("Failed to write transaction index"); + { + LOCK(cs_mapstake); + // add new entries + for (const CTransaction tx: block.vtx) { + if (tx.IsCoinBase() || tx.IsZerocoinSpend()) + continue; + + for (const CTxIn in: tx.vin) { + mapStakeSpent.insert(std::make_pair(in.prevout, pindex->nHeight)); + } + } + + for (auto it = mapStakeSpent.begin(); it != mapStakeSpent.end();) { + if (it->second < pindex->nHeight - Params().MaxReorganizationDepth()) { + it = mapStakeSpent.erase(it); + } + else { + it++; + } + } + } // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); From 6eaeffe8eea5032758d884d9d4aa4a3795e4e120 Mon Sep 17 00:00:00 2001 From: Damon Bryant Date: Thu, 31 Jan 2019 10:26:27 -0600 Subject: [PATCH 2/2] Modified AcceptBlock function to mitigate Fake Stake attack --- src/main.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 8b714a8..24b44e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,6 +85,7 @@ struct COrphanTx { CTransaction tx; NodeId fromPeer; }; +map mapStakeSpent; map mapOrphanTransactions; map > mapOrphanTransactionsByPrev; map mapRejectedBlocks; @@ -3672,13 +3673,25 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, if (mi == mapBlockIndex.end()) return state.DoS(0, error("%s : prev block %s not found", __func__, block.hashPrevBlock.ToString().c_str()), 0, "bad-prevblk"); pindexPrev = (*mi).second; - if (pindexPrev->nStatus & BLOCK_FAILED_MASK) + if (pindexPrev->nStatus & BLOCK_FAILED_MASK) { + //If this "invalid" block is an exact match from the checkpoints, then reconsider it + if (Checkpoints::CheckBlock(pindexPrev->nHeight, block.hashPrevBlock, true)) { + LogPrintf("%s : Reconsidering block %s height %d\n", __func__, pindexPrev->GetBlockHash().GetHex(), pindexPrev->nHeight); + CValidationState statePrev; + ReconsiderBlock(statePrev, pindexPrev); + if (statePrev.IsValid()) { + ActivateBestChain(statePrev); + return true; + } + } return state.DoS(100, error("%s : prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); + } } if (block.GetHash() != Params().HashGenesisBlock() && !CheckWork(block, pindexPrev)) return false; + if (!AcceptBlockHeader(block, state, &pindex)) return false; @@ -3698,6 +3711,60 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, int nHeight = pindex->nHeight; + if (block.IsProofOfStake()) { + LOCK(cs_main); + + CCoinsViewCache coins(pcoinsTip); + + if (!coins.HaveInputs(block.vtx[1])) { + LOCK(cs_mapstake); + + // the inputs are spent at the chain tip so we should look at the recently spent outputs + for (CTxIn in : block.vtx[1].vin) { + auto it = mapStakeSpent.find(in.prevout); + if (it == mapStakeSpent.end()) { + return false; + } + if (it->second < pindexPrev->nHeight) { + return false; + } + } + } + + // if this is on a fork + if (!chainActive.Contains(pindexPrev) && pindexPrev != NULL) { + + // start at the block we're adding on to + CBlockIndex *last = pindexPrev; + + // while that block is not on the main chain + while (!chainActive.Contains(last) && last != NULL) { + CBlock bl; + ReadBlockFromDisk(bl, last); + + // loop through every spent input from said block + for (CTransaction t : bl.vtx) { + for (CTxIn in: t.vin) { + + // loop through every spent input in the staking transaction of the new block + for (CTxIn stakeIn : block.vtx[1].vin) { + + // if they spend the same input + if (stakeIn.prevout == in.prevout) { + + // reject the block + return false; + } + } + } + } + + // go to the parent block + last = last->pprev; + } + } + } + // Write block to history file try { unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK, CLIENT_VERSION);