def leader_complain(self): complained_leader_id, new_leader_id = self.get_leader_ids_for_complaint() version = self.blockchain.block_versioner.get_version(self.epoch.height) leader_vote = Vote.get_leader_vote_class(version).new( signer=ChannelProperty().peer_auth, block_height=self.epoch.height, round_=self.epoch.round, old_leader=ExternalAddress.fromhex_address(complained_leader_id), new_leader=ExternalAddress.fromhex_address(new_leader_id), timestamp=util.get_time_stamp() ) util.logger.info( f"LeaderVote : old_leader({complained_leader_id}), new_leader({new_leader_id}), round({self.epoch.round})") self.add_complain(leader_vote) leader_vote_serialized = leader_vote.serialize() leader_vote_dumped = json.dumps(leader_vote_serialized) complain_kwargs = { "complain_vote": leader_vote_dumped, "channel": self.channel_name } if conf.RECOVERY_MODE: complain_kwargs["from_recovery"] = True request = loopchain_pb2.ComplainLeaderRequest(**complain_kwargs) util.logger.debug( f"complained_leader_id({complained_leader_id}), " f"new_leader_id({new_leader_id})") reps_hash = self.blockchain.get_next_reps_hash_by_header(self.blockchain.last_block.header) self.__channel_service.broadcast_scheduler.schedule_broadcast("ComplainLeader", request, reps_hash=reps_hash)
def create_tx(self, data): tx = Transaction() score_id = "" score_version = "" try: score_info = self._channel_service.score_info score_id = score_info[message_code.MetaParams.ScoreInfo.score_id] score_version = score_info[ message_code.MetaParams.ScoreInfo.score_version] except KeyError as e: logging.debug(f"CreateTX : load score info fail\n" f"cause : {e}") send_tx_type = self._channel_service.get_channel_option( )["send_tx_type"] tx.init_meta(ChannelProperty().peer_id, score_id, score_version, ChannelProperty().name, send_tx_type) tx.put_data(data) tx.sign_hash(ChannelProperty().peer_auth) self._channel_service.broadcast_scheduler.schedule_job( BroadcastCommand.CREATE_TX, tx) try: data_log = json.loads(data) except Exception as e: data_log = {'tx_hash': tx.tx_hash} util.apm_event( ChannelProperty().peer_id, { 'event_type': 'CreateTx', 'peer_id': ChannelProperty().peer_id, 'peer_name': conf.PEER_NAME, 'channel_name': ChannelProperty().name, 'tx_hash': tx.tx_hash, 'data': data_log }) return tx.tx_hash
def update_sub_services_properties(self, **properties): stub = StubCollection().channel_tx_creator_stubs[ChannelProperty().name] asyncio.run_coroutine_threadsafe(stub.async_task().update_properties(properties), self.loop) stub = StubCollection().channel_tx_receiver_stubs[ChannelProperty().name] asyncio.run_coroutine_threadsafe(stub.async_task().update_properties(properties), self.loop)
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()
def add_zero_limit_control(self): stub = StubCollection().channel_tx_creator_stubs[ChannelProperty().name] stub.sync_task().add_zero_limit_control()
def get_channel_option(self) -> dict: return conf.CHANNEL_OPTION[ChannelProperty().name]
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()
def __init_consensus(self): consensus = Consensus(self, ChannelProperty().name) self.__consensus = consensus self.__block_manager.consensus = consensus consensus.register_subscriber(self.__block_manager)
def __init_node_subscriber(self): self.__node_subscriber = NodeSubscriber( channel=ChannelProperty().name, rs_target=ChannelProperty().rs_target )
async def _serve(): await StubCollection().create_peer_stub() results = await StubCollection().peer_stub.async_task().get_channel_info_detail(ChannelProperty().name) await self.init(*results) self.__timer_service.start() logging.info(f'channel_service: init complete channel: {ChannelProperty().name}')
async def _select_node_type(self): if self._is_role_switched(): new_node_type = self._get_node_type_by_peer_list() utils.logger.info(f"Role switching to new node type: {new_node_type.name}") ChannelProperty().node_type = new_node_type self.__inner_service.update_sub_services_properties(node_type=ChannelProperty().node_type.value)
async def consensus(self): util.logger.debug( f"-------------------consensus " f"candidate_blocks({len(self._block_manager.candidate_blocks.blocks)})" ) with self.__lock: 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 vote_result = True self._block_manager.epoch.set_epoch_leader( ChannelProperty().peer_id) 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 = vote.get_result( last_unconfirmed_block.header.hash.hex(), conf.VOTING_RATIO) if not vote_result: return self.__block_generation_timer.call() self.__add_block(last_unconfirmed_block, vote) 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 = vote.get_result( last_unconfirmed_block.header.hash.hex(), conf.VOTING_RATIO) 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() last_block = self._blockchain.last_block block_builder.height = last_block.header.height + 1 block_builder.prev_hash = last_block.header.hash block_builder.next_leader = next_leader block_builder.peer_private_key = ObjectManager( ).channel_service.peer_auth.private_key block_builder.confirm_prev_block = vote_result or ( self._made_block_count > 0) candidate_block = block_builder.build() 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}") block_verifier = BlockVerifier.new(candidate_block.header.version, self._blockchain.tx_versioner) block_verifier.verify(candidate_block, self._blockchain.last_block, self._blockchain) 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) # TODO Temporary ignore below line for developing leader complain self.__start_broadcast_send_unconfirmed_block_timer(broadcast_func) if len(block_builder.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})") await ObjectManager().channel_service.reset_leader( next_leader.hex_hx()) else: self.__block_generation_timer.call()
async def consensus(self): util.logger.debug( f"-------------------consensus " f"candidate_blocks({len(self._blockmanager.candidate_blocks.blocks)})" ) with self.__lock: block_builder = self._makeup_block() vote_result = None if len(block_builder.transactions) > 0: # util.logger.debug(f"-------------------consensus logic-1") next_leader = ExternalAddress.fromhex( ChannelProperty().peer_id) if self._blockchain.last_unconfirmed_block: if (len(self._blockchain.last_unconfirmed_block.body. transactions) > 0) or ( len(self._blockchain.last_unconfirmed_block. body.transactions) == 0 and self._blockchain.last_unconfirmed_block.header. peer_id.hex_hx() != ChannelProperty().peer_id): # util.logger.debug(f"-------------------consensus logic-2") vote = self._blockmanager.candidate_blocks.get_vote( self._blockchain.last_unconfirmed_block.header.hash ) vote_result = vote.get_result( self._blockchain.last_unconfirmed_block.header. hash.hex(), conf.VOTING_RATIO) if not vote_result: return self.__block_generation_timer.call() self._blockmanager.add_block( self._blockchain.last_unconfirmed_block, vote) self._made_block_count += 1 next_leader = self._blockchain.last_unconfirmed_block.header.next_leader else: if self._blockchain.last_unconfirmed_block and len( self._blockchain.last_unconfirmed_block.body. transactions) > 0: # util.logger.debug(f"-------------------consensus logic-3") vote = self._blockmanager.candidate_blocks.get_vote( self._blockchain.last_unconfirmed_block.header.hash) vote_result = vote.get_result( self._blockchain.last_unconfirmed_block.header.hash. hex(), conf.VOTING_RATIO) if not vote_result: return self.__block_generation_timer.call() self._blockmanager.add_block( self._blockchain.last_unconfirmed_block, vote) self._made_block_count += 1 peer_manager = ObjectManager().channel_service.peer_manager next_leader = ExternalAddress.fromhex( peer_manager.get_next_leader_peer().peer_id) else: # util.logger.spam(f"tx count in block({len(block_builder.transactions)})") return self.__block_generation_timer.call() last_block = self._blockchain.last_block block_builder.height = last_block.header.height + 1 block_builder.prev_hash = last_block.header.hash block_builder.next_leader = next_leader block_builder.peer_private_key = ObjectManager( ).channel_service.peer_auth.peer_private_key block_builder.confirm_prev_block = vote_result or ( self._made_block_count > 0) candidate_block = block_builder.build() candidate_block, invoke_results = ObjectManager( ).channel_service.score_invoke(candidate_block) self._blockmanager.set_invoke_results( candidate_block.header.hash.hex(), invoke_results) block_verifier = BlockVerifier.new(candidate_block.header.version, self._blockchain.tx_versioner) block_verifier.verify(candidate_block, self._blockchain.last_block, self._blockchain) logging.debug(f"candidate block : {candidate_block.header}") self._blockmanager.vote_unconfirmed_block( candidate_block.header.hash, True) self._blockmanager.candidate_blocks.add_block(candidate_block) self._blockchain.last_unconfirmed_block = candidate_block broadcast_func = partial( self._blockmanager.broadcast_send_unconfirmed_block, candidate_block) self.__start_broadcast_send_unconfirmed_block_timer(broadcast_func) if len(block_builder.transactions) == 0 and not conf.ALLOW_MAKE_EMPTY_BLOCK and \ next_leader.hex() != ChannelProperty().peer_id: # util.logger.debug(f"-------------------turn_to_peer") ObjectManager().channel_service.state_machine.turn_to_peer() else: self.__block_generation_timer.call()
def __get_peer_stub_list(self) -> Tuple[int, int, List[Tuple]]: """It updates peer list for block manager refer to peer list on the loopchain network. This peer list is not same to the peer list of the loopchain network. :return max_height: a height of current blockchain :return unconfirmed_block_height: unconfirmed_block_height on the network :return peer_stubs: current peer list on the network (target, peer_stub) """ max_height = -1 # current max height unconfirmed_block_height = -1 peer_stubs = [] # peer stub list for block height synchronization if not ObjectManager().channel_service.is_support_node_function( conf.NodeFunction.Vote): rs_client = ObjectManager().channel_service.rs_client status_response = rs_client.call(RestMethod.Status) max_height = status_response['block_height'] peer_stubs.append((rs_client.target, rs_client)) return max_height, unconfirmed_block_height, peer_stubs # Make Peer Stub List [peer_stub, ...] and get max_height of network self.__block_height_sync_bad_targets = { k: v for k, v in self.__block_height_sync_bad_targets.items() if v > self.blockchain.block_height } util.logger.info( f"Bad Block Sync Peer : {self.__block_height_sync_bad_targets}") peer_target = ChannelProperty().peer_target my_height = self.blockchain.block_height if self.blockchain.last_block: reps_hash = self.blockchain.get_reps_hash_by_header( self.blockchain.last_block.header) else: reps_hash = ChannelProperty().crep_root_hash rep_targets = self.blockchain.find_preps_targets_by_roothash(reps_hash) target_list = list(rep_targets.values()) for target in target_list: if target == peer_target: continue if target in self.__block_height_sync_bad_targets: continue util.logger.debug(f"try to target({target})") channel = GRPCHelper().create_client_channel(target) stub = loopchain_pb2_grpc.PeerServiceStub(channel) try: response = stub.GetStatus( loopchain_pb2.StatusRequest( request='block_sync', channel=self.__channel_name, ), conf.GRPC_TIMEOUT_SHORT) target_block_height = max(response.block_height, response.unconfirmed_block_height) if target_block_height > my_height: peer_stubs.append((target, stub)) max_height = max(max_height, target_block_height) unconfirmed_block_height = max( unconfirmed_block_height, response.unconfirmed_block_height) except Exception as e: util.logger.warning( f"This peer has already been removed from the block height target node. {e}" ) return max_height, unconfirmed_block_height, peer_stubs
async def __block_height_sync_channel(self): # leader 로 시작하지 않았는데 자신의 정보가 leader Peer 정보이면 block height sync 하여 # 최종 블럭의 leader 를 찾는다. peer_manager = self.peer_manager peer_leader = peer_manager.get_leader_peer() self_peer_object = peer_manager.get_peer(ChannelProperty().peer_id) is_delay_announce_new_leader = False peer_old_leader = None if peer_leader: block_sync_target = peer_leader.target block_sync_target_stub = StubManager.get_stub_manager_to_server( block_sync_target, loopchain_pb2_grpc.PeerServiceStub, time_out_seconds=conf.CONNECTION_RETRY_TIMEOUT, ssl_auth_type=conf.GRPC_SSL_TYPE) else: block_sync_target = ChannelProperty().radio_station_target block_sync_target_stub = self.__radio_station_stub if block_sync_target != ChannelProperty().peer_target: if block_sync_target_stub is None: logging.warning( "You maybe Older from this network... or No leader in this network!" ) is_delay_announce_new_leader = True peer_old_leader = peer_leader peer_leader = self.peer_manager.leader_complain_to_rs( conf.ALL_GROUP_ID, is_announce_new_peer=False) if peer_leader is not None and ChannelProperty( ).node_type == conf.NodeType.CommunityNode: block_sync_target_stub = StubManager.get_stub_manager_to_server( peer_leader.target, loopchain_pb2_grpc.PeerServiceStub, time_out_seconds=conf.CONNECTION_RETRY_TIMEOUT, ssl_auth_type=conf.GRPC_SSL_TYPE) if self.is_support_node_function(conf.NodeFunction.Vote) and \ (not peer_leader or peer_leader.peer_id == ChannelProperty().peer_id): peer_leader = self_peer_object self.block_manager.set_peer_type(loopchain_pb2.BLOCK_GENERATOR) else: _, future = self.block_manager.block_height_sync( block_sync_target_stub) await future if block_sync_target_stub is None: util.exit_and_msg("Fail connect to leader!!") self.show_peers() if block_sync_target_stub is not None and self.is_support_node_function( conf.NodeFunction.Vote): await self.__subscribe_call_to_stub_by_method( block_sync_target_stub, loopchain_pb2.BLOCK_GENERATOR) if is_delay_announce_new_leader: self.peer_manager.announce_new_leader( peer_old_leader.peer_id, peer_leader.peer_id, self_peer_id=ChannelProperty().peer_id)
def __subscribe_call_to_rs_stub(self, rs_rest_stub): response = { 'response_code': message_code.Response.fail, 'message': message_code.get_response_msg(message_code.Response.fail) } try: if conf.REST_SSL_TYPE == conf.SSLAuthType.none: peer_target = ChannelProperty().rest_target else: peer_target = f"https://{ChannelProperty().rest_target}" response = rs_rest_stub.call("Subscribe", { 'channel': ChannelProperty().name, 'peer_target': peer_target }) except Exception as e: logging.warning( f"Due to Subscription fail to RadioStation(mother peer), " f"automatically retrying subscribe call") if response['response_code'] == message_code.Response.success: if TimerService.TIMER_KEY_SUBSCRIBE in self.__timer_service.timer_list.keys( ): self.__timer_service.stop_timer( TimerService.TIMER_KEY_SUBSCRIBE) self.radio_station_stub.update_methods_version() logging.debug( f"Subscription to RadioStation(mother peer) is successful." ) if TimerService.TIMER_KEY_SHUTDOWN_WHEN_FAIL_SUBSCRIBE in self.__timer_service.timer_list.keys( ): self.__timer_service.stop_timer( TimerService.TIMER_KEY_SHUTDOWN_WHEN_FAIL_SUBSCRIBE) # start next get_status timer timer_key = TimerService.TIMER_KEY_GET_LAST_BLOCK_KEEP_CITIZEN_SUBSCRIPTION if timer_key not in self.__timer_service.timer_list.keys(): util.logger.spam( f"add timer for check_block_height_call to radiostation..." ) self.__timer_service.add_timer( timer_key, Timer(target=timer_key, duration=conf.GET_LAST_BLOCK_TIMER, is_repeat=True, callback=self.__check_block_height_call_to_rs_stub, callback_kwargs={"rs_rest_stub": rs_rest_stub})) else: timer_key = TimerService.TIMER_KEY_SHUTDOWN_WHEN_FAIL_SUBSCRIBE if timer_key not in self.__timer_service.timer_list.keys(): error = f"Shutdown by Subscribe retry timeout({conf.SHUTDOWN_TIMER})" self.__timer_service.add_timer( timer_key, Timer(target=timer_key, duration=conf.SHUTDOWN_TIMER, callback=self.__shutdown_peer, callback_kwargs={"message": error})) return response
async def consensus(self): start_time = time.time() empty_block: Block = None try: self._loop = asyncio.get_event_loop() self._vote_queue = asyncio.Queue(loop=self._loop) block_builder = self._makeup_block() if len(block_builder.transactions ) == 0 and not conf.ALLOW_MAKE_EMPTY_BLOCK: return peer_manager = ObjectManager().channel_service.peer_manager last_block = self._blockchain.last_block block_builder.height = last_block.header.height + 1 block_builder.prev_hash = last_block.header.hash block_builder.next_leader = Address.fromhex( peer_manager.get_next_leader_peer().peer_id) block_builder.peer_private_key = ObjectManager( ).channel_service.peer_auth.peer_private_key block_builder.confirm_prev_block = (self._made_block_count > 0) candidate_block = block_builder.build() candidate_block, invoke_results = ObjectManager( ).channel_service.score_invoke(candidate_block) block_verifier = BlockVerifier.new("0.1a") block_verifier.verify(candidate_block, self._blockchain.last_block, self._blockchain) logging.info( f"candidate block height: {candidate_block.header.height}") logging.info( f"candidate block hash: {candidate_block.header.hash.hex()}") logging.info( f"candidate block next leader: {candidate_block.header.next_leader.hex()}" ) logging.info( f"candidate block confirm_prev_block: {candidate_block.body.confirm_prev_block}" ) vote = Vote(candidate_block.header.hash.hex(), ObjectManager().channel_service.peer_manager) vote.add_vote(ChannelProperty().group_id, ChannelProperty().peer_id, True) self._blockmanager.broadcast_send_unconfirmed_block( candidate_block) success = await self._wait_for_voting(candidate_block, vote) if not success: return self._blockmanager.set_invoke_results( candidate_block.header.hash.hex(), invoke_results) self._blockmanager.add_block(candidate_block) self._made_block_count += 1 pending_tx = self._txQueue.get_item_in_status( TransactionStatusInQueue.normal, TransactionStatusInQueue.normal) if not pending_tx and not conf.ALLOW_MAKE_EMPTY_BLOCK: block_builder = BlockBuilder.new("0.1a") block_builder.prev_hash = candidate_block.header.hash block_builder.height = candidate_block.header.height + 1 block_builder.next_leader = candidate_block.header.next_leader block_builder.peer_private_key = ObjectManager( ).channel_service.peer_auth.peer_private_key block_builder.confirm_prev_block = True empty_block = block_builder.build() self._blockmanager.broadcast_send_unconfirmed_block( empty_block) ObjectManager().channel_service.state_machine.turn_to_peer() finally: if not empty_block: elapsed_time = time.time() - start_time delay_time = conf.INTERVAL_BLOCKGENERATION - elapsed_time self._start_consensus_timer(delay_time)
async def _init_rs_client(self): self.__rs_client = RestClient(channel=ChannelProperty().name) await self._init_rs_target(refresh_all=True)
def __init_consensus(self): consensus = Consensus(self, ChannelProperty().name) self.__consensus = consensus self.__block_manager.consensus = consensus consensus.multiple_register(self.__block_manager)
async def consensus(self): util.logger.debug(f"-------------------consensus-------------------") 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}" ) self._vote_queue = asyncio.Queue(loop=self._loop) complain_votes = self.__get_complaint_votes() complained_result = self._block_manager.epoch.complained_result if complained_result: self._blockchain.last_unconfirmed_block = None else: self._block_manager.epoch.remove_duplicate_tx_when_turn_to_leader( ) last_block_vote_list = await self.__get_votes( self._blockchain.latest_block.header.hash) if last_block_vote_list is None: return last_unconfirmed_block: Optional[ Block] = self._blockchain.last_unconfirmed_block last_block_header = self._blockchain.last_block.header if last_block_header.prep_changed: new_term = last_unconfirmed_block is None else: new_term = False if last_unconfirmed_block and not last_block_vote_list and not new_term: return # unrecorded_block means the last block of term to add prep changed block. if last_unconfirmed_block and last_unconfirmed_block.header.prep_changed: first_leader_of_term = self._blockchain.find_preps_ids_by_roothash( last_unconfirmed_block.header.revealed_next_reps_hash)[0] is_unrecorded_block = ChannelProperty( ).peer_address != first_leader_of_term else: is_unrecorded_block = False skip_add_tx = is_unrecorded_block or complained_result block_builder = self._block_manager.epoch.makeup_block( complain_votes, last_block_vote_list, new_term, skip_add_tx) need_next_call = False try: if complained_result or new_term: util.logger.spam( "consensus block_builder.complained or new term") """ 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 """ block_builder = self._makeup_new_block( block_builder.version, complain_votes, self._blockchain.last_block.header.hash) elif self._blockchain.my_made_block_count == ( conf.MAX_MADE_BLOCK_COUNT - 2): # (conf.MAX_MADE_BLOCK_COUNT - 2) means if made_block_count is 8, # but after __add_block, it becomes 9 # so next unconfirmed block height is 10 (last). if last_unconfirmed_block: await self.__add_block(last_unconfirmed_block) else: util.logger.info( f"This leader already made " f"{self._blockchain.my_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 and not conf.ALLOW_MAKE_EMPTY_BLOCK and \ (last_unconfirmed_block and len(last_unconfirmed_block.body.transactions) == 0): need_next_call = True elif last_unconfirmed_block: await self.__add_block(last_unconfirmed_block) except (NotEnoughVotes, InvalidBlock): need_next_call = True except ThereIsNoCandidateBlock: util.logger.warning(f"There is no candidate block.") return finally: if need_next_call: return self.__block_generation_timer.call() util.logger.spam( f"self._block_manager.epoch.leader_id: {self._block_manager.epoch.leader_id}" ) candidate_block = self.__build_candidate_block(block_builder) candidate_block, invoke_results = self._blockchain.score_invoke( candidate_block, self._blockchain.latest_block, is_block_editable=True, is_unrecorded_block=is_unrecorded_block) util.logger.spam(f"candidate block : {candidate_block.header}") self._block_manager.candidate_blocks.add_block( candidate_block, self._blockchain.find_preps_addresses_by_header( candidate_block.header)) self.__broadcast_block(candidate_block) if is_unrecorded_block: self._blockchain.last_unconfirmed_block = None else: self._block_manager.vote_unconfirmed_block( candidate_block, self._block_manager.epoch.round, True) self._blockchain.last_unconfirmed_block = candidate_block try: await self._wait_for_voting(candidate_block) except NotEnoughVotes: return if not candidate_block.header.prep_changed: if (self._blockchain.made_block_count_reached_max( self._blockchain.last_block) or self._block_manager.epoch.leader_id != ChannelProperty().peer_id): ObjectManager().channel_service.reset_leader( self._block_manager.epoch.leader_id) self.__block_generation_timer.call()
def __init_broadcast_scheduler(self): scheduler = BroadcastSchedulerFactory.new(channel=ChannelProperty().name, self_target=ChannelProperty().peer_target) scheduler.start() self.__broadcast_scheduler = scheduler
def __get_role_switch_block_height(self): # Currently, only one way role switch is supported from Citizen to Rep if ChannelProperty().node_type != conf.NodeType.CitizenNode: return -1 return self.get_channel_option().get('role_switch_block_height', -1)
async def __init_sub_services(self): self.__inner_service.init_sub_services() await StubCollection().create_channel_tx_creator_stub(ChannelProperty().name) await StubCollection().create_channel_tx_receiver_stub(ChannelProperty().name)
def _load_peers_from_file(peer_manager: 'PeerManager'): utils.logger.debug(f"load_peers_from_file") channel_info = utils.load_json_data(conf.CHANNEL_MANAGE_DATA_PATH) reps: list = channel_info[ChannelProperty().name].get("peers") for peer in reps: peer_manager.add_peer(peer)
def is_support_node_function(self, node_function): return conf.NodeType.is_support_node_function(node_function, ChannelProperty().node_type)
def __init_peer_auth(self): try: self.__peer_auth = Signer.from_channel(ChannelProperty().name) except Exception as e: logging.exception(f"peer auth init fail cause : {e}") util.exit_and_msg(f"peer auth init fail cause : {e}")
def announce_new_leader(self, complained_leader_id, new_leader_id, is_broadcast=True, self_peer_id=None): """Announce New Leader Id to Network :param complained_leader_id: :param new_leader_id: :param is_broadcast: False(notify to RS only), True(broadcast to network include RS) :param self_peer_id: :return: """ util.logger.spam( f"peer_manager:announce_new_leader channel({self.__channel_name}), " f"complained_leader_id({complained_leader_id}), " f"new_leader_id({new_leader_id}), " f"is_broadcast({is_broadcast})") is_rs = ObjectManager().rs_service is not None announce_message = loopchain_pb2.ComplainLeaderRequest( complained_leader_id=complained_leader_id, channel=self.__channel_name, new_leader_id=new_leader_id, message="Announce New Leader", peer_id=ChannelProperty().peer_id, group_id=ChannelProperty().group_id) # new_leader_peer = self.get_peer(new_leader_id) # Announce New Leader to Radio station try: channel_service = ObjectManager().channel_service if channel_service: response = channel_service.radio_station_stub.call( "AnnounceNewLeader", announce_message) if response.response_code == message_code.Response.fail_no_peer_info_in_rs: util.logger.spam( f"peer_manager:announce_new_leader fail no peer info in rs! is_broadcast({is_broadcast})" ) announce_message.message = message_code.get_response_msg( message_code.Response.fail_no_peer_info_in_rs) ObjectManager().channel_service.connect_to_radio_station( is_reconnect=True) ObjectManager( ).channel_service.broadcast_scheduler.schedule_broadcast( "Request", loopchain_pb2.Message( code=message_code.Request.peer_reconnect_to_rs, channel=self.__channel_name)) except Exception as e: # logging.debug("in RS there is no peer_service....") is_rs = True if is_broadcast is True: for peer_id in list(self.peer_list[conf.ALL_GROUP_ID]): if new_leader_id == peer_id and is_rs is not True: util.logger.spam( f"Prevent reset leader loop in AnnounceNewLeader message" ) continue peer_each = self.peer_list[conf.ALL_GROUP_ID][peer_id] stub_manager = self.get_peer_stub_manager( peer_each, conf.ALL_GROUP_ID) try: stub_manager.call_async("AnnounceNewLeader", announce_message, is_stub_reuse=True) except Exception as e: logging.warning("gRPC Exception: " + str(e)) logging.debug("No response target: " + str(peer_each.target))
def __get_peer_stub_list(self, target_peer_stub=None): """It updates peer list for block manager refer to peer list on the loopchain network. This peer list is not same to the peer list of the loopchain network. :return max_height: a height of current blockchain :return peer_stubs: current peer list on the loopchain network """ peer_target = ChannelProperty().peer_target peer_manager = ObjectManager().channel_service.peer_manager # Make Peer Stub List [peer_stub, ...] and get max_height of network max_height = -1 # current max height peer_stubs = [] # peer stub list for block height synchronization if ObjectManager().channel_service.is_support_node_function( conf.NodeFunction.Vote): target_dict = peer_manager.get_IP_of_peers_dict() target_list = [ peer_target for peer_id, peer_target in target_dict.items() if peer_id != ChannelProperty().peer_id ] else: target_list = [f"{target_peer_stub.target}"] for target in target_list: if target != peer_target: logging.debug(f"try to target({target})") channel = GRPCHelper().create_client_channel(target) stub = loopchain_pb2_grpc.PeerServiceStub(channel) try: if ObjectManager( ).channel_service.is_support_node_function( conf.NodeFunction.Vote): response = stub.GetStatus( loopchain_pb2.StatusRequest( request="", channel=self.__channel_name, ), conf.GRPC_TIMEOUT_SHORT) else: response = target_peer_stub.call("Status") util.logger.spam('{/api/v1/status/peer} response: ' + response.text) response.block_height = int( json.loads(response.text)["block_height"]) response.unconfirmed_block_height = int( json.loads(response.text).get( "unconfirmed_block_height", -1)) stub.target = target response.block_height = max( response.block_height, response.unconfirmed_block_height) if response.block_height > max_height: # Add peer as higher than this max_height = response.block_height peer_stubs.append(stub) except Exception as e: logging.warning( f"This peer has already been removed from the block height target node. {e}" ) return max_height, peer_stubs
def _get_peer_stub_list(self) -> Tuple[int, int, List[Tuple[str, Any]]]: """It updates peer list for block manager refer to peer list on the loopchain network. This peer list is not same to the peer list of the loopchain network. :return max_height: a height of current blockchain :return unconfirmed_block_height: unconfirmed_block_height on the network :return peer_stubs: current peer list on the network (target, peer_stub) """ max_height = -1 # current max height unconfirmed_block_height = -1 peer_stubs = [] # peer stub list for block height synchronization rs_client: RestClient = self._channel_service.rs_client if not self._channel_service.is_support_node_function( conf.NodeFunction.Vote): status_response = rs_client.call(RestMethod.Status) max_height = status_response['block_height'] peer_stubs.append((rs_client.target, rs_client)) return max_height, unconfirmed_block_height, peer_stubs # Make Peer Stub List [peer_stub, ...] and get max_height of network self._block_height_sync_bad_targets = { k: v for k, v in self._block_height_sync_bad_targets.items() if v > self._blockchain.block_height } utils.logger.info( f"Bad Block Sync Peer : {self._block_height_sync_bad_targets}") peer_target = ChannelProperty().peer_target my_height = self._blockchain.block_height port_pattern = re.compile(r":([0-9]{2,5})$") def _converter(target) -> str: port = int(port_pattern.search(target).group(1)) new_port = f":{port + conf.PORT_DIFF_REST_SERVICE_CONTAINER}" return port_pattern.sub(new_port, target) endpoints = { target: _converter(target) for target in self._block_manager.get_target_list() } for grpc_endpoint, rest_endpoint in endpoints.items(): if grpc_endpoint == peer_target: continue if grpc_endpoint in self._block_height_sync_bad_targets: continue utils.logger.debug( f"try to grpc_endpoint({grpc_endpoint}), rest_endpoint({rest_endpoint})" ) channel = GRPCHelper().create_client_channel(grpc_endpoint) stub = loopchain_pb2_grpc.PeerServiceStub(channel) try: client = RestClient(self._block_manager.channel_name, rest_endpoint) response: dict = client.call(RestMethod.Status, timeout=conf.REST_TIMEOUT) target_block_height = max(response["block_height"], response["unconfirmed_block_height"]) recovery = response.get("recovery", {}) # only recovery_mode node should be included in block sync when running by recovery_mode if conf.RECOVERY_MODE and not recovery.get("mode", False): continue if target_block_height > my_height: peer_stubs.append((grpc_endpoint, stub)) max_height = max(max_height, target_block_height) unconfirmed_block_height = max( unconfirmed_block_height, response["unconfirmed_block_height"]) except Exception as e: utils.logger.warning( f"This peer has already been removed from the block height target node. {e!r}" ) return max_height, unconfirmed_block_height, peer_stubs
async def update_dos_properties(self, properties): stub = StubCollection().channel_tx_creator_stubs[ChannelProperty().name] await stub.async_task().update_dos_properties(properties)