Skip to content

Commit 19a56d1

Browse files
committed
Merge bitcoin#21009: Remove RewindBlockIndex logic
d831e71 [validation] RewindBlockIndex no longer needed (Dhruv Mehta) Pull request description: Closes bitcoin#17862 Context from [original comment](bitcoin#17862 (comment)) (minor edits): `RewindBlockIndex()` is a mechanism to allow nodes to be upgraded after segwit activation, while still keeping their chainstate/datadir in a consistent state. It works as follows: - A pre-segwit (i.e. v0.13.0 or older) node is running. - Segwit activates. The pre-segwit node remains sync'ed to the tip, but is not enforcing the new segwit rules. - The user upgrades the node to a segwit-aware version (v0.13.1 or newer). - On startup, in `AppInitMain()`, `RewindBlockIndex()` is called. This walks the chain backwards from the tip, disconnecting and erasing blocks that from after segwit activation that weren't validated with segwit rules. - those blocks are then redownloaded (with witness data) and validated with segwit rules. This logic probably isn't required any more since: - Segwit activated at height 481824, when the block chain was 130GB and the total number of txs was 250 million. Today, we're at height 667704, the blockchain is over 315GB and the total number of txs is over 600 million. Even if 20% of that added data is witness data (a high estimate), then around 150GB of transactions would need to be rewound to get back to segwit activation height. It'd probably be faster to simply validate from genesis, especially since we won't be validating any scripts before the assumevalid block. It's also unclear whether rewinding 150GB of transactions would even work. It's certainly never been tested. - Bitcoin Core v0.13 is hardly used any more. https://luke.dashjr.org/programs/bitcoin/files/charts/software.html shows less than 50 nodes running it. The software was EOL on Aug 1st 2018. It's very unlikely that anyone is running 0.13 and will want to upgrade to 0.22. This PR introduces `NeedsRedownload()` which merely checks for insufficiently validated segwit blocks and requests that the user restarts the node with `-reindex`. Reindexing the block files upon restart will make the node rebuild chain state and block index from the `blk*.dat` files on disk. The node won't be able to index the blocks with `BLOCK_OPT_WITNESS`, so they will be missing from the chain and be re-downloaded, with witness data. Removing this code allows the following (done in follow-up bitcoin#21090): - removal of tests using `segwitheight=-1` in `p2p_segwit.py`. - in turn, that allows us to drop support for `-segwitheight=-1`, which is only supported for that test. - that allows us to always set `NODE_WITNESS` in our local services. The only reason we don't do that is to support `-segwitheight=-1`. - that in turn allows us to drop all of the `GetLocalServices() & NODE_WITNESS` checks inside `net_processing.cpp`, since our local services would always include `NODE_WITNESS` ACKs for top commit: jnewbery: utACK d831e71 jamesob: ACK bitcoin@d831e71 laanwj: Cursory code review ACK d831e71. Agree with the direction of the change, thanks for simplifying the logic here. glozow: utACK d831e71 Tree-SHA512: 3eddf5121ccd081ad7f15a5c6478ef867083edc8ba0bf1ee759e87bc070ee3d2f0698a3feba8db8dc087987c8452887b6f72cff05b3e178f41cb10a515fb8053
2 parents f8f5552 + d831e71 commit 19a56d1

File tree

4 files changed

+43
-165
lines changed

4 files changed

+43
-165
lines changed

src/init.cpp

+8-20
Original file line numberDiff line numberDiff line change
@@ -1491,29 +1491,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
14911491
break;
14921492
}
14931493

