Reported by @rustyrussell on irc:
<rusty> Latest master branch, bitcoind in regtest mode:
bitcoind: validation.cpp:4203: void CheckBlockIndex(const Consensus::Params&): Assertion `(pindexFirstNeverProcessed != nullptr) == (pindex->nChainTx == 0)' failed.
<rusty> Pretty sure that was a .bitcoind dir from an older bitcoind.
I can reproduce this error with the following simple test case:
class RustyAssertTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def run_test(self):
self.log.info("initialise chain by activating segwit")
self.restart_node(0, ["-vbparams=segwit:0:999999999999"])
self.nodes[0].generate(500)
assert_equal(get_bip9_status(self.nodes[0], 'segwit')['status'], 'active')
self.log.info("restart with segwit always active")
self.restart_node(0)
or by hand by running:
$ rm -rf ~/.bitcoin/regtest
$ ./bitcoind -regtest -vbparams=segwit:0:999999999999 -daemon
$ ./bitcoin-cli -regtest generate 500
$ ./bitcoin-cli -regtest stop
$ ./bitcoind -regtest
bitcoind: validation.cpp:4213: void CheckBlockIndex(const Consensus::Params&): Assertion `(pindexFirstNeverProcessed != nullptr) == (pindex->nChainTx == 0)' failed.
Aborted
I believe what's happening is:
- bitcoind is invoking RewindBlockIndex at startup, and seeing that segwit is immediately active (due to #11389), but it doesn't have witness data stored (because the blocks were generated with segwit only activating following bip9)
- so the blocks are disconnected and then their validity is reduced, by setting nTx and nChainTx both to 0
- once that's done, RewindBlockIndex calls CheckBlockIndex which is where the assertion fails
- pindexFirstNeverProcessed gets set quickly, because nTx is mostly 0, and it stays set provided there's a subnode, of which there should be plenty
- the assertion then fails when it hits a block where nChainTx != 0
- and that happens as soon as it gets to a block where segwit had activated under bip9 rules: since segwit had activated, that block was stored with witness data, and the validation reduction didn't occur, leaving nChainTx as whatever it had been -- this should be block 433 on regtest i think (144 blocks of started, 144 blocks active voting, 144 blocks locked in, and 1 block active)
I don't think this bug can be hit on mainnet or testnet -- running an old client will either not recognise segwit at all and never store any witness data (so nChainTx won't be non-zero), or will see segwit started at the exact same block the current client does (so won't reduce the validation of any blocks). It also shouldn't impact most future bip 9 (or similar) deployments, as most of those presumably won't need to change the storage format.
So I think this might just warrant a cleaner error message rather than handling it properly. Perhaps RewindBlockIndex's test should change from:
if (IsWitnessEnabled(pprev) && !BLOCK_OPT_WITNESS && !chainActive) { ... }
to something like:
if (IsWitnessEnabled(pprev)) {
if (!BLOCK_OPT_WITNESS && !chainActive) {
...
} else if (BLOCK_OPT_WITNESS && pprev->nChainTx == 0) {
LogPrintf("segwit activation height has changed, cannot reuse blockchain");
return false;
}
}