def test_fork_recovery_rollbacked_already(self): # Switching to the new chain works, but test that if the _rollback() has already happened, _fork_recovery() does # not hiccup # Mock out irrelevant functions self.chain_manager._update_block_number_mapping = Mock() # Switching to the new chain should succeed! self.chain_manager.add_chain = Mock(return_value=True) self.chain_manager._rollback = Mock() block_1 = create_m_block(1, self.genesis_block, alice.address) block_2 = create_m_block(2, block_1, alice.address) block_1_alt = create_m_block(1, self.genesis_block, alice.address) block_2_alt = create_m_block(2, block_1_alt, alice.address) block_3_alt = create_m_block(3, block_2_alt, alice.address) fork_state = qrlstateinfo_pb2.ForkState( initiator_headerhash=block_3_alt.headerhash, fork_point_headerhash=self.genesis_block.headerhash, old_mainchain_hash_path=[b.headerhash for b in [block_1, block_2]], new_mainchain_hash_path=[ b.headerhash for b in [block_1_alt, block_2_alt, block_3_alt] ]) # State.get_block() should say that we are already on block_1_alt self.chain_manager._state.get_block.return_value = block_1_alt # _fork_recovery() will not call _rollback(), because it has already happened. self.chain_manager._fork_recovery(block_3_alt, fork_state) # _fork_recovery() should have _rollback()ed when trying to switch to the longer chain self.chain_manager._rollback.assert_not_called()
def get_fork_state(self) -> Optional[qrlstateinfo_pb2.ForkState]: try: data = self._db.get_raw(b'fork_state') fork_state = qrlstateinfo_pb2.ForkState() fork_state.ParseFromString(bytes(data)) return fork_state except KeyError: return None except Exception as e: logger.error('Exception in get_fork_state') logger.exception(e) raise
def test_fork_state(self): fork_state = qrlstateinfo_pb2.ForkState( initiator_headerhash=b'block2_right', fork_point_headerhash=b'block0_base_of_fork', old_mainchain_hash_path=[b'block1_right', b'block2_right'], new_mainchain_hash_path=[b'block1_left', b'block2_left']) self.assertIsNone(self.state.get_fork_state()) self.state.put_fork_state(fork_state) self.assertEqual(fork_state, self.state.get_fork_state()) self.state.delete_fork_state() self.assertIsNone(self.state.get_fork_state())
def _try_branch_add_block(self, block, batch, check_stale=True) -> (bool, bool): """ This function returns list of bool types. The first bool represent if the block has been added successfully and the second bool represent the fork_flag, which becomes true when a block triggered into fork recovery. :param block: :param batch: :return: [Added successfully, fork_flag] """ if self._last_block.headerhash == block.prev_headerhash: if not self._apply_block(block, batch): return False, False self._state.put_block(block, batch) last_block_metadata = self._state.get_block_metadata( self._last_block.headerhash) if last_block_metadata is None: logger.warning("Could not find log metadata for %s", bin2hstr(self._last_block.headerhash)) return False, False last_block_difficulty = int( UInt256ToString(last_block_metadata.cumulative_difficulty)) new_block_metadata = self._add_block_metadata(block.headerhash, block.timestamp, block.prev_headerhash, batch) new_block_difficulty = int( UInt256ToString(new_block_metadata.cumulative_difficulty)) if new_block_difficulty > last_block_difficulty: if self._last_block.headerhash != block.prev_headerhash: fork_state = qrlstateinfo_pb2.ForkState( initiator_headerhash=block.headerhash) self._state.put_fork_state(fork_state, batch) self._state.write_batch(batch) return self._fork_recovery(block, fork_state), True self._update_chainstate(block, batch) if check_stale: self.tx_pool.check_stale_txn(self._state, block.block_number) self.trigger_miner = True return True, False
def test_add_chain_fails_if_apply_block_fails(self): block_1 = create_m_block(1, self.genesis_block, alice.address) block_2 = create_m_block(2, block_1, alice.address) block_1_alt = create_m_block(1, self.genesis_block, alice.address) block_2_alt = create_m_block(2, block_1_alt, alice.address) block_3_alt = create_m_block(3, block_2_alt, alice.address) fork_state = qrlstateinfo_pb2.ForkState( initiator_headerhash=block_3_alt.headerhash, fork_point_headerhash=self.genesis_block.headerhash, old_mainchain_hash_path=[b.headerhash for b in [block_1, block_2]], new_mainchain_hash_path=[ b.headerhash for b in [block_1_alt, block_2_alt, block_3_alt] ]) # we want to add_chain(block_*_alt chain), but for some reason applying a Block to the State didn't work. self.chain_manager._apply_block = Mock(return_value=False) ans = self.chain_manager.add_chain( [block_1_alt.headerhash, block_2_alt.headerhash], fork_state) self.assertFalse(ans)
def test_add_chain_fails_if_fork_recovery_didnt_complete_successfully( self): block_1 = create_m_block(1, self.genesis_block, alice.address) block_2 = create_m_block(2, block_1, alice.address) block_1_alt = create_m_block(1, self.genesis_block, alice.address) block_2_alt = create_m_block(2, block_1_alt, alice.address) block_3_alt = create_m_block(3, block_2_alt, alice.address) fork_state = qrlstateinfo_pb2.ForkState( initiator_headerhash=block_3_alt.headerhash, fork_point_headerhash=self.genesis_block.headerhash, old_mainchain_hash_path=[b.headerhash for b in [block_1, block_2]], new_mainchain_hash_path=[ b.headerhash for b in [block_1_alt, block_2_alt, block_3_alt] ]) # We want to add_chain(block_*_alt chain), but we're still on block_1 (we should have rolled back to genesis) self.chain_manager._last_block = block_1 ans = self.chain_manager.add_chain( [block_1_alt.headerhash, block_2_alt.headerhash], fork_state) self.assertFalse(ans)
def test_fork_recovery_failed(self): # When switching to the longer chain fails, _fork_recovery() must _rollback and restore the shorter chain. # Mock out irrelevant functions self.chain_manager._update_block_number_mapping = Mock() # Switching to the new chain should fail! self.chain_manager.add_chain = Mock(return_value=False) self.chain_manager._rollback = Mock() block_1 = create_m_block(1, self.genesis_block, alice.address) block_2 = create_m_block(2, block_1, alice.address) block_1_alt = create_m_block(1, self.genesis_block, alice.address) block_2_alt = create_m_block(2, block_1_alt, alice.address) block_3_alt = create_m_block(3, block_2_alt, alice.address) fork_state = qrlstateinfo_pb2.ForkState( initiator_headerhash=block_3_alt.headerhash, fork_point_headerhash=self.genesis_block.headerhash, old_mainchain_hash_path=[b.headerhash for b in [block_1, block_2]], new_mainchain_hash_path=[ b.headerhash for b in [block_1_alt, block_2_alt, block_3_alt] ]) # _fork_recovery() will _rollback() to the genesis block and go on the longer chain. # At this point, _rollback() should return the old hash path as a backup # in case switching to the longer chain fails. self.chain_manager._rollback.return_value = [ block_2.headerhash, block_1.headerhash ] self.chain_manager._fork_recovery(block_3_alt, fork_state) # _fork_recovery() should have _rollback()ed when trying to switch to the longer chain self.chain_manager._rollback.assert_any_call( self.genesis_block.headerhash, fork_state) # _fork_recovery() should have _rollback()ed to the genesis block when trying to restore the shorter chain self.chain_manager._rollback.assert_called_with( self.genesis_block.headerhash)