1494-
bool failed_rewind{false};
1495-
// Can't hold cs_main while calling RewindBlockIndex, so retrieve the relevant
1496-
// chainstates beforehand.
1497-
for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
1498-
if (!fReset) {
1499-
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
1500-
// It both disconnects blocks based on the chainstate, and drops block data in
1501-
// BlockIndex() based on lack of available witness data.
1502-
uiInterface.InitMessage(_("Rewinding blocks...").translated);
1503-
if (!chainstate->RewindBlockIndex(chainparams)) {
1504-
strLoadError = _(
1505-
"Unable to rewind the database to a pre-fork state. "
1506-
"You will need to redownload the blockchain");
1507-
failed_rewind = true;
1508-
break; // out of the per-chainstate loop
1509-
}
1494+
if (!fReset) {
1495+
LOCK(cs_main);
1496+
auto chainstates{chainman.GetAll()};
1497+
if (std::any_of(chainstates.begin(), chainstates.end(),
1498+
[&chainparams](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(chainparams); })) {
1499+
strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
1500+
chainparams.GetConsensus().SegwitHeight);
1501+
break;
15101502
}
15111503
}
15121504

1513-
if (failed_rewind) {
1514-
break; // out of the chainstate activation do-while
1515-
}
1516-
15171505
bool failed_verification = false;
15181506

15191507
try {

src/validation.cpp

+10-130
Original file line numberDiff line numberDiff line change
@@ -4289,143 +4289,23 @@ bool CChainState::ReplayBlocks(const CChainParams& params)
42894289
return true;
42904290
}
42914291

