Add rewind logic to deal with post-fork software updates
Includes logic for dealing with pruning by Suhas Daftuar.
This commit is contained in:
parent
b7dbeb24eb
commit
6032f6930a
4 changed files with 124 additions and 17 deletions
|
@ -144,6 +144,8 @@ enum BlockStatus: uint32_t {
|
|||
BLOCK_FAILED_VALID = 32, //! stage after last reached validness failed
|
||||
BLOCK_FAILED_CHILD = 64, //! descends from failed block
|
||||
BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD,
|
||||
|
||||
BLOCK_OPT_WITNESS = 128, //! block data in blk*.data was received with a witness-enforcing client
|
||||
};
|
||||
|
||||
/** The block chain is a tree shaped structure starting with the
|
||||
|
|
|
@ -1284,6 +1284,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||
break;
|
||||
}
|
||||
|
||||
if (!fReindex) {
|
||||
uiInterface.InitMessage(_("Rewinding blocks..."));
|
||||
if (!RewindBlockIndex(chainparams)) {
|
||||
strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uiInterface.InitMessage(_("Verifying blocks..."));
|
||||
if (fHavePruned && GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
|
||||
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks",
|
||||
|
|
128
src/main.cpp
128
src/main.cpp
|
@ -658,6 +658,9 @@ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& loc
|
|||
CBlockIndex* pindex = (*mi).second;
|
||||
if (chain.Contains(pindex))
|
||||
return pindex;
|
||||
if (pindex->GetAncestor(chain.Height()) == chain.Tip()) {
|
||||
return chain.Tip();
|
||||
}
|
||||
}
|
||||
}
|
||||
return chain.Genesis();
|
||||
|
@ -2777,7 +2780,7 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) {
|
|||
}
|
||||
|
||||
/** Disconnect chainActive's tip. You probably want to call mempool.removeForReorg and manually re-limit mempool size after this, with cs_main held. */
|
||||
bool static DisconnectTip(CValidationState& state, const CChainParams& chainparams)
|
||||
bool static DisconnectTip(CValidationState& state, const CChainParams& chainparams, bool fBare = false)
|
||||
{
|
||||
CBlockIndex *pindexDelete = chainActive.Tip();
|
||||
assert(pindexDelete);
|
||||
|
@ -2797,24 +2800,28 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara
|
|||
// Write the chain state to disk, if necessary.
|
||||
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
|
||||
return false;
|
||||
// Resurrect mempool transactions from the disconnected block.
|
||||
std::vector<uint256> vHashUpdate;
|
||||
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
|
||||
// ignore validation errors in resurrected transactions
|
||||
list<CTransaction> removed;
|
||||
CValidationState stateDummy;
|
||||
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, true)) {
|
||||
mempool.removeRecursive(tx, removed);
|
||||
} else if (mempool.exists(tx.GetHash())) {
|
||||
vHashUpdate.push_back(tx.GetHash());
|
||||
|
||||
if (!fBare) {
|
||||
// Resurrect mempool transactions from the disconnected block.
|
||||
std::vector<uint256> vHashUpdate;
|
||||
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
|
||||
// ignore validation errors in resurrected transactions
|
||||
list<CTransaction> removed;
|
||||
CValidationState stateDummy;
|
||||
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, true)) {
|
||||
mempool.removeRecursive(tx, removed);
|
||||
} else if (mempool.exists(tx.GetHash())) {
|
||||
vHashUpdate.push_back(tx.GetHash());
|
||||
}
|
||||
}
|
||||
// AcceptToMemoryPool/addUnchecked all assume that new mempool entries have
|
||||
// no in-mempool children, which is generally not true when adding
|
||||
// previously-confirmed transactions back to the mempool.
|
||||
// UpdateTransactionsFromBlock finds descendants of any transactions in this
|
||||
// block that were added back and cleans up the mempool state.
|
||||
mempool.UpdateTransactionsFromBlock(vHashUpdate);
|
||||
}
|
||||
// AcceptToMemoryPool/addUnchecked all assume that new mempool entries have
|
||||
// no in-mempool children, which is generally not true when adding
|
||||
// previously-confirmed transactions back to the mempool.
|
||||
// UpdateTransactionsFromBlock finds descendants of any transactions in this
|
||||
// block that were added back and cleans up the mempool state.
|
||||
mempool.UpdateTransactionsFromBlock(vHashUpdate);
|
||||
|
||||
// Update chainActive and related variables.
|
||||
UpdateTip(pindexDelete->pprev, chainparams);
|
||||
// Let wallets know transactions went from 1-confirmed to
|
||||
|
@ -3266,6 +3273,9 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
|
|||
pindexNew->nDataPos = pos.nPos;
|
||||
pindexNew->nUndoPos = 0;
|
||||
pindexNew->nStatus |= BLOCK_HAVE_DATA;
|
||||
if (IsWitnessEnabled(pindexNew->pprev, Params().GetConsensus())) {
|
||||
pindexNew->nStatus |= BLOCK_OPT_WITNESS;
|
||||
}
|
||||
pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS);
|
||||
setDirtyBlockIndex.insert(pindexNew);
|
||||
|
||||
|
@ -4214,6 +4224,90 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool RewindBlockIndex(const CChainParams& params)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
|
||||
int nHeight = 1;
|
||||
while (nHeight <= chainActive.Height()) {
|
||||
if (IsWitnessEnabled(chainActive[nHeight - 1], params.GetConsensus()) && !(chainActive[nHeight]->nStatus & BLOCK_OPT_WITNESS)) {
|
||||
break;
|
||||
}
|
||||
nHeight++;
|
||||
}
|
||||
|
||||
// nHeight is now the height of the first insufficiently-validated block, or tipheight + 1
|
||||
CValidationState state;
|
||||
CBlockIndex* pindex = chainActive.Tip();
|
||||
while (chainActive.Height() >= nHeight) {
|
||||
if (fPruneMode && !(chainActive.Tip()->nStatus & BLOCK_HAVE_DATA)) {
|
||||
// If pruning, don't try rewinding past the HAVE_DATA point;
|
||||
// since older blocks can't be served anyway, there's
|
||||
// no need to walk further, and trying to DisconnectTip()
|
||||
// will fail (and require a needless reindex/redownload
|
||||
// of the blockchain).
|
||||
break;
|
||||
}
|
||||
if (!DisconnectTip(state, params, true)) {
|
||||
return error("RewindBlockIndex: unable to disconnect block at height %i", pindex->nHeight);
|
||||
}
|
||||
// Occasionally flush state to disk.
|
||||
if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reduce validity flag and have-data flags.
|
||||
// We do this after actual disconnecting, otherwise we'll end up writing the lack of data
|
||||
// to disk before writing the chainstate, resulting in a failure to continue if interrupted.
|
||||
for (BlockMap::iterator it = mapBlockIndex.begin(); it != mapBlockIndex.end(); it++) {
|
||||
CBlockIndex* pindexIter = it->second;
|
||||
|
||||
// Note: If we encounter an insufficiently validated block that
|
||||
// is on chainActive, it must be because we are a pruning node, and
|
||||
// this block or some successor doesn't HAVE_DATA, so we were unable to
|
||||
// rewind all the way. Blocks remaining on chainActive at this point
|
||||
// must not have their validity reduced.
|
||||
if (IsWitnessEnabled(pindexIter->pprev, params.GetConsensus()) && !(pindexIter->nStatus & BLOCK_OPT_WITNESS) && !chainActive.Contains(pindexIter)) {
|
||||
// Reduce validity
|
||||
pindexIter->nStatus = std::min<unsigned int>(pindexIter->nStatus & BLOCK_VALID_MASK, BLOCK_VALID_TREE) | (pindexIter->nStatus & ~BLOCK_VALID_MASK);
|
||||
// Remove have-data flags.
|
||||
pindexIter->nStatus &= ~(BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO);
|
||||
// Remove storage location.
|
||||
pindexIter->nFile = 0;
|
||||
pindexIter->nDataPos = 0;
|
||||
pindexIter->nUndoPos = 0;
|
||||
// Remove various other things
|
||||
pindexIter->nTx = 0;
|
||||
pindexIter->nChainTx = 0;
|
||||
pindexIter->nSequenceId = 0;
|
||||
// Make sure it gets written.
|
||||
setDirtyBlockIndex.insert(pindexIter);
|
||||
// Update indexes
|
||||
setBlockIndexCandidates.erase(pindexIter);
|
||||
std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> ret = mapBlocksUnlinked.equal_range(pindexIter->pprev);
|
||||
while (ret.first != ret.second) {
|
||||
if (ret.first->second == pindexIter) {
|
||||
mapBlocksUnlinked.erase(ret.first++);
|
||||
} else {
|
||||
++ret.first;
|
||||
}
|
||||
}
|
||||
} else if (pindexIter->IsValid(BLOCK_VALID_TRANSACTIONS) && pindexIter->nChainTx) {
|
||||
setBlockIndexCandidates.insert(pindexIter);
|
||||
}
|
||||
}
|
||||
|
||||
PruneBlockIndexCandidates();
|
||||
|
||||
CheckBlockIndex(params.GetConsensus());
|
||||
|
||||
if (!FlushStateToDisk(state, FLUSH_STATE_ALWAYS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnloadBlockIndex()
|
||||
{
|
||||
LOCK(cs_main);
|
||||
|
|
|
@ -459,6 +459,9 @@ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams,
|
|||
/** Check whether witness commitments are required for block. */
|
||||
bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params);
|
||||
|
||||
/** When there are blocks in the active chain with missing data, rewind the chainstate and remove them from the block index */
|
||||
bool RewindBlockIndex(const CChainParams& params);
|
||||
|
||||
/** Update uncommitted block structures (currently: only the witness nonce). This is safe for submitted blocks. */
|
||||
void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams);
|
||||
|
||||
|
|
Loading…
Reference in a new issue