def score_write_precommit_state(self, block: Block): logging.debug(f"call score commit {ChannelProperty().name} {block.header.height} {block.header.hash.hex()}") request = { "blockHeight": block.header.height, "blockHash": block.header.hash.hex(), } request = convert_params(request, ParamType.write_precommit_state) stub = StubCollection().icon_score_stubs[ChannelProperty().name] stub.sync_task().write_precommit_state(request) return True
def Subscribe(self, request, context): """BlockGenerator 가 broadcast(unconfirmed or confirmed block) 하는 채널에 Peer 를 등록한다. :param request: :param context: :return: """ channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel if not request.peer_id or not request.peer_target: return loopchain_pb2.CommonReply( response_code=message_code.get_response_code( message_code.Response.fail_wrong_subscribe_info), message=message_code.get_response_msg( message_code.Response.fail_wrong_subscribe_info)) try: channel_stub = StubCollection().channel_stubs[channel_name] except KeyError: return loopchain_pb2.CommonReply( response_code=message_code.get_response_code( message_code.Response.fail), message= f"There is no channel_stubs for channel({channel_name}).") peer_list = [ target['peer_target'] for target in self.peer_service.channel_infos[channel_name]["peers"] ] if (request.peer_target in peer_list and conf.ENABLE_CHANNEL_AUTH) or \ (request.node_type == loopchain_pb2.CommunityNode and not conf.ENABLE_CHANNEL_AUTH): channel_stub.sync_task().add_audience( peer_target=request.peer_target) util.logger.debug( f"peer_outer_service::Subscribe add_audience " f"target({request.peer_target}) in channel({request.channel}), " f"order({request.peer_order})") else: logging.error( f"This target({request.peer_target}, {request.node_type}) failed to subscribe." ) return loopchain_pb2.CommonReply( response_code=message_code.get_response_code( message_code.Response.fail), message=message_code.get_response_msg("Unknown type peer")) return loopchain_pb2.CommonReply( response_code=message_code.get_response_code( message_code.Response.success), message=message_code.get_response_msg( message_code.Response.success))
def AnnounceUnconfirmedBlock(self, request, context): """수집된 tx 로 생성한 Block 을 각 peer 에 전송하여 검증을 요청한다. :param request: :param context: :return: """ channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel logging.debug(f"peer_outer_service::AnnounceUnconfirmedBlock channel({channel_name})") channel_stub = StubCollection().channel_stubs[channel_name] channel_stub.sync_task().announce_unconfirmed_block(request.block) return loopchain_pb2.CommonReply(response_code=message_code.Response.success, message="success")
def VoteUnconfirmedBlock(self, request, context): channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel util.logger.debug(f"VoteUnconfirmedBlock block_hash({request.block_hash})") channel_stub = StubCollection().channel_stubs[channel_name] channel_stub.sync_task().vote_unconfirmed_block( peer_id=request.peer_id, group_id=request.group_id, block_hash=request.block_hash, vote_code=request.vote_code) return loopchain_pb2.CommonReply(response_code=message_code.Response.success, message="success")
def AnnounceNewLeader(self, request, context): if not request.channel: raise Exception( "peer_outer_service:AnnounceNewLeader : Channel is not defined." ) logging.debug(f"AnnounceNewLeader({request.channel}): " + request.message) channel_stub = StubCollection().channel_stubs[request.channel] channel_stub.sync_task().reset_leader(request.new_leader_id) return loopchain_pb2.CommonReply( response_code=message_code.Response.success, message="success")
def AnnounceDeletePeer(self, request, context): """delete peer by radio station heartbeat, It delete peer info over whole channels. :param request: :param context: :return: """ channel = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel logging.debug(f"AnnounceDeletePeer peer_id({request.peer_id}) group_id({request.group_id})") if self.peer_service.peer_id != request.peer_id: channel_stub = StubCollection().channel_stubs[channel] channel_stub.sync_task().delete_peer(request.peer_id, request.group_id) return loopchain_pb2.CommonReply(response_code=0, message="success")
def ComplainLeader(self, request: ComplainLeaderRequest, context): channel = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel util.logger.notice(f"ComplainLeader " f"height({request.block_height}) complained_peer({request.complained_leader_id})") channel_stub = StubCollection().channel_stubs[channel] channel_stub.sync_task().complain_leader( complained_leader_id=request.complained_leader_id, new_leader_id=request.new_leader_id, block_height=request.block_height, peer_id=request.peer_id, group_id=request.group_id ) return loopchain_pb2.CommonReply(response_code=message_code.Response.success, message="success")
def GetBlock(self, request, context): """Block 정보를 조회한다. :param request: loopchain.proto 의 GetBlockRequest 참고 request.block_hash: 조회할 block 의 hash 값, "" 로 조회하면 마지막 block 의 hash 값을 리턴한다. request.block_data_filter: block 정보 중 조회하고 싶은 key 값 목록 "key1, key2, key3" 형식의 string request.tx_data_filter: block 에 포함된 transaction(tx) 중 조회하고 싶은 key 값 목록 "key1, key2, key3" 형식의 string :param context: :return: loopchain.proto 의 GetBlockReply 참고, block_hash, block 정보 json, block 에 포함된 tx 정보의 json 리스트를 받는다. 포함되는 정보는 param 의 filter 에 따른다. """ channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel channel_stub = StubCollection().channel_stubs[channel_name] response_code, block_hash, block_data_json, tx_data_json_list = \ channel_stub.sync_task().get_block( block_height=request.block_height, block_hash=request.block_hash, block_data_filter=request.block_data_filter, tx_data_filter=request.tx_data_filter) return loopchain_pb2.GetBlockReply(response_code=response_code, block_hash=block_hash, block_data_json=block_data_json, tx_data_json=tx_data_json_list)
def genesis_invoke(self, block: Block) -> ('Block', dict): method = "icx_sendTransaction" transactions = [] for tx in block.body.transactions.values(): hash_version = conf.CHANNEL_OPTION[ ChannelProperty().name]["genesis_tx_hash_version"] tx_serializer = TransactionSerializer.new(tx.version, hash_version) transaction = { "method": method, "params": { "txHash": tx.hash.hex() }, "genesisData": tx_serializer.to_full_data(tx) } transactions.append(transaction) request = { 'block': { 'blockHeight': block.header.height, 'blockHash': block.header.hash.hex(), 'timestamp': block.header.timestamp }, 'transactions': transactions } request = convert_params(request, ParamType.invoke) stub = StubCollection().icon_score_stubs[ChannelProperty().name] response = stub.sync_task().invoke(request) response_to_json_query(response) block_builder = BlockBuilder.from_new(block) block_builder.commit_state = { ChannelProperty().name: response['stateRootHash'] } new_block = block_builder.build() return new_block, response["txResults"]
def __request_roll_back(self): target_block = self.blockchain.find_block_by_hash32( self.blockchain.last_block.header.prev_hash) if not self.blockchain.check_rollback_possible(target_block): util.logger.warning( f"The request cannot be rolled back to the target block({target_block})." ) return request_origin = { 'blockHeight': target_block.header.height, 'blockHash': target_block.header.hash.hex_0x() } request = convert_params(request_origin, ParamType.roll_back) stub = StubCollection().icon_score_stubs[ChannelProperty().name] util.logger.debug(f"Rollback request({request})") response: dict = cast(dict, stub.sync_task().rollback(request)) response_to_json_query(response) result_height = response.get("blockHeight") if hex(target_block.header.height) == result_height: util.logger.info(f"Rollback Success") self.blockchain.roll_back(target_block) self.rebuild_block() else: util.logger.warning(f"{response}")
def request_rollback(self) -> bool: """Request block data rollback behind to 1 block :return: if rollback success return True, else return False """ target_block = self.blockchain.find_block_by_hash32(self.blockchain.last_block.header.prev_hash) if not self.blockchain.check_rollback_possible(target_block): util.logger.warning(f"The request cannot be rollback to the target block({target_block}).") return False request_origin = { 'blockHeight': target_block.header.height, 'blockHash': target_block.header.hash.hex_0x() } request = convert_params(request_origin, ParamType.roll_back) stub = StubCollection().icon_score_stubs[ChannelProperty().name] util.logger.debug(f"Rollback request({request})") response: dict = cast(dict, stub.sync_task().rollback(request)) try: response_to_json_query(response) except GenericJsonRpcServerError as e: util.logger.warning(f"response error = {e}") else: result_height = response.get("blockHeight") if hex(target_block.header.height) == result_height: util.logger.info(f"Rollback Success. result height = {result_height}") self.blockchain.rollback(target_block) self.rebuild_block() return True util.logger.warning(f"Rollback Fail. response = {response}") return False
def score_write_precommit_state(self, block: Block): logging.debug( f"call score commit {ChannelProperty().name} {block.header.height} {block.header.hash.hex()}" ) new_block_hash = block.header.hash try: old_block_hash = self.__block_manager.get_old_block_hash( block.header.height, new_block_hash) except KeyError: old_block_hash = new_block_hash logging.debug(f"Block Hash : {old_block_hash} -> {new_block_hash}") request = { "blockHeight": block.header.height, "oldBlockHash": old_block_hash.hex(), "newBlockHash": new_block_hash.hex() } request = convert_params(request, ParamType.write_precommit_state) stub = StubCollection().icon_score_stubs[ChannelProperty().name] precommit_result: dict = stub.sync_task().write_precommit_state( request) if "error" in precommit_result: raise WritePrecommitStateError(precommit_result['error']) self.__block_manager.pop_old_block_hashes(block.header.height) return True
def GetTx(self, request, context): """get transaction :param request: tx_hash :param context:channel_loopchain_default :return: """ channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel channel_stub = StubCollection().channel_stubs[channel_name] tx = channel_stub.sync_task().get_tx(request.tx_hash) response_code, response_msg = message_code.get_response(message_code.Response.fail) response_meta = "" response_data = "" response_sign = b'' response_public_key = b'' if tx is not None: response_code, response_msg = message_code.get_response(message_code.Response.success) response_meta = json.dumps(tx.meta) response_data = tx.get_data().decode(conf.PEER_DATA_ENCODING) response_sign = tx.signature response_public_key = tx.public_key return loopchain_pb2.GetTxReply(response_code=response_code, meta=response_meta, data=response_data, signature=response_sign, public_key=response_public_key, more_info=response_msg)
def score_invoke(self, _block: Block) -> dict or None: method = "icx_sendTransaction" transactions = [] for tx in _block.body.transactions.values(): tx_serializer = TransactionSerializer.new(tx.version, self.block_manager.get_blockchain().tx_versioner) transaction = { "method": method, "params": tx_serializer.to_full_data(tx) } transactions.append(transaction) request = { 'block': { 'blockHeight': _block.header.height, 'blockHash': _block.header.hash.hex(), 'prevBlockHash': _block.header.prev_hash.hex() if _block.header.prev_hash else '', 'timestamp': _block.header.timestamp }, 'transactions': transactions } request = convert_params(request, ParamType.invoke) stub = StubCollection().icon_score_stubs[ChannelProperty().name] response = stub.sync_task().invoke(request) response_to_json_query(response) block_builder = BlockBuilder.from_new(_block, self.__block_manager.get_blockchain().tx_versioner) block_builder.commit_state = { ChannelProperty().name: response['stateRootHash'] } new_block = block_builder.build() return new_block, response["txResults"]
def __prevent_next_block_mismatch(self, next_block: Block) -> bool: logging.debug(f"prevent_block_mismatch...") score_stub = StubCollection().icon_score_stubs[self.__channel_name] request = { "method": "ise_getStatus", "params": { "filter": ["lastBlock"] } } response = score_stub.sync_task().query(request) score_last_block_height = int(response['lastBlock']['blockHeight'], 16) if score_last_block_height == next_block.header.height: logging.debug(f"already invoked block in score...") return False if score_last_block_height < next_block.header.height: for invoke_block_height in range(score_last_block_height + 1, next_block.header.height): logging.debug( f"mismatch invoke_block_height({invoke_block_height}) " f"score_last_block_height({score_last_block_height}) " f"next_block_height({next_block.header.height})") invoke_block = self.find_block_by_height(invoke_block_height) if invoke_block is None: raise RuntimeError( "Error raised during prevent mismatch block, " f"Cannot find block({invoke_block_height}") invoke_block, invoke_block_result = ObjectManager( ).channel_service.score_invoke(invoke_block) self.__add_tx_to_block_db(invoke_block, invoke_block_result) ObjectManager().channel_service.score_write_precommit_state( invoke_block) return True if score_last_block_height == next_block.header.height + 1: try: invoke_result_block_height_bytes = \ self.__confirmed_block_db.Get(BlockChain.INVOKE_RESULT_BLOCK_HEIGHT_KEY) invoke_result_block_height = int.from_bytes( invoke_result_block_height_bytes, byteorder='big') if invoke_result_block_height == next_block.header.height: logging.debug(f"already saved invoke result...") return False except KeyError: logging.debug(f"There is no invoke result height in db.") else: util.exit_and_msg( "Too many different(over 2) of block height between the loopchain and score. " "Peer will be down. : " f"loopchain({next_block.header.height})/score({score_last_block_height})" ) return True
def Query(self, request, context): """Score 의 invoke 로 생성된 data 에 대한 query 를 수행한다.""" channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel score_stub = StubCollection().score_stubs[channel_name] response_code, response = score_stub.sync_task().query(request.params) return loopchain_pb2.QueryReply(response_code=response_code, response=response)
def score_remove_precommit_state(self, block: Block): if not util.channel_use_icx(ChannelProperty().name): request = { "blockHeight": block.height, "blockHash": block.block_hash, } request = convert_params(request, ParamType.remove_precommit_state) stub = StubCollection().icon_score_stubs[ChannelProperty().name] stub.sync_task().remove_precommit_state(request) return True else: invoke_fail_info = json.dumps({"block_height": block.height, "block_hash": block.block_hash}) stub = StubCollection().score_stubs[ChannelProperty().name] stub.sync_task().remove_precommit_state(invoke_fail_info) return True
def BroadcastVote(self, request, context): channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel vote: VoteMessage = VoteMessage().loads(request.vote_data) logging.debug( f"peer_outer_service.py:BroadcastVote :: channel({channel_name})") logging.info( f"Peer vote to : {vote.block_hash} / {request.vote_code} from {request.peer_id}" ) util.logger.spam( f"peer_outer_service.py:BroadcastVote::{vote.print_vote_message()}" ) channel_stub = StubCollection().channel_stubs[request.channel] channel_stub.sync_task().broadcast_vote(vote) return loopchain_pb2.CommonReply( response_code=message_code.Response.success, message="success")
def AnnounceNewPeer(self, request, context): """RadioStation에서 Broadcasting 으로 신규 피어정보를 받아온다 :param request: PeerRequest :param context: :return: """ # RadioStation To Peer # prevent to show certificate content # logging.info('Here Comes new peer: ' + str(request)) channel = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel logging.debug(f"peer outer service::AnnounceNewPeer channel({channel})") if request.peer_object: channel_stub = StubCollection().channel_stubs[channel] channel_stub.sync_task().announce_new_peer(request.peer_object, request.peer_target) return loopchain_pb2.CommonReply(response_code=0, message="success")
def Stop(self, request, context): """Peer를 중지시킨다 :param request: 중지요청 :param context: :return: 중지결과 """ if request is not None: logging.info('Peer will stop... by: ' + request.reason) try: for channel_name in self.peer_service.channel_infos: channel_stub = StubCollection().channel_stubs[channel_name] channel_stub.sync_task().stop() self.peer_service.p2p_server_stop() except Exception as e: logging.debug("Score Service Already stop by other reason. %s", e) return loopchain_pb2.StopReply(status="0")
def __handler_peer_list(self, request, context): channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel channel_stub = StubCollection().channel_stubs[channel_name] all_group_peer_list_str, peer_list_str = channel_stub.sync_task().get_peer_list() message = "All Group Peers count: " + all_group_peer_list_str return loopchain_pb2.Message( code=message_code.Response.success, message=message, meta=peer_list_str)
def GetInvokeResult(self, request, context): """get invoke result by tx_hash :param request: request.tx_hash = tx_hash :param context: :return: verify result """ channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel logging.debug(f"peer_outer_service:GetInvokeResult in channel({channel_name})") channel_stub = StubCollection().channel_stubs[channel_name] response_code, result = channel_stub.sync_task().get_invoke_result(request.tx_hash) return loopchain_pb2.GetInvokeResultReply(response_code=response_code, result=result)
def __handler_status(self, request, context): util.logger.debug( f"peer_outer_service:handler_status ({request.message})") if request.message == "get_stub_manager_to_server": # this case is check only gRPC available return loopchain_pb2.Message(code=message_code.Response.success) channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel channel_stub = StubCollection().channel_stubs[channel_name] # FIXME : is need? if conf.ENABLE_REP_RADIO_STATION and request.message == "check peer status by rs": channel_stub.sync_task().reset_timer( TimerService.TIMER_KEY_CONNECT_PEER) callback = partial(self.__status_update, request.channel) future = asyncio.run_coroutine_threadsafe( channel_stub.async_task().get_status(), self.peer_service.inner_service.loop) future.add_done_callback(callback) status = self.__get_status_peer_type_data(request.channel) if status is None: return loopchain_pb2.Message(code=message_code.Response.fail) meta = json.loads(request.meta) if request.meta else {} if meta.get( "highest_block_height", None ) and meta["highest_block_height"] > status["block_height"]: util.logger.spam( f"(peer_outer_service.py:__handler_status) there is difference of height !" ) status_json = json.dumps(status) return loopchain_pb2.Message(code=message_code.Response.success, meta=status_json)
def score_write_precommit_state(self, block: Block): logging.debug(f"call score commit {ChannelProperty().name} {block.height} {block.block_hash}") if util.channel_use_icx(ChannelProperty().name): request = { "blockHeight": block.height, "blockHash": block.block_hash, } request = convert_params(request, ParamType.write_precommit_state) stub = StubCollection().icon_score_stubs[ChannelProperty().name] stub.sync_task().write_precommit_state(request) return True else: block_commit_info = json.dumps({"block_height": block.height, "block_hash": block.block_hash}) stub = StubCollection().score_stubs[ChannelProperty().name] response = stub.sync_task().write_precommit_state(block_commit_info) if response.code == message_code.Response.success: return True else: logging.error(f"score db commit fail cause {response.message}") return False
def __get_status_from_cache(self, channel: str): if channel in self.peer_service.status_cache: if channel in self.__status_cache_update_time: if util.datetime_diff_in_mins( self.__status_cache_update_time[channel]) \ > conf.ALLOW_STATUS_CACHE_LAST_UPDATE_IN_MINUTES: return None status_data = self.peer_service.status_cache[channel] else: channel_stub = StubCollection().channel_stubs[channel] status_data = channel_stub.sync_task().get_status() self.peer_service.status_cache[channel] = status_data return status_data
def score_invoke(self, _block: Block) -> dict or None: if util.channel_use_icx(ChannelProperty().name): method = "icx_sendTransaction" transactions = [] for tx in _block.confirmed_transaction_list: data = tx.icx_origin_data transaction = { "method": method, "params": data } transactions.append(transaction) request = { 'block': { 'blockHeight': _block.height, 'blockHash': _block.block_hash, 'prevBlockHash': _block.prev_block_hash, 'timestamp': _block.time_stamp }, 'transactions': transactions } request = convert_params(request, ParamType.invoke) stub = StubCollection().icon_score_stubs[ChannelProperty().name] response = stub.sync_task().invoke(request) response_to_json_query(response) _block.commit_state[ChannelProperty().name] = response['stateRootHash'] return response["txResults"] else: stub = StubCollection().score_stubs[ChannelProperty().name] response = stub.sync_task().score_invoke(_block) if response.code == message_code.Response.success: commit_state = pickle.loads(response.object) _block.commit_state = commit_state return json.loads(response.meta) return None
def genesis_invoke(self, block: Block) -> dict or None: if conf.USE_EXTERNAL_SCORE: method = "icx_sendTransaction" transactions = [] for tx in block.confirmed_transaction_list: transaction = { "method": method, "params": { "txHash": tx.tx_hash }, "genesisData": tx.genesis_origin_data } transactions.append(transaction) request = { 'block': { 'blockHeight': block.height, 'blockHash': block.block_hash, 'timestamp': block.time_stamp }, 'transactions': transactions } request = convert_params(request, ParamType.invoke) stub = StubCollection().icon_score_stubs[ChannelProperty().name] response = stub.sync_task().invoke(request) response_to_json_query(response) block.commit_state[ ChannelProperty().name] = response['stateRootHash'] return response["txResults"] else: block_object = pickle.dumps(block) stub = StubCollection().score_stubs[ChannelProperty().name] response = stub.sync_task().genesis_invoke(block_object) if response.code == message_code.Response.success: return json.loads(response.meta) return None
def UnSubscribe(self, request, context): """BlockGenerator 의 broadcast 채널에서 Peer 를 제외한다. :param request: :param context: :return: """ channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel channel_stub = StubCollection().channel_stubs[channel_name] peer_list = [target['peer_target'] for target in self.peer_service.channel_infos[channel_name]["peers"]] if (request.peer_target in peer_list and conf.ENABLE_CHANNEL_AUTH) or \ (request.node_type == loopchain_pb2.CommunityNode and not conf.ENABLE_CHANNEL_AUTH): channel_stub.sync_task().remove_audience(peer_target=request.peer_target) util.logger.spam(f"peer_outer_service::Unsubscribe remove_audience target({request.peer_target}) " f"in channel({request.channel})") else: logging.error(f"This target({request.peer_target}), {request.node_type} failed to unsubscribe.") return loopchain_pb2.CommonReply(response_code=message_code.get_response_code(message_code.Response.fail), message=message_code.get_response_msg("Unknown type peer")) return loopchain_pb2.CommonReply(response_code=message_code.get_response_code(message_code.Response.success), message=message_code.get_response_msg(message_code.Response.success))
def BlockSync(self, request, context): # Peer To Peer channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel logging.info(f"BlockSync request hash({request.block_hash}) " f"request height({request.block_height}) channel({channel_name})") channel_stub = StubCollection().channel_stubs[channel_name] response_code, block_height, max_block_height, block_dumped = \ channel_stub.sync_task().block_sync(request.block_hash, request.block_height) return loopchain_pb2.BlockSyncReply( response_code=response_code, block_height=block_height, max_block_height=max_block_height, block=block_dumped)
def GetPrecommitBlock(self, request, context): """Return the precommit bock. :param request: :param context: :return: loopchain.proto 의 PrecommitBlockReply 참고, """ channel_name = conf.LOOPCHAIN_DEFAULT_CHANNEL if request.channel == '' else request.channel channel_stub = StubCollection().channel_stubs[channel_name] response_code, response_message, block = \ channel_stub.sync_task().get_precommit_block(last_block_height=request.last_block_height) return loopchain_pb2.PrecommitBlockReply( response_code=response_code, response_message=response_message, block=block)