def __block_height_sync(self): # Make Peer Stub List [peer_stub, ...] and get max_height of network max_height, unconfirmed_block_height, peer_stubs = self.__get_peer_stub_list( ) if self.__blockchain.last_unconfirmed_block is not None: self.candidate_blocks.remove_block( self.__blockchain.last_unconfirmed_block.header.hash) self.__blockchain.last_unconfirmed_block = None my_height = self.__current_block_height() logging.debug( f"in __block_height_sync max_height({max_height}), my_height({my_height})" ) # prevent_next_block_mismatch until last_block_height in block DB. (excludes last_unconfirmed_block_height) self.get_blockchain().prevent_next_block_mismatch( self.__blockchain.block_height + 1) try: if peer_stubs: my_height, max_height = self.__block_request_to_peers_in_sync( peer_stubs, my_height, unconfirmed_block_height, max_height) except Exception as e: logging.warning(f"block_manager.py >>> block_height_sync :: {e}") traceback.print_exc() self.__start_block_height_sync_timer() return False curr_state = self.__channel_service.state_machine.state if curr_state != 'BlockSync': util.logger.info(f"Current state{curr_state} is not BlockSync") return True if my_height >= max_height: util.logger.debug(f"block_manager:block_height_sync is complete.") next_leader = self.__current_last_block().header.next_leader leader_peer = self.__channel_service.peer_manager.get_peer( next_leader.hex_hx()) if next_leader else None if leader_peer: self.__channel_service.peer_manager.set_leader_peer( leader_peer, None) self.epoch = Epoch.new_epoch(leader_peer.peer_id) elif self.epoch.height < my_height: self.epoch = Epoch.new_epoch() self.__channel_service.state_machine.complete_sync() else: logging.warning( f"it's not completed block height synchronization in once ...\n" f"try block_height_sync again... my_height({my_height}) in channel({self.__channel_name})" ) self.__channel_service.state_machine.block_sync() return True
def __add_block(self, block: Block, vote: Vote = None): with self.__add_block_lock: invoke_results = self.__invoke_results.get(block.header.hash.hex(), None) if invoke_results is None: if block.header.height == 0: block, invoke_results = ObjectManager( ).channel_service.genesis_invoke(block) else: block, invoke_results = ObjectManager( ).channel_service.score_invoke(block) try: self.__add_tx_to_block_db(block, invoke_results) ObjectManager().channel_service.score_write_precommit_state( block) except Exception as e: logging.warning(f"blockchain:add_block FAIL " f"channel_service.score_write_precommit_state") raise e finally: self.__invoke_results.pop(block.header.hash, None) next_total_tx = self.__write_block_data(block, vote) self.__last_block = block self.__block_height = self.__last_block.header.height self.__total_tx = next_total_tx logging.debug( f"blockchain add_block set block_height({self.__block_height}), " f"last_block({self.__last_block.header.hash.hex()})") logging.info(f"ADD BLOCK HEIGHT : {block.header.height} , " f"HASH : {block.header.hash.hex()} , " f"CHANNEL : {self.__channel_name}") logging.debug(f"ADDED BLOCK HEADER : {block.header}") util.apm_event( self.__peer_id, { 'event_type': 'AddBlock', 'peer_id': self.__peer_id, 'peer_name': conf.PEER_NAME, 'channel_name': self.__channel_name, 'data': { 'block_height': self.__block_height } }) # stop leader complain timer ObjectManager().channel_service.stop_leader_complain_timer() # start new epoch ObjectManager( ).channel_service.block_manager.epoch = Epoch.new_epoch( block.header.height + 1) # notify new block ObjectManager().channel_service.inner_service.notify_new_block() return True
def confirm_prev_block(self, current_block: Block): self.__blockchain.confirm_prev_block(current_block) # stop leader complain timer self.__channel_service.stop_leader_complain_timer() # start new epoch self.epoch = Epoch.new_epoch()
def reset_leader(self, new_leader_id, block_height=0, complained=False): """ :param new_leader_id: :param block_height: :param complained: :return: """ if self.peer_manager.get_leader_id(conf.ALL_GROUP_ID) == new_leader_id: return utils.logger.info( f"RESET LEADER channel({ChannelProperty().name}) leader_id({new_leader_id}), " f"complained={complained}") leader_peer = self.peer_manager.get_peer(new_leader_id, None) if block_height > 0 and block_height != self.block_manager.get_blockchain( ).last_block.header.height + 1: util.logger.warning( f"height behind peer can not take leader role. block_height({block_height}), " f"last_block.header.height(" f"{self.block_manager.get_blockchain().last_block.header.height})" ) return if leader_peer is None: logging.warning( f"in peer_service:reset_leader There is no peer by peer_id({new_leader_id})" ) return util.logger.spam( f"peer_service:reset_leader target({leader_peer.target}), complained={complained}" ) self_peer_object = self.peer_manager.get_peer( ChannelProperty().peer_id) self.peer_manager.set_leader_peer(leader_peer, None) if complained: self.block_manager.epoch.new_round(leader_peer.peer_id) else: self.block_manager.epoch = Epoch.new_epoch(leader_peer.peer_id) logging.info( f"Epoch height({self.block_manager.epoch.height}), leader ({self.block_manager.epoch.leader_id})" ) if self_peer_object.peer_id == leader_peer.peer_id: logging.debug("Set Peer Type Leader!") peer_type = loopchain_pb2.BLOCK_GENERATOR self.state_machine.turn_to_leader() else: logging.debug("Set Peer Type Peer!") peer_type = loopchain_pb2.PEER self.state_machine.turn_to_peer() self.block_manager.set_peer_type(peer_type)
async def __add_block_and_new_epoch(self, block_builder, last_unconfirmed_block: Block): """Add Block and start new epoch :param block_builder: :param last_unconfirmed_block: :return: next leader """ await self.__add_block(last_unconfirmed_block) self.__remove_duplicate_tx_when_turn_to_leader(block_builder, last_unconfirmed_block) self._block_manager.epoch = Epoch.new_epoch(ChannelProperty().peer_id) return last_unconfirmed_block.header.next_leader
def confirm_prev_block(self, current_block: Block): try: self.__blockchain.confirm_prev_block(current_block) # stop leader complain timer self.__channel_service.stop_leader_complain_timer() # start new epoch self.epoch = Epoch.new_epoch() except BlockchainError as e: logging.warning( f"BlockchainError while confirm_block({e}), retry block_height_sync" ) self.block_height_sync()
def confirm_prev_block(self, current_block: Block): confirmed_block = self.__blockchain.confirm_prev_block(current_block) if confirmed_block is None: return # stop leader complain timer self.__channel_service.stop_leader_complain_timer() # start new epoch if not current_block.header.complained: self.epoch = Epoch.new_epoch() # reset leader self.__channel_service.reset_leader( current_block.header.next_leader.hex_hx())
async def consensus(self): util.logger.debug( f"-------------------consensus " f"candidate_blocks({len(self._block_manager.candidate_blocks.blocks)})" ) async with self.__lock: if self._block_manager.epoch.leader_id != ChannelProperty( ).peer_id: util.logger.warning( f"This peer is not leader. epoch leader={self._block_manager.epoch.leader_id}" ) return self._vote_queue = asyncio.Queue(loop=self._loop) complained_result = self._block_manager.epoch.complained_result block_builder = self._block_manager.epoch.makeup_block( complained_result) vote_result = None last_unconfirmed_block = self._blockchain.last_unconfirmed_block next_leader = ExternalAddress.fromhex(ChannelProperty().peer_id) need_next_call = False try: if complained_result: util.logger.spam("consensus block_builder.complained") """ confirm_info = self._blockchain.find_confirm_info_by_hash(self._blockchain.last_block.header.hash) if not confirm_info and self._blockchain.last_block.header.height > 0: util.logger.spam("Can't make a block as a leader, this peer will be complained too.") return """ self._made_block_count += 1 elif self.made_block_count >= (conf.MAX_MADE_BLOCK_COUNT - 1): if last_unconfirmed_block: await self.__add_block(last_unconfirmed_block) peer_manager = ObjectManager( ).channel_service.peer_manager next_leader = ExternalAddress.fromhex( peer_manager.get_next_leader_peer( current_leader_peer_id=ChannelProperty( ).peer_id).peer_id) else: util.logger.info( f"This leader already made {self.made_block_count} blocks. " f"MAX_MADE_BLOCK_COUNT is {conf.MAX_MADE_BLOCK_COUNT} " f"There is no more right. Consensus loop will return." ) return elif len(block_builder.transactions ) > 0 or conf.ALLOW_MAKE_EMPTY_BLOCK: if last_unconfirmed_block: next_leader = await self.__add_block_and_new_epoch( block_builder, last_unconfirmed_block) elif len(block_builder.transactions) == 0 and ( last_unconfirmed_block and len(last_unconfirmed_block.body.transactions) > 0): next_leader = await self.__add_block_and_new_epoch( block_builder, last_unconfirmed_block) else: need_next_call = True except NotEnoughVotes: need_next_call = True finally: if need_next_call: return self.__block_generation_timer.call() candidate_block = self.__build_candidate_block( block_builder, next_leader, vote_result) candidate_block, invoke_results = ObjectManager( ).channel_service.score_invoke(candidate_block) self._block_manager.set_invoke_results( candidate_block.header.hash.hex(), invoke_results) util.logger.spam(f"candidate block : {candidate_block.header}") self._block_manager.vote_unconfirmed_block( candidate_block.header.hash, True) self._block_manager.candidate_blocks.add_block(candidate_block) self._blockchain.last_unconfirmed_block = candidate_block broadcast_func = partial( self._block_manager.broadcast_send_unconfirmed_block, candidate_block) self.__start_broadcast_send_unconfirmed_block_timer(broadcast_func) if await self._wait_for_voting(candidate_block) is None: return if next_leader.hex_hx() != ChannelProperty().peer_id: util.logger.spam(f"-------------------turn_to_peer " f"next_leader({next_leader.hex_hx()}) " f"peer_id({ChannelProperty().peer_id})") ObjectManager().channel_service.reset_leader( next_leader.hex_hx()) ObjectManager().channel_service.turn_on_leader_complain_timer() else: self._block_manager.epoch = Epoch.new_epoch( next_leader.hex_hx()) if not conf.ALLOW_MAKE_EMPTY_BLOCK: self.__block_generation_timer.call_instantly() else: self.__block_generation_timer.call()
async def consensus(self): util.logger.debug( f"-------------------consensus " f"candidate_blocks({len(self._block_manager.candidate_blocks.blocks)})" ) async with self.__lock: if self._block_manager.epoch.leader_id != ChannelProperty( ).peer_id: util.logger.warning( f"This peer is not leader. epoch leader={self._block_manager.epoch.leader_id}" ) return self._vote_queue = asyncio.Queue(loop=self._loop) complained_result = self._block_manager.epoch.complained_result block_builder = self._block_manager.epoch.makeup_block( complained_result) vote_result = None last_unconfirmed_block = self._blockchain.last_unconfirmed_block next_leader = ExternalAddress.fromhex(ChannelProperty().peer_id) if complained_result: util.logger.spam("consensus block_builder.complained") """ confirm_info = self._blockchain.find_confirm_info_by_hash(self._blockchain.last_block.header.hash) if not confirm_info and self._blockchain.last_block.header.height > 0: util.logger.spam("Can't make a block as a leader, this peer will be complained too.") return """ self._made_block_count += 1 elif len(block_builder.transactions) > 0: util.logger.spam( f"consensus len(block_builder.transactions) > 0") if last_unconfirmed_block: if (len(last_unconfirmed_block.body.transactions) > 0 or last_unconfirmed_block.header.complained) or ( len(last_unconfirmed_block.body.transactions) == 0 and last_unconfirmed_block.header.peer_id.hex_hx() != ChannelProperty().peer_id): vote = self._block_manager.candidate_blocks.get_vote( last_unconfirmed_block.header.hash) vote_result = await self._wait_for_voting( last_unconfirmed_block) if not vote_result: return self.__block_generation_timer.call() self.__add_block(last_unconfirmed_block, vote) self._block_manager.epoch = Epoch.new_epoch( ChannelProperty().peer_id) next_leader = last_unconfirmed_block.header.next_leader else: if (last_unconfirmed_block) and ( len(last_unconfirmed_block.body.transactions) > 0 or last_unconfirmed_block.header.complained): vote = self._block_manager.candidate_blocks.get_vote( last_unconfirmed_block.header.hash) vote_result = await self._wait_for_voting( last_unconfirmed_block) if not vote_result: return self.__block_generation_timer.call() self.__add_block(last_unconfirmed_block, vote) peer_manager = ObjectManager().channel_service.peer_manager next_leader = ExternalAddress.fromhex( peer_manager.get_next_leader_peer( current_leader_peer_id=ChannelProperty().peer_id). peer_id) else: return self.__block_generation_timer.call() candidate_block = self.__build_candidate_block( block_builder, next_leader, vote_result) candidate_block, invoke_results = ObjectManager( ).channel_service.score_invoke(candidate_block) self._block_manager.set_invoke_results( candidate_block.header.hash.hex(), invoke_results) util.logger.spam(f"candidate block : {candidate_block.header}") self._block_manager.vote_unconfirmed_block( candidate_block.header.hash, True) self._block_manager.candidate_blocks.add_block(candidate_block) self._blockchain.last_unconfirmed_block = candidate_block broadcast_func = partial( self._block_manager.broadcast_send_unconfirmed_block, candidate_block) self.__start_broadcast_send_unconfirmed_block_timer(broadcast_func) if await self._wait_for_voting(candidate_block) is None: return if len(candidate_block.body.transactions) == 0 and not conf.ALLOW_MAKE_EMPTY_BLOCK and \ next_leader.hex_hx() != ChannelProperty().peer_id: util.logger.spam(f"-------------------turn_to_peer " f"next_leader({next_leader.hex_hx()}) " f"peer_id({ChannelProperty().peer_id})") ObjectManager().channel_service.reset_leader( next_leader.hex_hx()) else: self._block_manager.epoch = Epoch.new_epoch( next_leader.hex_hx()) if not conf.ALLOW_MAKE_EMPTY_BLOCK: self.__block_generation_timer.call_instantly() else: self.__block_generation_timer.call()