def GetPeerStatus(self, request, context): # request parsing channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if not request.channel else request.channel logging.debug( f"rs service GetPeerStatus peer_id({request.peer_id}) group_id({request.group_id})" ) # get stub of target peer peer_manager = ObjectManager( ).rs_service.channel_manager.get_peer_manager(channel_name) peer = peer_manager.get_peer(request.peer_id) if peer is not None: peer_stub_manager = peer_manager.get_peer_stub_manager(peer) if peer_stub_manager is not None: try: response = peer_stub_manager.call_in_times( "GetStatus", loopchain_pb2.StatusRequest( request="get peer status from rs", channel=channel_name)) if response is not None: return response except Exception as e: logging.warning(f"fail GetStatus... ({e})") return loopchain_pb2.StatusReply(status="", block_height=0, total_tx=0)
def __find_highest_peer(self, group_id) -> PeerInfo: # 강제로 list 를 적용하여 값을 복사한 다음 사용한다. (중간에 값이 변경될 때 발생하는 오류를 방지하기 위해서) most_height = 0 most_height_peer = None for peer_id in list(self.peer_list[group_id]): peer_each = self.peer_list[group_id][peer_id] stub_manager = peer_each.stub_manager try: response = stub_manager.call( "GetStatus", loopchain_pb2.StatusRequest(request="find highest peer"), is_stub_reuse=True) peer_status = json.loads(response.status) if int(peer_status["block_height"]) >= most_height: most_height = int(peer_status["block_height"]) most_height_peer = peer_each except Exception as e: logging.warning("gRPC Exception: " + str(e)) if len(self.peer_list[group_id] ) == 0 and group_id != conf.ALL_GROUP_ID: del self.peer_list[group_id] del self.peer_object_list[group_id] return most_height_peer
def complain_leader(self, group_id=None, is_announce=False) -> PeerInfo: """When current leader is offline, Find last height alive peer and set as a new leader. :param complain_peer: :param group_id: :param is_announce: :return: """ if group_id is None: group_id = conf.ALL_GROUP_ID leader_peer = self.get_leader_peer(group_id=group_id, is_peer=False) try: stub_manager = self.get_peer_stub_manager(leader_peer, group_id) response = stub_manager.call("GetStatus", loopchain_pb2.StatusRequest(request=""), is_stub_reuse=True) status_json = json.loads(response.status) logging.warning(f"stub_manager target({stub_manager.target}) type({status_json['peer_type']})") if status_json["peer_type"] == str(loopchain_pb2.BLOCK_GENERATOR): return leader_peer else: raise Exception except Exception as e: new_leader = self.__find_highest_peer(group_id=group_id) if new_leader is not None: # 변경된 리더를 announce 해야 한다 logging.warning("Change peer to leader that complain old leader.") self.set_leader_peer(new_leader, None) return new_leader
def broadcast_getstatus(self): """peer 들의 접속 상태를 확인하기 위해서 status 조회를 broadcast 로 모든 peer 에 전달한다. """ logging.info("BroadCast GetStatus....") if self.__common_service is not None: self.__common_service.broadcast( "GetStatus", (loopchain_pb2.StatusRequest( request="BlockGenerator BroadCast")))
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"]) stub.target = target 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 GetPeerStatus(self, request, context): # request parsing logging.debug(f"rs service GetPeerStatus peer_id({request.peer_id}) group_id({request.group_id})") # get stub of target peer peer_stub_manager = self.__peer_manager.get_peer_stub_manager(self.__peer_manager.get_peer(request.peer_id)) if peer_stub_manager is not None: try: response = peer_stub_manager.call_in_times( "GetStatus", loopchain_pb2.StatusRequest(request="get peer status from rs")) if response is not None: return response except Exception as e: logging.warning(f"fail GetStatus... ({e})") return loopchain_pb2.StatusReply(status="", block_height=0, total_tx=0)
def __reset_peers_in_group(self, group_id, reset_action): # 강제로 list 를 적용하여 값을 복사한 다음 사용한다. (중간에 값이 변경될 때 발생하는 오류를 방지하기 위해서) for peer_id in list(self.peer_list[group_id]): peer_each = self.peer_list[group_id][peer_id] stub_manager = self.get_peer_stub_manager(peer_each, group_id) try: stub_manager.call("GetStatus", loopchain_pb2.StatusRequest(request="reset peers in group"), is_stub_reuse=True) except Exception as e: logging.warning("gRPC Exception: " + str(e)) logging.debug("remove this peer(target): " + str(peer_each.target)) self.remove_peer(peer_each.peer_id, group_id) if reset_action is not None: reset_action(peer_each.peer_id, peer_each.target) if len(self.peer_list[group_id]) == 0 and group_id != conf.ALL_GROUP_ID: del self.peer_list[group_id] del self.peer_object_list[group_id]
def block_height_sync(self, target_peer_stub=None): """block height sync with other peers """ if self.__block_height_sync_lock is True: # ***** 이 보정 프로세스는 AnnounceConfirmBlock 메시지를 받았을때 블럭 Height 차이로 Peer 가 처리하지 못한 경우에도 진행한다. # 따라서 이미 sync 가 진행 중일때의 요청은 무시한다. logging.warning("block height sync is already running...") return peer_target = ObjectManager().peer_service.peer_target peer_manager = ObjectManager( ).peer_service.channel_manager.get_peer_manager(self.__channel_name) block_manager = ObjectManager( ).peer_service.channel_manager.get_block_manager(self.__channel_name) self.__block_height_sync_lock = True if target_peer_stub is None: target_peer_stub = peer_manager.get_leader_stub_manager() ### Block Height 보정 작업, Peer의 데이타 동기화 Process ### ### Love&Hate Algorithm ### logging.info("try block height sync...with love&hate") # Make Peer Stub List [peer_stub, ...] and get max_height of network max_height = 0 peer_stubs = [] target_list = list(peer_manager.get_IP_of_peers_in_group()) for peer_target_each in target_list: target = ":".join(peer_target_each.split(":")[1:]) if target != peer_target: logging.debug(f"try to target({target})") channel = grpc.insecure_channel(target) stub = loopchain_pb2_grpc.PeerServiceStub(channel) try: response = stub.GetStatus( loopchain_pb2.StatusRequest( request="", channel=self.__channel_name)) 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("Already bad.... I don't love you" + str(e)) if len(peer_stubs) == 0: util.logger.warning( f"peer_service:block_height_sync there is no other peer to height sync!" ) self.__block_height_sync_lock = False return my_height = block_manager.get_blockchain().block_height if max_height > my_height: # 자기가 가장 높은 블럭일때 처리 필요 TODO logging.info( f"You need block height sync to: {max_height} yours: {my_height}" ) # 자기(현재 Peer)와 다르면 Peer 목록을 순회하며 마지막 block 에서 자기 Height Block 까지 역순으로 요청한다. # (blockchain 의 block 탐색 로직 때문에 height 순으로 조회는 비효율적이다.) preload_blocks = {} # height : block dictionary # Target Peer 의 마지막 block hash 부터 시작한다. response = target_peer_stub.call( "GetLastBlockHash", loopchain_pb2.StatusRequest(request="", channel=self.__channel_name)) logging.debug(response) request_hash = response.block_hash max_try = max_height - my_height while block_manager.get_blockchain().last_block.block_hash \ != request_hash and max_try > 0: for peer_stub in peer_stubs: response = None try: # 이때 요청 받은 Peer 는 해당 Block 과 함께 자신의 현재 Height 를 같이 보내준다. # TODO target peer 의 마지막 block 보다 높은 Peer 가 있으면 현재 target height 까지 완료 후 # TODO Height Sync 를 다시 한다. response = peer_stub.BlockSync( loopchain_pb2.BlockSyncRequest( block_hash=request_hash, channel=self.__channel_name), conf.GRPC_TIMEOUT) except Exception as e: logging.warning("There is a bad peer, I hate you: " + str(e)) if response is not None and response.response_code == message_code.Response.success: util.logger.spam( f"response block_height({response.block_height})") dump = response.block block = pickle.loads(dump) # 마지막 블럭에서 역순으로 블럭을 구한다. request_hash = block.prev_block_hash # add block to preload_blocks logging.debug("Add preload_blocks Height: " + str(block.height)) preload_blocks[block.height] = block if response.max_block_height > max_height: max_height = response.max_block_height if (my_height + 1) == block.height: max_try = 0 # 더이상 요청을 진행하지 않는다. logging.info("Block Height Sync Complete.") break max_try -= 1 else: # 이 반복 요청중 응답 하지 않은 Peer 는 반복중에 다시 요청하지 않는다. # (TODO: 향후 Bad에 대한 리포트 전략은 별도로 작업한다.) peer_stubs.remove(peer_stub) logging.warning( "Make this peer to bad (error above or no response): " + str(peer_stub)) if preload_blocks.__len__() > 0: while my_height < max_height: add_height = my_height + 1 logging.debug("try add block height: " + str(add_height)) try: block_manager.add_block(preload_blocks[add_height]) my_height = add_height except KeyError as e: logging.error("fail block height sync: " + str(e)) break except exception.BlockError as e: logging.error( "Block Error Clear all block and restart peer.") block_manager.clear_all_blocks() util.exit_and_msg( "Block Error Clear all block and restart peer.") if my_height < max_height: # block height sync 가 완료되지 않았으면 다시 시도한다. logging.warning( "fail block height sync in one time... try again...") self.__block_height_sync_lock = False self.block_height_sync(target_peer_stub) self.__block_height_sync_lock = False