This loop structure is repeated a bunch; might be clearer as:
def test_height(self, height, *status, mine=None):
if height > self.height:
assert mine is not None
self.log.info(f"Test status at height {height}...")
self.nodes[mine].generate(height - self.height)
self.sync_blocks()
elif height < self.height:
assert mine is None
self.log.info(f"Roll back to {height}...")
old_block = self.nodes[0].getblockhash(height + 1)
for node in self.nodes:
node.invalidateblock(old_block)
else:
assert height == 0 and self.height == 0
self.log.info(f"Test status at genesis...")
self.height = height
for (i, node), st in zip(enumerate(self.nodes), status):
self.log.debug('Node #{}...'.format(i))
info = node.getblockchaininfo()
assert_equal(info['blocks'], height)
assert_equal(info["softforks"]["testdummy"]["bip8"]["status"], st)
def run_test(self):
self.test_height(0, "defined", "defined", "defined")
# BIP 8 state transitions from "defined" to "started" or "failed" after
# the last block of the retargeting period has been mined. This means
# any new rules apply to transactions currently in the mempool, which
# might be mined in the next block.
#
# The next retargeting period starts at block 144, so nothing should
# happen at 142 and the state should change at 143.
self.test_height(144-2, "defined", "defined", "defined", mine=0)
self.test_height(144-1, "failed", "started", "started", mine=0)
self.log.info("Test status when not signalling...")
self.test_height(144*2-1, "failed", "started", "failed", mine=0)
self.test_height(144*3-1, "failed", "failed", "failed", mine=0)
self.log.info("Test status when signalling...")
# The new branch has unique block hashes, because of the signalling and
# because generate uses a deterministic address that depends on the node
# index.
self.test_height(144-1, "failed", "started", "started")
self.test_height(144*2-1, "failed", "locked_in", "locked_in", mine=2)
self.test_height(144*3-1, "failed", "active", "locked_in", mine=2)
self.test_height(144*4-1, "failed", "active", "active", mine=2)