4292-
//! Helper for CChainState::RewindBlockIndex
4293-
void CChainState::EraseBlockData(CBlockIndex* index)
4292+
bool CChainState::NeedsRedownload(const CChainParams& params) const
42944293
{
42954294
AssertLockHeld(cs_main);
4296-
assert(!m_chain.Contains(index)); // Make sure this block isn't active
4297-
4298-
// Reduce validity
4299-
index->nStatus = std::min<unsigned int>(index->nStatus & BLOCK_VALID_MASK, BLOCK_VALID_TREE) | (index->nStatus & ~BLOCK_VALID_MASK);
4300-
// Remove have-data flags.
4301-
index->nStatus &= ~(BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO);
4302-
// Remove storage location.
4303-
index->nFile = 0;
4304-
index->nDataPos = 0;
4305-
index->nUndoPos = 0;
4306-
// Remove various other things
4307-
index->nTx = 0;
4308-
index->nChainTx = 0;
4309-
index->nSequenceId = 0;
4310-
// Make sure it gets written.
4311-
setDirtyBlockIndex.insert(index);
4312-
// Update indexes
4313-
setBlockIndexCandidates.erase(index);
4314-
auto ret = m_blockman.m_blocks_unlinked.equal_range(index->pprev);
4315-
while (ret.first != ret.second) {
4316-
if (ret.first->second == index) {
4317-
m_blockman.m_blocks_unlinked.erase(ret.first++);
4318-
} else {
4319-
++ret.first;
4320-
}
4321-
}
4322-
// Mark parent as eligible for main chain again
4323-
if (index->pprev && index->pprev->IsValid(BLOCK_VALID_TRANSACTIONS) && index->pprev->HaveTxsDownloaded()) {
4324-
setBlockIndexCandidates.insert(index->pprev);
4325-
}
4326-
}
4327-
4328-
bool CChainState::RewindBlockIndex(const CChainParams& params)
4329-
{
4330-
// Note that during -reindex-chainstate we are called with an empty m_chain!
43314295

4332-
// First erase all post-segwit blocks without witness not in the main chain,
4333-
// as this can we done without costly DisconnectTip calls. Active
4334-
// blocks will be dealt with below (releasing cs_main in between).
4335-
{
4336-
LOCK(cs_main);
4337-
for (const auto& entry : m_blockman.m_block_index) {
4338-
if (IsWitnessEnabled(entry.second->pprev, params.GetConsensus()) && !(entry.second->nStatus & BLOCK_OPT_WITNESS) && !m_chain.Contains(entry.second)) {
4339-
EraseBlockData(entry.second);
4340-
}
4341-
}
4342-
}
4296+
// At and above params.SegwitHeight, segwit consensus rules must be validated
4297+
CBlockIndex* block{m_chain.Tip()};
4298+
const int segwit_height{params.GetConsensus().SegwitHeight};
43434299

4344-
// Find what height we need to reorganize to.
4345-
CBlockIndex *tip;
4346-
int nHeight = 1;
4347-
{
4348-
LOCK(cs_main);
4349-
while (nHeight <= m_chain.Height()) {
4350-
// Although SCRIPT_VERIFY_WITNESS is now generally enforced on all
4351-
// blocks in ConnectBlock, we don't need to go back and
4352-
// re-download/re-verify blocks from before segwit actually activated.
4353-
if (IsWitnessEnabled(m_chain[nHeight - 1], params.GetConsensus()) && !(m_chain[nHeight]->nStatus & BLOCK_OPT_WITNESS)) {
4354-
break;
4355-
}
4356-
nHeight++;
4357-
}
4358-
4359-
tip = m_chain.Tip();
4360-
}
4361-
// nHeight is now the height of the first insufficiently-validated block, or tipheight + 1
4362-
4363-
BlockValidationState state;
4364-
// Loop until the tip is below nHeight, or we reach a pruned block.
4365-
while (!ShutdownRequested()) {
4366-
{
4367-
LOCK(cs_main);
4368-
LOCK(m_mempool.cs);
4369-
// Make sure nothing changed from under us (this won't happen because RewindBlockIndex runs before importing/network are active)
4370-
assert(tip == m_chain.Tip());
4371-
if (tip == nullptr || tip->nHeight < nHeight) break;
4372-
if (fPruneMode && !(tip->nStatus & BLOCK_HAVE_DATA)) {
4373-
// If pruning, don't try rewinding past the HAVE_DATA point;
4374-
// since older blocks can't be served anyway, there's
4375-
// no need to walk further, and trying to DisconnectTip()
4376-
// will fail (and require a needless reindex/redownload
4377-
// of the blockchain).
4378-
break;
4379-
}
4380-
4381-
// Disconnect block
4382-
if (!DisconnectTip(state, params, nullptr)) {
4383-
return error("RewindBlockIndex: unable to disconnect block at height %i (%s)", tip->nHeight, state.ToString());
4384-
}
4385-
4386-
// Reduce validity flag and have-data flags.
4387-
// We do this after actual disconnecting, otherwise we'll end up writing the lack of data
4388-
// to disk before writing the chainstate, resulting in a failure to continue if interrupted.
4389-
// Note: If we encounter an insufficiently validated block that
4390-
// is on m_chain, it must be because we are a pruning node, and
4391-
// this block or some successor doesn't HAVE_DATA, so we were unable to
4392-
// rewind all the way. Blocks remaining on m_chain at this point
4393-
// must not have their validity reduced.
4394-
EraseBlockData(tip);
4395-
4396-
tip = tip->pprev;
4397-
}
4398-
// Make sure the queue of validation callbacks doesn't grow unboundedly.
4399-
LimitValidationInterfaceQueue();
4400-
4401-
// Occasionally flush state to disk.
4402-
if (!FlushStateToDisk(params, state, FlushStateMode::PERIODIC)) {
4403-
LogPrintf("RewindBlockIndex: unable to flush state to disk (%s)\n", state.ToString());
4404-
return false;
4405-
}
4406-
}
4407-
4408-
{
4409-
LOCK(cs_main);
4410-
if (m_chain.Tip() != nullptr) {
4411-
// We can't prune block index candidates based on our tip if we have
4412-
// no tip due to m_chain being empty!
4413-
PruneBlockIndexCandidates();
4414-
4415-
CheckBlockIndex(params.GetConsensus());
4416-
4417-
// FlushStateToDisk can possibly read ::ChainActive(). Be conservative
4418-
// and skip it here, we're about to -reindex-chainstate anyway, so
4419-
// it'll get called a bunch real soon.
4420-
BlockValidationState state;
4421-
if (!FlushStateToDisk(params, state, FlushStateMode::ALWAYS)) {
4422-
LogPrintf("RewindBlockIndex: unable to flush state to disk (%s)\n", state.ToString());
4423-
return false;
4424-
}
4300+
while (block != nullptr && block->nHeight >= segwit_height) {
4301+
if (!(block->nStatus & BLOCK_OPT_WITNESS)) {
4302+
// block is insufficiently validated for a segwit client
4303+
return true;
44254304
}
4305+
block = block->pprev;
44264306
}
44274307

4428-
return true;
4308+
return false;
44294309
}
44304310

44314311
void CChainState::UnloadBlockIndex() {

src/validation.h

+3-4
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,9 @@ class CChainState
713713

714714
/** Replay blocks that aren't fully applied to the database. */
715715
bool ReplayBlocks(const CChainParams& params);
716-
bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main);
716+
717+
/** Whether the chain state needs to be redownloaded due to lack of witness data */
718+
[[nodiscard]] bool NeedsRedownload(const CChainParams& params) const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
717719
/** Ensures we have a genesis block in the block tree, possibly writing one to disk. */
718720
bool LoadGenesisBlock(const CChainParams& chainparams);
719721

@@ -760,9 +762,6 @@ class CChainState
760762

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

763-
//! Mark a block as not having block data
764-
void EraseBlockData(CBlockIndex* index) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
765-
766765
void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
767766
void InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
768767

test/functional/p2p_segwit.py

+22-11
Original file line numberDiff line numberDiff line change
@@ -1956,22 +1956,33 @@ def test_non_standard_witness(self):
19561956
def test_upgrade_after_activation(self):
19571957
"""Test the behavior of starting up a segwit-aware node after the softfork has activated."""
19581958

1959-
self.restart_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)])
1959+
# All nodes are caught up and node 2 is a pre-segwit node that will soon upgrade.
1960+
for n in range(2):
1961+
assert_equal(self.nodes[n].getblockcount(), self.nodes[2].getblockcount())
1962+
assert softfork_active(self.nodes[n], "segwit")
1963+
assert SEGWIT_HEIGHT < self.nodes[2].getblockcount()
1964+
assert 'segwit' not in self.nodes[2].getblockchaininfo()['softforks']
1965+
1966+
# Restarting node 2 should result in a shutdown because the blockchain consists of
1967+
# insufficiently validated blocks per segwit consensus rules.
1968+
self.stop_node(2)
1969+
with self.nodes[2].assert_debug_log(expected_msgs=[
1970+
f"Witness data for blocks after height {SEGWIT_HEIGHT} requires validation. Please restart with -reindex."], timeout=10):
1971+
self.nodes[2].start([f"-segwitheight={SEGWIT_HEIGHT}"])
1972+
1973+
# As directed, the user restarts the node with -reindex
1974+
self.start_node(2, extra_args=["-reindex", f"-segwitheight={SEGWIT_HEIGHT}"])
1975+
1976+
# With the segwit consensus rules, the node is able to validate only up to SEGWIT_HEIGHT - 1
1977+
assert_equal(self.nodes[2].getblockcount(), SEGWIT_HEIGHT - 1)
19601978
self.connect_nodes(0, 2)
19611979

19621980
# We reconnect more than 100 blocks, give it plenty of time
1981+
# sync_blocks() also verifies the best block hash is the same for all nodes
19631982
self.sync_blocks(timeout=240)
19641983

1965-
# Make sure that this peer thinks segwit has activated.
1966-
assert softfork_active(self.nodes[2], 'segwit')
1967-
1968-
# Make sure this peer's blocks match those of node0.
1969-
height = self.nodes[2].getblockcount()
1970-
while height >= 0:
1971-
block_hash = self.nodes[2].getblockhash(height)
1972-
assert_equal(block_hash, self.nodes[0].getblockhash(height))
1973-
assert_equal(self.nodes[0].getblock(block_hash), self.nodes[2].getblock(block_hash))
1974-
height -= 1
1984+
# The upgraded node should now have segwit activated
1985+
assert softfork_active(self.nodes[2], "segwit")
19751986

19761987
@subtest # type: ignore
19771988
def test_witness_sigops(self):

0 commit comments

Comments
 (0)