Skip to content

Commit 9bae47d

Browse files
committed
[validation] RewindBlockIndex no longer needed
Instead of rewinding blocks, we request that the user deletes blocks dir and chain state dir to IBD
1 parent ea5a50f commit 9bae47d

File tree

4 files changed

+58
-153
lines changed

4 files changed

+58
-153
lines changed

src/init.cpp

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,26 +1686,24 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
16861686
break;
16871687
}
16881688

1689-
bool failed_rewind{false};
1690-
// Can't hold cs_main while calling RewindBlockIndex, so retrieve the relevant
1691-
// chainstates beforehand.
1692-
for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
1693-
if (!fReset) {
1694-
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
1695-
// It both disconnects blocks based on the chainstate, and drops block data in
1696-
// BlockIndex() based on lack of available witness data.
1697-
uiInterface.InitMessage(_("Rewinding blocks...").translated);
1698-
if (!chainstate->RewindBlockIndex(chainparams)) {
1699-
strLoadError = _(
1700-
"Unable to rewind the database to a pre-fork state. "
1701-
"You will need to redownload the blockchain");
1702-
failed_rewind = true;
1703-
break; // out of the per-chainstate loop
1689+
bool needs_ibd{false};
1690+
{
1691+
LOCK(cs_main);
1692+
for (CChainState* chainstate : chainman.GetAll()) {
1693+
if (!fReset) {
1694+
uiInterface.InitMessage(_("Checking if IBD is needed...").translated);
1695+
if (chainstate->NeedsIBD(chainparams)) {
1696+
strLoadError = _(
1697+
"Segwit blocks are insufficiently validated. "
1698+
"Please delete blocks dir and chain state dir and restart.");
1699+
needs_ibd = true;
1700+
break; // out of the per-chainstate loop
1701+
}
17041702
}
17051703
}
17061704
}
17071705

1708-
if (failed_rewind) {
1706+
if (needs_ibd) {
17091707
break; // out of the chainstate activation do-while
17101708
}
17111709

src/validation.cpp

Lines changed: 9 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -4390,143 +4390,22 @@ bool CChainState::ReplayBlocks(const CChainParams& params)
43904390
return true;
43914391
}
43924392

4393-
//! Helper for CChainState::RewindBlockIndex
4394-
void CChainState::EraseBlockData(CBlockIndex* index)
4393+
bool CChainState::NeedsIBD(const CChainParams& params)
43954394
{
43964395
AssertLockHeld(cs_main);
4397-
assert(!m_chain.Contains(index)); // Make sure this block isn't active
4398-
4399-
// Reduce validity
4400-
index->nStatus = std::min<unsigned int>(index->nStatus & BLOCK_VALID_MASK, BLOCK_VALID_TREE) | (index->nStatus & ~BLOCK_VALID_MASK);
4401-
// Remove have-data flags.
4402-
index->nStatus &= ~(BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO);
4403-
// Remove storage location.
4404-
index->nFile = 0;
4405-
index->nDataPos = 0;
4406-
index->nUndoPos = 0;
4407-
// Remove various other things
4408-
index->nTx = 0;
4409-
index->nChainTx = 0;
4410-
index->nSequenceId = 0;
4411-
// Make sure it gets written.
4412-
setDirtyBlockIndex.insert(index);
4413-
// Update indexes
4414-
setBlockIndexCandidates.erase(index);
4415-
auto ret = m_blockman.m_blocks_unlinked.equal_range(index->pprev);
4416-
while (ret.first != ret.second) {
4417-
if (ret.first->second == index) {
4418-
m_blockman.m_blocks_unlinked.erase(ret.first++);
4419-
} else {
4420-
++ret.first;
4421-
}
4422-
}
4423-
// Mark parent as eligible for main chain again
4424-
if (index->pprev && index->pprev->IsValid(BLOCK_VALID_TRANSACTIONS) && index->pprev->HaveTxsDownloaded()) {
4425-
setBlockIndexCandidates.insert(index->pprev);
4426-
}
4427-
}
4428-
4429-
bool CChainState::RewindBlockIndex(const CChainParams& params)
4430-
{
4431-
// Note that during -reindex-chainstate we are called with an empty m_chain!
44324396

4433-
// First erase all post-segwit blocks without witness not in the main chain,
4434-
// as this can we done without costly DisconnectTip calls. Active
4435-
// blocks will be dealt with below (releasing cs_main in between).
4436-
{
4437-
LOCK(cs_main);
4438-
for (const auto& entry : m_blockman.m_block_index) {
4439-
if (IsWitnessEnabled(entry.second->pprev, params.GetConsensus()) && !(entry.second->nStatus & BLOCK_OPT_WITNESS) && !m_chain.Contains(entry.second)) {
4440-
EraseBlockData(entry.second);
4441-
}
4442-
}
4443-
}
4397+
// At and above params.SegwitHeight, segwit consensus rules must be validated
4398+
CBlockIndex* block = m_chain[params.GetConsensus().SegwitHeight];
44444399

4445-
// Find what height we need to reorganize to.
4446-
CBlockIndex *tip;
4447-
int nHeight = 1;
4448-
{
4449-
LOCK(cs_main);
4450-
while (nHeight <= m_chain.Height()) {
4451-
// Although SCRIPT_VERIFY_WITNESS is now generally enforced on all
4452-
// blocks in ConnectBlock, we don't need to go back and
4453-
// re-download/re-verify blocks from before segwit actually activated.
4454-
if (IsWitnessEnabled(m_chain[nHeight - 1], params.GetConsensus()) && !(m_chain[nHeight]->nStatus & BLOCK_OPT_WITNESS)) {
4455-
break;
4456-
}
4457-
nHeight++;
4458-
}
4459-
4460-
tip = m_chain.Tip();
4461-
}
4462-
// nHeight is now the height of the first insufficiently-validated block, or tipheight + 1
4463-
4464-
BlockValidationState state;
4465-
// Loop until the tip is below nHeight, or we reach a pruned block.
4466-
while (!ShutdownRequested()) {
4467-
{
4468-
LOCK(cs_main);
4469-
LOCK(m_mempool.cs);
4470-
// Make sure nothing changed from under us (this won't happen because RewindBlockIndex runs before importing/network are active)
4471-
assert(tip == m_chain.Tip());
4472-
if (tip == nullptr || tip->nHeight < nHeight) break;
4473-
if (fPruneMode && !(tip->nStatus & BLOCK_HAVE_DATA)) {
4474-
// If pruning, don't try rewinding past the HAVE_DATA point;
4475-
// since older blocks can't be served anyway, there's
4476-
// no need to walk further, and trying to DisconnectTip()
4477-
// will fail (and require a needless reindex/redownload
4478-
// of the blockchain).
4479-
break;
4480-
}
4481-
4482-
// Disconnect block
4483-
if (!DisconnectTip(state, params, nullptr)) {
4484-
return error("RewindBlockIndex: unable to disconnect block at height %i (%s)", tip->nHeight, state.ToString());
4485-
}
4486-
4487-
// Reduce validity flag and have-data flags.
4488-
// We do this after actual disconnecting, otherwise we'll end up writing the lack of data
4489-
// to disk before writing the chainstate, resulting in a failure to continue if interrupted.
4490-
// Note: If we encounter an insufficiently validated block that
4491-
// is on m_chain, it must be because we are a pruning node, and
4492-
// this block or some successor doesn't HAVE_DATA, so we were unable to
4493-
// rewind all the way. Blocks remaining on m_chain at this point
4494-
// must not have their validity reduced.
4495-
EraseBlockData(tip);
4496-
4497-
tip = tip->pprev;
4498-
}
4499-
// Make sure the queue of validation callbacks doesn't grow unboundedly.
4500-
LimitValidationInterfaceQueue();
4501-
4502-
// Occasionally flush state to disk.
4503-
if (!FlushStateToDisk(params, state, FlushStateMode::PERIODIC)) {
4504-
LogPrintf("RewindBlockIndex: unable to flush state to disk (%s)\n", state.ToString());
4505-
return false;
4506-
}
4507-
}
4508-
4509-
{
4510-
LOCK(cs_main);
4511-
if (m_chain.Tip() != nullptr) {
4512-
// We can't prune block index candidates based on our tip if we have
4513-
// no tip due to m_chain being empty!
4514-
PruneBlockIndexCandidates();
4515-
4516-
CheckBlockIndex(params.GetConsensus());
4517-
4518-
// FlushStateToDisk can possibly read ::ChainActive(). Be conservative
4519-
// and skip it here, we're about to -reindex-chainstate anyway, so
4520-
// it'll get called a bunch real soon.
4521-
BlockValidationState state;
4522-
if (!FlushStateToDisk(params, state, FlushStateMode::ALWAYS)) {
4523-
LogPrintf("RewindBlockIndex: unable to flush state to disk (%s)\n", state.ToString());
4524-
return false;
4525-
}
4400+
while (block != nullptr) {
4401+
if (!(block->nStatus & BLOCK_OPT_WITNESS)) {
4402+
// block is insufficiently validated for a segwit client
4403+
return true;
45264404
}
4405+
block = m_chain.Next(block);
45274406
}
45284407

4529-
return true;
4408+
return false;
45304409
}
45314410

45324411
void CChainState::UnloadBlockIndex() {

src/validation.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ class CChainState
684684

685685
/** Replay blocks that aren't fully applied to the database. */
686686
bool ReplayBlocks(const CChainParams& params);
687-
bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main);
687+
[[nodiscard]] bool NeedsIBD(const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
688688
bool LoadGenesisBlock(const CChainParams& chainparams);
689689

690690
void PruneBlockIndexCandidates();
@@ -730,9 +730,6 @@ class CChainState
730730

731731
bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
732732

733-
//! Mark a block as not having block data
734-
void EraseBlockData(CBlockIndex* index) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
735-
736733
friend ChainstateManager;
737734
};
738735

test/functional/p2p_segwit.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"""Test segwit transactions and blocks on P2P network."""
66
from decimal import Decimal
77
import math
8+
import os
89
import random
10+
import shutil
911
import struct
1012
import time
1113

@@ -83,6 +85,7 @@
8385
softfork_active,
8486
hex_str_to_bytes,
8587
assert_raises_rpc_error,
88+
get_datadir_path,
8689
)
8790

8891
# The versionbit bit used to signal activation of SegWit
@@ -1956,21 +1959,49 @@ def test_non_standard_witness(self):
19561959
def test_upgrade_after_activation(self):
19571960
"""Test the behavior of starting up a segwit-aware node after the softfork has activated."""
19581961

1959-
self.restart_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)])
1962+
# All nodes are caught up and node 2 is a pre-segwit node that will soon upgrade.
1963+
assert_equal(self.nodes[0].getblockcount(), self.nodes[2].getblockcount())
1964+
assert_equal(self.nodes[1].getblockcount(), self.nodes[2].getblockcount())
1965+
assert SEGWIT_HEIGHT < self.nodes[2].getblockcount()
1966+
assert softfork_active(self.nodes[0], 'segwit')
1967+
assert softfork_active(self.nodes[1], 'segwit')
1968+
assert 'segwit' not in self.nodes[2].getblockchaininfo()['softforks']
1969+
1970+
# Restarting node 2 should result in a shutdown because the blockchain consists of
1971+
# insufficiently validated blocks per segwit consensus rules.
1972+
self.stop_node(2)
1973+
with self.nodes[2].assert_debug_log(expected_msgs=["Segwit blocks are insufficiently validated. Please delete blocks dir and chain state dir and restart."], timeout=10):
1974+
self.nodes[2].start(["-segwitheight={}".format(SEGWIT_HEIGHT)])
1975+
1976+
# As directed, the user deletes the blocks and chainstate directories and restarts the node
1977+
datadir = get_datadir_path(self.options.tmpdir, 2)
1978+
blocks_dir = os.path.join(datadir, "regtest", "blocks")
1979+
chainstate_dir = os.path.join(datadir, "regtest", "chainstate")
1980+
shutil.rmtree(blocks_dir)
1981+
shutil.rmtree(chainstate_dir)
1982+
1983+
self.start_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)])
1984+
assert_equal(self.nodes[2].getblockcount(), 0)
19601985
self.connect_nodes(0, 2)
19611986

