Prepare block connection logic for headers-first.

This changes the block processing logic from "try to atomically switch
to a new block" to a continuous "(dis)connect a block, aiming for the
assumed best chain".

This means the smallest atomic operations on the chainstate become
individual block connections or disconnections, instead of entire
reorganizations. It may mean that we try to reorganize to one block,
fail, and rereorganize again to the old block. This is slower, but
doesn't require unbounded RAM.

It also means that a ConnectBlock which fails may be no longer called
from the ProcessBlock which knows which node sent it. To deal with that,
a mapBlockSource is kept, and invalid blocks cause asynchronous "reject"
messages and banning (if necessary).
This commit is contained in:
Pieter Wuille 2013-11-16 19:28:24 +01:00
parent 0ec16f35d6
commit 75f51f2a63
3 changed files with 265 additions and 213 deletions

View file

@ -983,7 +983,7 @@ bool AppInit2(boost::thread_group& threadGroup)
// scan for better chains in the block chain database, that are not yet connected in the active best chain // scan for better chains in the block chain database, that are not yet connected in the active best chain
CValidationState state; CValidationState state;
if (!ConnectBestBlock(state)) if (!ActivateBestChain(state))
strErrors << "Failed to connect best block"; strErrors << "Failed to connect best block";
std::vector<boost::filesystem::path> vImportFiles; std::vector<boost::filesystem::path> vImportFiles;

View file

