From 9e851450abc5495db2938eadbcde869f1d981689 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 27 Jan 2018 23:37:43 +0000 Subject: [PATCH] Adjust rewind logic to use the network upgrade mechanism --- src/chain.h | 14 +++++++++++- src/init.cpp | 2 +- src/main.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++---------- src/main.h | 6 +++++- src/txdb.cpp | 1 + 5 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/chain.h b/src/chain.h index 28f77e006..c0239d5d2 100644 --- a/src/chain.h +++ b/src/chain.h @@ -95,9 +95,13 @@ enum BlockStatus: uint32_t { 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 + BLOCK_ACTIVATES_UPGRADE = 128, //! block activates a network upgrade }; +//! Short-hand for the highest consensus validity we implement. +//! Blocks with this validity are assumed to satisfy all consensus rules. +static const BlockStatus BLOCK_VALID_CONSENSUS = BLOCK_VALID_SCRIPTS; + /** The block chain is a tree shaped structure starting with the * genesis block at the root, with each block potentially having multiple * candidates to be the next block. A blockindex may have multiple pprev pointing @@ -142,6 +146,11 @@ public: //! Verification status of this block. See enum BlockStatus unsigned int nStatus; + //! Branch ID corresponding to the consensus rules used to validate this block. + //! Only accurate if block validity is BLOCK_VALID_CONSENSUS. + //! Persisted at each activation height, memory-only for intervening blocks. + uint32_t nConsensusBranchId; + //! The anchor for the tree state up to the start of this block uint256 hashAnchor; @@ -182,6 +191,7 @@ public: nTx = 0; nChainTx = 0; nStatus = 0; + nConsensusBranchId = 0; hashAnchor = uint256(); hashAnchorEnd = uint256(); nSequenceId = 0; @@ -343,6 +353,8 @@ public: READWRITE(VARINT(nDataPos)); if (nStatus & BLOCK_HAVE_UNDO) READWRITE(VARINT(nUndoPos)); + if (nStatus & BLOCK_ACTIVATES_UPGRADE) + READWRITE(nConsensusBranchId); READWRITE(hashAnchor); // block header diff --git a/src/init.cpp b/src/init.cpp index e45fe1659..b8143d178 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1425,7 +1425,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) 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"); + strLoadError = _("Unable to rewind the database to a pre-upgrade state. You will need to redownload the blockchain"); break; } } diff --git a/src/main.cpp b/src/main.cpp index 8464c9247..9aed20d64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,6 +13,7 @@ #include "chainparams.h" #include "checkpoints.h" #include "checkqueue.h" +#include "consensus/upgrades.h" #include "consensus/validation.h" #include "deprecation.h" #include "init.h" @@ -2222,6 +2223,17 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin pindex->nStatus |= BLOCK_HAVE_UNDO; } + // Now that all consensus rules have been validated, set nConsensusBranchId. + // Move this if BLOCK_VALID_CONSENSUS is ever altered. + static_assert(BLOCK_VALID_CONSENSUS == BLOCK_VALID_SCRIPTS, + "nConsensusBranchId must be set after all consensus rules have been validated."); + if (IsActivationHeightForAnyUpgrade(pindex->nHeight, Params().GetConsensus())) { + pindex->nStatus |= BLOCK_ACTIVATES_UPGRADE; + pindex->nConsensusBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus()); + } else if (pindex->pprev) { + pindex->nConsensusBranchId = pindex->pprev->nConsensusBranchId; + } + pindex->RaiseValidity(BLOCK_VALID_SCRIPTS); setDirtyBlockIndex.insert(pindex); } @@ -2855,9 +2867,6 @@ 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); @@ -3565,6 +3574,13 @@ bool static LoadBlockIndexDB() pindex->nChainSproutValue = pindex->nSproutValue; } } + // Construct in-memory chain of branch IDs. + // Relies on invariant: a block that does not activate a network upgrade + // will always be valid under the same consensus rules as its parent. + // Activation blocks will have non-zero branch IDs (read from disk). + if (pindex->IsValid(BLOCK_VALID_CONSENSUS) && pindex->nConsensusBranchId == 0 && pindex->pprev) { + pindex->nConsensusBranchId = pindex->pprev->nConsensusBranchId; + } if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == NULL)) setBlockIndexCandidates.insert(pindex); if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork)) @@ -3752,9 +3768,25 @@ bool RewindBlockIndex(const CChainParams& params) { LOCK(cs_main); + // RewindBlockIndex is called after LoadBlockIndex, so at this point every block + // index will have nConsensusBranchId set based on the values previously persisted + // to disk. By definition, a non-zero nConsensusBranchId means that the block was + // fully-validated under the corresponding consensus rules. Thus we can quickly + // identify whether the current active chain matches our expected sequence of + // consensus rule changes, with two checks: + // + // - BLOCK_ACTIVATES_UPGRADE is set only on blocks that activate upgrades. + // - nConsensusBranchId for each block matches what we expect. + auto sufficientlyValidated = [¶ms](const CBlockIndex* pindex) { + auto consensus = params.GetConsensus(); + bool fFlagSet = pindex->nStatus & BLOCK_ACTIVATES_UPGRADE; + bool fFlagExpected = IsActivationHeightForAnyUpgrade(pindex->nHeight, consensus); + return fFlagSet == fFlagExpected && pindex->nConsensusBranchId == CurrentEpochBranchId(pindex->nHeight, consensus); + }; + int nHeight = 1; while (nHeight <= chainActive.Height()) { - if (IsWitnessEnabled(chainActive[nHeight - 1], params.GetConsensus()) && !(chainActive[nHeight]->nStatus & BLOCK_OPT_WITNESS)) { + if (!sufficientlyValidated(chainActive[nHeight])) { break; } nHeight++; @@ -3791,24 +3823,31 @@ bool RewindBlockIndex(const CChainParams& params) // 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)) { + if (!sufficientlyValidated(pindexIter) && !chainActive.Contains(pindexIter)) { // Reduce validity - pindexIter->nStatus = std::min(pindexIter->nStatus & BLOCK_VALID_MASK, BLOCK_VALID_TREE) | (pindexIter->nStatus & ~BLOCK_VALID_MASK); - // Remove have-data flags. + pindexIter->nStatus = + std::min(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. + // Remove branch ID + pindexIter->nStatus &= ~BLOCK_ACTIVATES_UPGRADE; + pindexIter->nConsensusBranchId = 0; + // Remove storage location pindexIter->nFile = 0; pindexIter->nDataPos = 0; pindexIter->nUndoPos = 0; // Remove various other things pindexIter->nTx = 0; pindexIter->nChainTx = 0; + pindexIter->nSproutValue = boost::none; + pindexIter->nChainSproutValue = boost::none; pindexIter->nSequenceId = 0; - // Make sure it gets written. + // Make sure it gets written setDirtyBlockIndex.insert(pindexIter); - // Update indexes + // Update indices setBlockIndexCandidates.erase(pindexIter); - std::pair::iterator, std::multimap::iterator> ret = mapBlocksUnlinked.equal_range(pindexIter->pprev); + auto ret = mapBlocksUnlinked.equal_range(pindexIter->pprev); while (ret.first != ret.second) { if (ret.first->second == pindexIter) { mapBlocksUnlinked.erase(ret.first++); diff --git a/src/main.h b/src/main.h index c8378e4e9..03b0464eb 100644 --- a/src/main.h +++ b/src/main.h @@ -436,7 +436,11 @@ bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBloc -/** When there are blocks in the active chain with missing data, rewind the chainstate and remove them from the block index */ +/** + * When there are blocks in the active chain with missing data (e.g. if the + * activation height and branch ID of a particular upgrade have been altered), + * rewind the chainstate and remove them from the block index. + */ bool RewindBlockIndex(const CChainParams& params); class CBlockFileInfo diff --git a/src/txdb.cpp b/src/txdb.cpp index e1e29d9ac..ec0234ba2 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -309,6 +309,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts() pindexNew->nNonce = diskindex.nNonce; pindexNew->nSolution = diskindex.nSolution; pindexNew->nStatus = diskindex.nStatus; + pindexNew->nConsensusBranchId = diskindex.nConsensusBranchId; pindexNew->nTx = diskindex.nTx; pindexNew->nSproutValue = diskindex.nSproutValue;