19621987
# We reconnect more than 100 blocks, give it plenty of time
19631988
self.sync_blocks(timeout=240)
19641989

1965-
# Make sure that this peer thinks segwit has activated.
1990+
# The upgraded node syncs headers and performs IBD
1991+
assert_equal(self.nodes[0].getblockcount(), self.nodes[2].getblockcount())
1992+
assert_equal(self.nodes[1].getblockcount(), self.nodes[2].getblockcount())
1993+
assert softfork_active(self.nodes[0], 'segwit')
1994+
assert softfork_active(self.nodes[1], 'segwit')
19661995
assert softfork_active(self.nodes[2], 'segwit')
19671996

1968-
# Make sure this peer's blocks match those of node0.
1997+
# Make sure all peers have the same blocks.
19691998
height = self.nodes[2].getblockcount()
19701999
while height >= 0:
19712000
block_hash = self.nodes[2].getblockhash(height)
19722001
assert_equal(block_hash, self.nodes[0].getblockhash(height))
2002+
assert_equal(block_hash, self.nodes[1].getblockhash(height))
19732003
assert_equal(self.nodes[0].getblock(block_hash), self.nodes[2].getblock(block_hash))
2004+
assert_equal(self.nodes[1].getblock(block_hash), self.nodes[2].getblock(block_hash))
19742005
height -= 1
19752006

19762007
@subtest # type: ignore

0 commit comments

Comments
 (0)