@ -41,6 +41,7 @@ CTxMemPool mempool;
map<uint256, CBlockIndex*> mapBlockIndex; map<uint256, CBlockIndex*> mapBlockIndex;
CChain chainActive; CChain chainActive;
CChain chainMostWork;
int64_t nTimeBestReceived = 0; int64_t nTimeBestReceived = 0;
int nScriptCheckThreads = 0; int nScriptCheckThreads = 0;
bool fImporting = false; bool fImporting = false;
@ -77,13 +78,21 @@ namespace {
struct CBlockIndexWorkComparator struct CBlockIndexWorkComparator
{ {
bool operator()(CBlockIndex *pa, CBlockIndex *pb) { bool operator()(CBlockIndex *pa, CBlockIndex *pb) {
// First sort by most total work, ...
if (pa->nChainWork > pb->nChainWork) return false; if (pa->nChainWork > pb->nChainWork) return false;
if (pa->nChainWork < pb->nChainWork) return true; if (pa->nChainWork < pb->nChainWork) return true;
if (pa->GetBlockHash() < pb->GetBlockHash()) return false; // ... then by earliest time received, ...
if (pa->GetBlockHash() > pb->GetBlockHash()) return true; if (pa->nSequenceId < pb->nSequenceId) return false;
if (pa->nSequenceId > pb->nSequenceId) return true;
return false; // identical blocks // Use pointer address as tie breaker (should only happen with blocks
// loaded from disk, as those all have id 0).
if (pa < pb) return false;
if (pa > pb) return true;
// Identical blocks.
return false;
} }
}; };
@ -93,6 +102,16 @@ set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexValid; // may contain
CCriticalSection cs_LastBlockFile; CCriticalSection cs_LastBlockFile;
CBlockFileInfo infoLastBlockFile; CBlockFileInfo infoLastBlockFile;
int nLastBlockFile = 0; int nLastBlockFile = 0;
// Every received block is assigned a unique and increasing identifier, so we
// know which one to give priority in case of a fork.
CCriticalSection cs_nBlockSequenceId;
// Blocks loaded from disk are assigned id 0, so start the counter at 1.
uint32_t nBlockSequenceId = 1;
// Sources of received blocks, to be able to send them reject messages or ban
// them, if processing happens afterwards. Protected by cs_main.
map<uint256, NodeId> mapBlockSource;
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@ -156,14 +175,26 @@ void SyncWithWallets(const uint256 &hash, const CTransaction &tx, const CBlock *
// //
namespace { namespace {
struct CBlockReject {
unsigned char chRejectCode;
string strRejectReason;
uint256 hashBlock;
};
// Maintain validation-specific state about nodes, protected by cs_main, instead // Maintain validation-specific state about nodes, protected by cs_main, instead
// by CNode's own locks. This simplifies asynchronous operation, where // by CNode's own locks. This simplifies asynchronous operation, where
// processing of incoming data is done after the ProcessMessage call returns, // processing of incoming data is done after the ProcessMessage call returns,
// and we're no longer holding the node's locks. // and we're no longer holding the node's locks.
struct CNodeState { struct CNodeState {
// Accumulated misbehaviour score for this peer.
int nMisbehavior; int nMisbehavior;
// Whether this peer should be disconnected and banned.
bool fShouldBan; bool fShouldBan;
// String name of this peer (debugging/logging purposes).
std::string name; std::string name;
// List of asynchronously-determined block rejections to notify this peer about.
std::vector<CBlockReject> rejects;
CNodeState() { CNodeState() {
nMisbehavior = 0; nMisbehavior = 0;
@ -171,6 +202,7 @@ struct CNodeState {
} }
}; };
// Map maintaining per-node state. Requires cs_main.
map<NodeId, CNodeState> mapNodeState; map<NodeId, CNodeState> mapNodeState;
// Requires cs_main. // Requires cs_main.
@ -1242,6 +1274,24 @@ void CheckForkWarningConditionsOnNewFork(CBlockIndex* pindexNewForkTip)
CheckForkWarningConditions(); CheckForkWarningConditions();
} }
void Misbehaving(NodeId pnode, int howmuch)
{
if (howmuch == 0)
return;
CNodeState *state = State(pnode);
if (state == NULL)
return;
state->nMisbehavior += howmuch;
if (state->nMisbehavior >= GetArg("-banscore", 100))
{
LogPrintf("Misbehaving: %s (%d -> %d) BAN THRESHOLD EXCEEDED\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior);
state->fShouldBan = true;
} else
LogPrintf("Misbehaving: %s (%d -> %d)\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior);
}
void static InvalidChainFound(CBlockIndex* pindexNew) void static InvalidChainFound(CBlockIndex* pindexNew)
{ {
if (!pindexBestInvalid || pindexNew->nChainWork > pindexBestInvalid->nChainWork) if (!pindexBestInvalid || pindexNew->nChainWork > pindexBestInvalid->nChainWork)
@ -1263,69 +1313,25 @@ void static InvalidChainFound(CBlockIndex* pindexNew)
CheckForkWarningConditions(); CheckForkWarningConditions();
} }
void static InvalidBlockFound(CBlockIndex *pindex) { void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) {
int nDoS = 0;
if (state.IsInvalid(nDoS)) {
std::map<uint256, NodeId>::iterator it = mapBlockSource.find(pindex->GetBlockHash());
if (it != mapBlockSource.end() && State(it->second)) {
CBlockReject reject = {state.GetRejectCode(), state.GetRejectReason(), pindex->GetBlockHash()};
State(it->second)->rejects.push_back(reject);
if (nDoS > 0)
Misbehaving(it->second, nDoS);
}
}
if (!state.CorruptionPossible()) {
pindex->nStatus |= BLOCK_FAILED_VALID; pindex->nStatus |= BLOCK_FAILED_VALID;
pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex)); pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex));
setBlockIndexValid.erase(pindex); setBlockIndexValid.erase(pindex);
InvalidChainFound(pindex); InvalidChainFound(pindex);
if (chainActive.Next(pindex)) {
CValidationState stateDummy;
ConnectBestBlock(stateDummy); // reorganise away from the failed block
} }
} }
bool ConnectBestBlock(CValidationState &state) {
do {
CBlockIndex *pindexNewBest;
{
std::set<CBlockIndex*,CBlockIndexWorkComparator>::reverse_iterator it = setBlockIndexValid.rbegin();
if (it == setBlockIndexValid.rend())
return true;
pindexNewBest = *it;
}
if (pindexNewBest == chainActive.Tip() || (chainActive.Tip() && pindexNewBest->nChainWork == chainActive.Tip()->nChainWork))
return true; // nothing to do
// check ancestry
CBlockIndex *pindexTest = pindexNewBest;
std::vector<CBlockIndex*> vAttach;
do {
if (pindexTest->nStatus & BLOCK_FAILED_MASK) {
// mark descendants failed
CBlockIndex *pindexFailed = pindexNewBest;
while (pindexTest != pindexFailed) {
pindexFailed->nStatus |= BLOCK_FAILED_CHILD;
setBlockIndexValid.erase(pindexFailed);
pblocktree->WriteBlockIndex(CDiskBlockIndex(pindexFailed));
pindexFailed = pindexFailed->pprev;
}
InvalidChainFound(pindexNewBest);
break;
}
if (chainActive.Tip() == NULL || pindexTest->nChainWork > chainActive.Tip()->nChainWork)
vAttach.push_back(pindexTest);
if (pindexTest->pprev == NULL || chainActive.Next(pindexTest)) {
reverse(vAttach.begin(), vAttach.end());
BOOST_FOREACH(CBlockIndex *pindexSwitch, vAttach) {
boost::this_thread::interruption_point();
try {
if (!SetBestChain(state, pindexSwitch))
return false;
} catch(std::runtime_error &e) {
return state.Abort(_("System error: ") + e.what());
}
}
return true;
}
pindexTest = pindexTest->pprev;
} while(true);
} while(true);
}
void UpdateTime(CBlockHeader& block, const CBlockIndex* pindexPrev) void UpdateTime(CBlockHeader& block, const CBlockIndex* pindexPrev)
{ {
block.nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); block.nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
@ -1746,8 +1752,10 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C
return true; return true;
} }
// Update the on-disk chain state.
bool static WriteChainState(CValidationState &state) { bool static WriteChainState(CValidationState &state) {
if (!IsInitialBlockDownload() || pcoinsTip->GetCacheSize() > nCoinCacheSize) { static int64_t nLastWrite = 0;
if (!IsInitialBlockDownload() || pcoinsTip->GetCacheSize() > nCoinCacheSize || GetTimeMicros() > nLastWrite + 600*1000000) {
// Typical CCoins structures on disk are around 100 bytes in size. // Typical CCoins structures on disk are around 100 bytes in size.
// Pushing a new one to the database can cause it to be written // Pushing a new one to the database can cause it to be written
// twice (once in the log, and once in the tables). This is already // twice (once in the log, and once in the tables). This is already
@ -1759,10 +1767,12 @@ bool static WriteChainState(CValidationState &state) {
pblocktree->Sync(); pblocktree->Sync();
if (!pcoinsTip->Flush()) if (!pcoinsTip->Flush())
return state.Abort(_("Failed to write to coin database")); return state.Abort(_("Failed to write to coin database"));
nLastWrite = GetTimeMicros();
} }
return true; return true;
} }
// Update chainActive and related internal data structures.
void static UpdateTip(CBlockIndex *pindexNew) { void static UpdateTip(CBlockIndex *pindexNew) {
chainActive.SetTip(pindexNew); chainActive.SetTip(pindexNew);
@ -1796,129 +1806,179 @@ void static UpdateTip(CBlockIndex *pindexNew) {
// strMiscWarning is read by GetWarnings(), called by Qt and the JSON-RPC code to warn the user: // strMiscWarning is read by GetWarnings(), called by Qt and the JSON-RPC code to warn the user:
strMiscWarning = _("Warning: This version is obsolete, upgrade required!"); strMiscWarning = _("Warning: This version is obsolete, upgrade required!");
} }
}
// Disconnect chainActive's tip.
bool static DisconnectTip(CValidationState &state) {
CBlockIndex *pindexDelete = chainActive.Tip();
assert(pindexDelete);
mempool.check(pcoinsTip);
// Read block from disk.
CBlock block;
if (!ReadBlockFromDisk(block, pindexDelete))
return state.Abort(_("Failed to read block"));
// Apply the block atomically to the chain state.
int64_t nStart = GetTimeMicros();
{
CCoinsViewCache view(*pcoinsTip, true);
if (!DisconnectBlock(block, state, pindexDelete, view))
return error("DisconnectTip() : DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString());
assert(view.Flush());
}
if (fBenchmark)
LogPrintf("- Disconnect: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
// Write the chain state to disk, if necessary.
if (!WriteChainState(state))
return false;
// Ressurect mempool transactions from the disconnected block.
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
// ignore validation errors in resurrected transactions
CValidationState stateDummy;
if (!tx.IsCoinBase())
if (!AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
mempool.remove(tx, true);
}
mempool.check(pcoinsTip);
// Update chainActive and related variables.
UpdateTip(pindexDelete->pprev);
return true;
}
// Connect a new block to chainActive.
bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew) {
assert(pindexNew->pprev == chainActive.Tip());
mempool.check(pcoinsTip);
// Read block from disk.
CBlock block;
if (!ReadBlockFromDisk(block, pindexNew))
return state.Abort(_("Failed to read block"));
// Apply the block atomically to the chain state.
int64_t nStart = GetTimeMicros();
{
CCoinsViewCache view(*pcoinsTip, true);
CInv inv(MSG_BLOCK, pindexNew->GetBlockHash());
if (!ConnectBlock(block, state, pindexNew, view)) {
if (state.IsInvalid())
InvalidBlockFound(pindexNew, state);
return error("ConnectTip() : ConnectBlock %s failed", pindexNew->GetBlockHash().ToString());
}
mapBlockSource.erase(inv.hash);
assert(view.Flush());
}
if (fBenchmark)
LogPrintf("- Connect: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
// Write the chain state to disk, if necessary.
if (!WriteChainState(state))
return false;
// Remove conflicting transactions from the mempool.
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
mempool.remove(tx);
mempool.removeConflicts(tx);
}
mempool.check(pcoinsTip);
// Update chainActive & related variables.
UpdateTip(pindexNew);
return true;
}
// Make chainMostWork correspond to the chain with the most work in it, that isn't
// known to be invalid (it's however far from certain to be valid).
void static FindMostWorkChain() {
CBlockIndex *pindexNew = NULL;
// In case the current best is invalid, do not consider it.
while (chainMostWork.Tip() && (chainMostWork.Tip()->nStatus & BLOCK_FAILED_MASK)) {
setBlockIndexValid.erase(chainMostWork.Tip());
chainMostWork.SetTip(chainMostWork.Tip()->pprev);
}
do {
// Find the best candidate header.
{
std::set<CBlockIndex*, CBlockIndexWorkComparator>::reverse_iterator it = setBlockIndexValid.rbegin();
if (it == setBlockIndexValid.rend())
return;
pindexNew = *it;
}
// Check whether all blocks on the path between the currently active chain and the candidate are valid.
// Just going until the active chain is an optimization, as we know all blocks in it are valid already.
CBlockIndex *pindexTest = pindexNew;
bool fInvalidAncestor = false;
while (pindexTest && !chainActive.Contains(pindexTest)) {
if (pindexTest->nStatus & BLOCK_FAILED_MASK) {
// Candidate has an invalid ancestor, remove entire chain from the set.
if (pindexBestInvalid == NULL || pindexNew->nChainWork > pindexBestInvalid->nChainWork)
pindexBestInvalid = pindexNew; CBlockIndex *pindexFailed = pindexNew;
while (pindexTest != pindexFailed) {
pindexFailed->nStatus |= BLOCK_FAILED_CHILD;
setBlockIndexValid.erase(pindexFailed);
pindexFailed = pindexFailed->pprev;
}
fInvalidAncestor = true;
break;
}
pindexTest = pindexTest->pprev;
}
if (fInvalidAncestor)
continue;
break;
} while(true);
// Check whether it's actually an improvement.
if (chainMostWork.Tip() && !CBlockIndexWorkComparator()(chainMostWork.Tip(), pindexNew))
return;
// We have a new best.
chainMostWork.SetTip(pindexNew);
}
// Try to activate to the most-work chain (thereby connecting it).
bool ActivateBestChain(CValidationState &state) {
CBlockIndex *pindexOldTip = chainActive.Tip();
bool fComplete = false;
while (!fComplete) {
FindMostWorkChain();
fComplete = true;
// Check whether we have something to do.
if (chainMostWork.Tip() == NULL) break;
// Disconnect active blocks which are no longer in the best chain.
while (chainActive.Tip() && !chainMostWork.Contains(chainActive.Tip())) {
if (!DisconnectTip(state))
return false;
}
// Connect new blocks.
while (!chainActive.Contains(chainMostWork.Tip())) {
CBlockIndex *pindexConnect = chainMostWork[chainActive.Height() + 1];
if (!ConnectTip(state, pindexConnect)) {
if (state.IsInvalid()) {
// The block violates a consensus rule.
if (!state.CorruptionPossible())
InvalidChainFound(chainMostWork.Tip());
fComplete = false;
state = CValidationState();
break;
} else {
// A system error occurred (disk space, database error, ...).
return false;
}
}
}
}
if (chainActive.Tip() != pindexOldTip) {
std::string strCmd = GetArg("-blocknotify", ""); std::string strCmd = GetArg("-blocknotify", "");
if (!IsInitialBlockDownload() && !strCmd.empty())
if (!fIsInitialDownload && !strCmd.empty())
{ {
boost::replace_all(strCmd, "%s", chainActive.Tip()->GetBlockHash().GetHex()); boost::replace_all(strCmd, "%s", chainActive.Tip()->GetBlockHash().GetHex());
boost::thread t(runCommand, strCmd); // thread runs free boost::thread t(runCommand, strCmd); // thread runs free
} }
} }
bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew)
{
mempool.check(pcoinsTip);
// All modifications to the coin state will be done in this cache.
// Only when all have succeeded, we push it to pcoinsTip.
CCoinsViewCache view(*pcoinsTip, true);
// Find the fork (typically, there is none)
std::map<uint256, CBlockIndex*>::iterator it = mapBlockIndex.find(view.GetBestBlock());
CBlockIndex* ptip = (it != mapBlockIndex.end()) ? it->second : NULL;
CBlockIndex* pfork = ptip;
CBlockIndex* plonger = pindexNew;
while (pfork && pfork != plonger)
{
while (plonger->nHeight > pfork->nHeight) {
plonger = plonger->pprev;
assert(plonger != NULL);
}
if (pfork == plonger)
break;
pfork = pfork->pprev;
assert(pfork != NULL);
}
// List of what to disconnect (typically nothing)
vector<CBlockIndex*> vDisconnect;
for (CBlockIndex* pindex = ptip; pindex != pfork; pindex = pindex->pprev)
vDisconnect.push_back(pindex);
// List of what to connect (typically only pindexNew)
vector<CBlockIndex*> vConnect;
for (CBlockIndex* pindex = pindexNew; pindex != pfork; pindex = pindex->pprev)
vConnect.push_back(pindex);
reverse(vConnect.begin(), vConnect.end());
if (vDisconnect.size() > 0) {
LogPrintf("REORGANIZE: Disconnect %"PRIszu" blocks; %s...\n", vDisconnect.size(), pfork->GetBlockHash().ToString());
LogPrintf("REORGANIZE: Connect %"PRIszu" blocks; ...%s\n", vConnect.size(), pindexNew->GetBlockHash().ToString());
}
// Disconnect shorter branch
list<CTransaction> vResurrect;
BOOST_FOREACH(CBlockIndex* pindex, vDisconnect) {
CBlock block;
if (!ReadBlockFromDisk(block, pindex))
return state.Abort(_("Failed to read block"));
int64_t nStart = GetTimeMicros();
if (!DisconnectBlock(block, state, pindex, view))
return error("SetBestBlock() : DisconnectBlock %s failed", pindex->GetBlockHash().ToString());
if (fBenchmark)
LogPrintf("- Disconnect: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
// Queue memory transactions to resurrect.
// We only do this for blocks after the last checkpoint (reorganisation before that
// point should only happen with -reindex/-loadblock, or a misbehaving peer.
BOOST_REVERSE_FOREACH(const CTransaction& tx, block.vtx)
if (!tx.IsCoinBase() && pindex->nHeight > Checkpoints::GetTotalBlocksEstimate())
vResurrect.push_front(tx);
}
// Connect longer branch
vector<CTransaction> vDelete;
BOOST_FOREACH(CBlockIndex *pindex, vConnect) {
CBlock block;
if (!ReadBlockFromDisk(block, pindex))
return state.Abort(_("Failed to read block"));
int64_t nStart = GetTimeMicros();
if (!ConnectBlock(block, state, pindex, view)) {
if (state.IsInvalid()) {
InvalidChainFound(pindexNew);
InvalidBlockFound(pindex);
}
return error("SetBestBlock() : ConnectBlock %s failed", pindex->GetBlockHash().ToString());
}
if (fBenchmark)
LogPrintf("- Connect: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
// Queue memory transactions to delete
BOOST_FOREACH(const CTransaction& tx, block.vtx)
vDelete.push_back(tx);
}
// Flush changes to global coin state
int64_t nStart = GetTimeMicros();
int nModified = view.GetCacheSize();
bool ret;
ret = view.Flush();
assert(ret);
int64_t nTime = GetTimeMicros() - nStart;
if (fBenchmark)
LogPrintf("- Flush %i transactions: %.2fms (%.4fms/tx)\n", nModified, 0.001 * nTime, 0.001 * nTime / nModified);
if (!WriteChainState(state))
return false;
// Resurrect memory transactions that were in the disconnected branch
BOOST_FOREACH(CTransaction& tx, vResurrect) {
// ignore validation errors in resurrected transactions
CValidationState stateDummy;
if (!AcceptToMemoryPool(mempool,stateDummy, tx, false, NULL))
mempool.remove(tx, true);
}
// Delete redundant memory transactions that are in the connected branch
BOOST_FOREACH(CTransaction& tx, vDelete) {
mempool.remove(tx);
mempool.removeConflicts(tx);
}
mempool.check(pcoinsTip);
UpdateTip(pindexNew);
return true; return true;
} }
@ -1931,7 +1991,12 @@ bool AddToBlockIndex(CBlock& block, CValidationState& state, const CDiskBlockPos
// Construct new block index object // Construct new block index object
CBlockIndex* pindexNew = new CBlockIndex(block); CBlockIndex* pindexNew = new CBlockIndex(block);
{
LOCK(cs_nBlockSequenceId);
pindexNew->nSequenceId = nBlockSequenceId++;
}
assert(pindexNew); assert(pindexNew);
mapAlreadyAskedFor.erase(CInv(MSG_BLOCK, hash));
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
pindexNew->phashBlock = &((*mi).first); pindexNew->phashBlock = &((*mi).first);
map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock); map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock);
@ -1953,7 +2018,7 @@ bool AddToBlockIndex(CBlock& block, CValidationState& state, const CDiskBlockPos
return state.Abort(_("Failed to write block index")); return state.Abort(_("Failed to write block index"));
// New best? // New best?
if (!ConnectBestBlock(state)) if (!ActivateBestChain(state))
return false; return false;
if (pindexNew == chainActive.Tip()) if (pindexNew == chainActive.Tip())
@ -2277,8 +2342,11 @@ bool ProcessBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBl
return state.Invalid(error("ProcessBlock() : already have block (orphan) %s", hash.ToString())); return state.Invalid(error("ProcessBlock() : already have block (orphan) %s", hash.ToString()));
// Preliminary checks // Preliminary checks
if (!CheckBlock(*pblock, state)) if (!CheckBlock(*pblock, state)) {
if (state.CorruptionPossible())
mapAlreadyAskedFor.erase(CInv(MSG_BLOCK, hash));
return error("ProcessBlock() : CheckBlock FAILED"); return error("ProcessBlock() : CheckBlock FAILED");
}
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex);
if (pcheckpoint && pblock->hashPrevBlock != (chainActive.Tip() ? chainActive.Tip()->GetBlockHash() : uint256(0))) if (pcheckpoint && pblock->hashPrevBlock != (chainActive.Tip() ? chainActive.Tip()->GetBlockHash() : uint256(0)))
@ -3007,24 +3075,6 @@ bool static AlreadyHave(const CInv& inv)
} }
void Misbehaving(NodeId pnode, int howmuch)
{
if (howmuch == 0)
return;
CNodeState *state = State(pnode);
if (state == NULL)
return;
state->nMisbehavior += howmuch;
if (state->nMisbehavior >= GetArg("-banscore", 100))
{
LogPrintf("Misbehaving: %s (%d -> %d) BAN THRESHOLD EXCEEDED\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior);
state->fShouldBan = true;
} else
LogPrintf("Misbehaving: %s (%d -> %d)\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior);
}
void static ProcessGetData(CNode* pfrom) void static ProcessGetData(CNode* pfrom)
{ {
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin(); std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
@ -3587,18 +3637,11 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
pfrom->AddInventoryKnown(inv); pfrom->AddInventoryKnown(inv);
LOCK(cs_main); LOCK(cs_main);
// Remember who we got this block from.
mapBlockSource[inv.hash] = pfrom->GetId();
CValidationState state; CValidationState state;
if (ProcessBlock(state, pfrom, &block) || state.CorruptionPossible()) ProcessBlock(state, pfrom, &block);
mapAlreadyAskedFor.erase(inv);
int nDoS = 0;
if (state.IsInvalid(nDoS))
{
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
state.GetRejectReason(), inv.hash);
if (nDoS > 0)
Misbehaving(pfrom->GetId(), nDoS);
}
} }
@ -4045,16 +4088,21 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
if (!lockMain) if (!lockMain)
return true; return true;
if (State(pto->GetId())->fShouldBan) { CNodeState &state = *State(pto->GetId());
if (state.fShouldBan) {
if (pto->addr.IsLocal()) if (pto->addr.IsLocal())
LogPrintf("Warning: not banning local node %s!\n", pto->addr.ToString()); LogPrintf("Warning: not banning local node %s!\n", pto->addr.ToString());
else { else {
pto->fDisconnect = true; pto->fDisconnect = true;
CNode::Ban(pto->addr); CNode::Ban(pto->addr);
} }
State(pto->GetId())->fShouldBan = false; state.fShouldBan = false;
} }
BOOST_FOREACH(const CBlockReject& reject, state.rejects)
pto->PushMessage("reject", (string)"block", reject.chRejectCode, reject.strRejectReason, reject.hashBlock);
state.rejects.clear();
// Start block sync // Start block sync
if (pto->fStartSync && !fImporting && !fReindex) { if (pto->fStartSync && !fImporting && !fReindex) {
pto->fStartSync = false; pto->fStartSync = false;

View file

@ -165,10 +165,8 @@ bool IsInitialBlockDownload();
std::string GetWarnings(std::string strFor); std::string GetWarnings(std::string strFor);
/** Retrieve a transaction (from memory pool, or from disk, if possible) */ /** Retrieve a transaction (from memory pool, or from disk, if possible) */
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock, bool fAllowSlow = false); bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock, bool fAllowSlow = false);
/** Connect/disconnect blocks until pindexNew is the new tip of the active block chain */
bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew);
/** Find the best known block, and make it the tip of the block chain */ /** Find the best known block, and make it the tip of the block chain */
bool ConnectBestBlock(CValidationState &state); bool ActivateBestChain(CValidationState &state);
int64_t GetBlockValue(int nHeight, int64_t nFees); int64_t GetBlockValue(int nHeight, int64_t nFees);
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock); unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock);
@ -716,6 +714,8 @@ public:
unsigned int nBits; unsigned int nBits;
unsigned int nNonce; unsigned int nNonce;
// (memory only) Sequencial id assigned to distinguish order in which blocks are received.
uint32_t nSequenceId;
CBlockIndex() CBlockIndex()
{ {
@ -729,6 +729,7 @@ public:
nTx = 0; nTx = 0;
nChainTx = 0; nChainTx = 0;
nStatus = 0; nStatus = 0;
nSequenceId = 0;
nVersion = 0; nVersion = 0;
hashMerkleRoot = 0; hashMerkleRoot = 0;
@ -749,6 +750,7 @@ public:
nTx = 0; nTx = 0;
nChainTx = 0; nChainTx = 0;
nStatus = 0; nStatus = 0;
nSequenceId = 0;
nVersion = block.nVersion; nVersion = block.nVersion;
hashMerkleRoot = block.hashMerkleRoot; hashMerkleRoot = block.hashMerkleRoot;
@ -958,23 +960,23 @@ public:
AbortNode(msg); AbortNode(msg);
return Error(); return Error();
} }
bool IsValid() { bool IsValid() const {
return mode == MODE_VALID; return mode == MODE_VALID;
} }
bool IsInvalid() { bool IsInvalid() const {
return mode == MODE_INVALID; return mode == MODE_INVALID;
} }
bool IsError() { bool IsError() const {
return mode == MODE_ERROR; return mode == MODE_ERROR;
} }
bool IsInvalid(int &nDoSOut) { bool IsInvalid(int &nDoSOut) const {
if (IsInvalid()) { if (IsInvalid()) {
nDoSOut = nDoS; nDoSOut = nDoS;
return true; return true;
} }
return false; return false;
} }
bool CorruptionPossible() { bool CorruptionPossible() const {
return corruptionPossible; return corruptionPossible;
} }
unsigned char GetRejectCode() const { return chRejectCode; } unsigned char GetRejectCode() const { return chRejectCode; }
@ -1041,6 +1043,8 @@ public:
/** The currently-connected chain of blocks. */ /** The currently-connected chain of blocks. */
extern CChain chainActive; extern CChain chainActive;
/** The currently best known chain of headers (some of which may be invalid). */
extern CChain chainMostWork;
/** Global variable that points to the active CCoinsView (protected by cs_main) */ /** Global variable that points to the active CCoinsView (protected by cs_main) */
extern CCoinsViewCache *pcoinsTip; extern CCoinsViewCache *pcoinsTip;