class IconScoreInnerTask(object): def __init__(self, conf: 'IconConfig'): self._conf = conf self._thread_flag = ENABLE_THREAD_FLAG self._icon_service_engine = IconServiceEngine() self._open() self._thread_pool = { THREAD_INVOKE: ThreadPoolExecutor(1), THREAD_QUERY: ThreadPoolExecutor(1), THREAD_VALIDATE: ThreadPoolExecutor(1) } def _open(self): Logger.info("icon_score_service open", ICON_INNER_LOG_TAG) self._icon_service_engine.open(self._conf) def _is_thread_flag_on(self, flag: 'EnableThreadFlag') -> bool: return (self._thread_flag & flag) == flag def _check_icon_service_ready(self): if not self._icon_service_engine.is_reward_calculator_ready(): raise ServiceNotReadyException("Reward Calculator is not ready") @staticmethod def _log_exception(e: BaseException, tag: str = ICON_INNER_LOG_TAG) -> None: Logger.exception(str(e), tag) Logger.error(str(e), tag) @message_queue_task async def hello(self): Logger.info('hello() start', ICON_INNER_LOG_TAG) ready_future = self._icon_service_engine.get_ready_future() await ready_future if self._is_thread_flag_on(EnableThreadFlag.INVOKE): loop = asyncio.get_event_loop() ret = await loop.run_in_executor(self._thread_pool[THREAD_INVOKE], self._hello) else: ret = self._hello() Logger.info('hello() end', ICON_INNER_LOG_TAG) return ret def _hello(self): return self._icon_service_engine.hello() def _close(self): Logger.info(tag=_TAG, msg="_close() start") if self._icon_service_engine: self._icon_service_engine.close() self._icon_service_engine = None MessageQueueService.loop.stop() Logger.info(tag=_TAG, msg="_close() end") @message_queue_task async def close(self): Logger.info(tag=_TAG, msg="close() start") self._close() Logger.info(tag=_TAG, msg="close() end") @message_queue_task async def invoke(self, request: dict): Logger.info(f'invoke request with {request}', ICON_INNER_LOG_TAG) self._check_icon_service_ready() if self._is_thread_flag_on(EnableThreadFlag.INVOKE): loop = asyncio.get_event_loop() return await loop.run_in_executor(self._thread_pool[THREAD_INVOKE], self._invoke, request) else: return self._invoke(request) def _invoke(self, request: dict): """Process transactions in a block :param request: :return: """ response = None try: params = TypeConverter.convert(request, ParamType.INVOKE) converted_block_params = params['block'] block = Block.from_dict(converted_block_params) converted_tx_requests = params['transactions'] convert_tx_result_to_dict: bool = 'isBlockEditable' in params converted_is_block_editable = params.get('isBlockEditable', False) converted_prev_block_generator = params.get('prevBlockGenerator') converted_prev_block_validators = params.get('prevBlockValidators') converted_prev_votes = params.get('prevBlockVotes') tx_results, state_root_hash, added_transactions, main_prep_as_dict = self._icon_service_engine.invoke( block=block, tx_requests=converted_tx_requests, prev_block_generator=converted_prev_block_generator, prev_block_validators=converted_prev_block_validators, prev_block_votes=converted_prev_votes, is_block_editable=converted_is_block_editable) if convert_tx_result_to_dict: convert_tx_results = [ tx_result.to_dict(to_camel_case) for tx_result in tx_results ] else: # old version convert_tx_results = { bytes.hex(tx_result.tx_hash): tx_result.to_dict(to_camel_case) for tx_result in tx_results } results = { 'txResults': convert_tx_results, 'stateRootHash': bytes.hex(state_root_hash), 'addedTransactions': added_transactions } if main_prep_as_dict: results["prep"] = main_prep_as_dict Logger.info(f'invoke origin response with {results}', ICON_INNER_LOG_TAG) response = MakeResponse.make_response(results) except FatalException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) self._close() except InvalidBaseTransactionException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) except IconServiceBaseException as icon_e: self._log_exception(icon_e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( icon_e.code, icon_e.message) except Exception as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) finally: if self._icon_service_engine: self._icon_service_engine.clear_context_stack() return response @message_queue_task async def query(self, request: dict): Logger.debug(f'query request with {request}', ICON_INNER_LOG_TAG) self._check_icon_service_ready() if self._is_thread_flag_on(EnableThreadFlag.QUERY): loop = asyncio.get_event_loop() return await loop.run_in_executor(self._thread_pool[THREAD_QUERY], self._query, request) else: return self._query(request) def _query(self, request: dict): response = None try: method = request['method'] if method == 'debug_estimateStep': converted_request = TypeConverter.convert( request, ParamType.INVOKE_TRANSACTION) value = self._icon_service_engine.estimate_step( converted_request) else: converted_request = TypeConverter.convert( request, ParamType.QUERY) value = self._icon_service_engine.query( method, converted_request['params']) if isinstance(value, Address): value = str(value) response = MakeResponse.make_response(value) except FatalException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) except IconServiceBaseException as icon_e: self._log_exception(icon_e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( icon_e.code, icon_e.message) except Exception as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) finally: Logger.debug(f'query response with {response}', ICON_INNER_LOG_TAG) self._icon_service_engine.clear_context_stack() return response @message_queue_task async def call(self, request: dict): Logger.info(f'call request with {request}', ICON_INNER_LOG_TAG) self._check_icon_service_ready() if self._is_thread_flag_on(EnableThreadFlag.QUERY): loop = asyncio.get_event_loop() return await loop.run_in_executor(self._thread_pool[THREAD_QUERY], self._call, request) else: return self._call(request) def _call(self, request: dict): response = None try: response = self._icon_service_engine.inner_call(request) if isinstance(response, Address): response = str(response) except FatalException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) except IconServiceBaseException as icon_e: self._log_exception(icon_e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( icon_e.code, icon_e.message) except Exception as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) finally: Logger.info(f'call response with {response}', ICON_INNER_LOG_TAG) return response @message_queue_task async def write_precommit_state(self, request: dict): Logger.info(f'write_precommit_state request with {request}', ICON_INNER_LOG_TAG) self._check_icon_service_ready() if self._is_thread_flag_on(EnableThreadFlag.INVOKE): loop = asyncio.get_event_loop() return await loop.run_in_executor(self._thread_pool[THREAD_INVOKE], self._write_precommit_state, request) else: return self._write_precommit_state(request) @staticmethod def _get_block_info_for_precommit_state( converted_block_params: dict ) -> Tuple[int, bytes, Optional[bytes]]: block_height: int = converted_block_params[ConstantKeys.BLOCK_HEIGHT] block_hash: Optional[bytes] = None if ConstantKeys.BLOCK_HASH in converted_block_params: instant_block_hash: bytes = converted_block_params[ ConstantKeys.BLOCK_HASH] else: instant_block_hash: bytes = converted_block_params[ ConstantKeys.OLD_BLOCK_HASH] block_hash = converted_block_params[ConstantKeys.NEW_BLOCK_HASH] return block_height, instant_block_hash, block_hash def _write_precommit_state(self, request: dict): response = None try: converted_block_params = TypeConverter.convert( request, ParamType.WRITE_PRECOMMIT) block_height, instant_block_hash, block_hash = \ self._get_block_info_for_precommit_state(converted_block_params) self._icon_service_engine.commit(block_height, instant_block_hash, block_hash) response = MakeResponse.make_response(ExceptionCode.OK) except FatalException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) self._close() except IconServiceBaseException as icon_e: self._log_exception(icon_e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( icon_e.code, icon_e.message) except Exception as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) finally: Logger.info(f'write_precommit_state response with {response}', ICON_INNER_LOG_TAG) return response @message_queue_task async def remove_precommit_state(self, request: dict): Logger.info(f'remove_precommit_state request with {request}', ICON_INNER_LOG_TAG) self._check_icon_service_ready() if self._is_thread_flag_on(EnableThreadFlag.INVOKE): loop = asyncio.get_event_loop() return await loop.run_in_executor(self._thread_pool[THREAD_INVOKE], self._remove_precommit_state, request) else: return self._remove_precommit_state(request) def _remove_precommit_state(self, request: dict): response = None try: converted_block_params = TypeConverter.convert( request, ParamType.WRITE_PRECOMMIT) block_height, instant_block_hash, _ = \ self._get_block_info_for_precommit_state(converted_block_params) self._icon_service_engine.remove_precommit_state( block_height, instant_block_hash) response = MakeResponse.make_response(ExceptionCode.OK) except FatalException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) self._close() except IconServiceBaseException as icon_e: self._log_exception(icon_e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( icon_e.code, icon_e.message) except Exception as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) finally: Logger.info(f'remove_precommit_state response with {response}', ICON_INNER_LOG_TAG) return response @message_queue_task async def rollback(self, request: dict): """Go back to the state of the given previous block :param request: :return: """ Logger.info(tag=ICON_INNER_LOG_TAG, msg=f"rollback() start: {request}") self._check_icon_service_ready() if self._is_thread_flag_on(EnableThreadFlag.INVOKE): loop = asyncio.get_event_loop() response = await loop.run_in_executor( self._thread_pool[THREAD_INVOKE], self._rollback, request) else: response = self._rollback(request) Logger.info(tag=ICON_INNER_LOG_TAG, msg=f"rollback() end: {response}") return response def _rollback(self, request: dict) -> dict: Logger.info(tag=ICON_INNER_LOG_TAG, msg=f"_rollback() start: {request}") response = {} try: converted_params = TypeConverter.convert(request, ParamType.ROLLBACK) block_height: int = converted_params[ConstantKeys.BLOCK_HEIGHT] block_hash: bytes = converted_params[ConstantKeys.BLOCK_HASH] response: dict = self._icon_service_engine.rollback( block_height, block_hash) response = MakeResponse.make_response(response) except FatalException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) self._close() except IconServiceBaseException as icon_e: self._log_exception(icon_e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( icon_e.code, icon_e.message) except BaseException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) finally: Logger.info(tag=ICON_INNER_LOG_TAG, msg=f"_rollback() end: {response}") return response @message_queue_task async def validate_transaction(self, request: dict): Logger.debug(f'pre_validate_check request with {request}', ICON_INNER_LOG_TAG) self._check_icon_service_ready() if self._is_thread_flag_on(EnableThreadFlag.VALIDATE): loop = asyncio.get_event_loop() return await loop.run_in_executor( self._thread_pool[THREAD_VALIDATE], self._validate_transaction, request) else: return self._validate_transaction(request) def _validate_transaction(self, request: dict): response = None try: converted_request = TypeConverter.convert( request, ParamType.VALIDATE_TRANSACTION) self._icon_service_engine.validate_transaction(converted_request) response = MakeResponse.make_response(ExceptionCode.OK) except FatalException as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) except IconServiceBaseException as icon_e: self._log_exception(icon_e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( icon_e.code, icon_e.message) except Exception as e: self._log_exception(e, ICON_SERVICE_LOG_TAG) response = MakeResponse.make_error_response( ExceptionCode.SYSTEM_ERROR, str(e)) finally: Logger.debug(f'pre_validate_check response with {response}', ICON_INNER_LOG_TAG) self._icon_service_engine.clear_context_stack() return response @message_queue_task async def change_block_hash(self, _params): self._check_icon_service_ready() return ExceptionCode.OK
class TestIntegrateBase(TestCase): @classmethod def setUpClass(cls): cls._score_root_path = '.score' cls._state_db_root_path = '.statedb' cls._iiss_db_root_path = '.iissdb' cls._test_sample_root = "samples" cls._signature = "VAia7YZ2Ji6igKWzjR2YsGa2m53nKPrfK7uXYW78QLE+ATehAVZPC40szvAiA6NEU5gCYB4c4qaQzqDh2ugcHgA=" cls._version = 3 cls._admin: 'EOAAccount' = cls.create_eoa_accounts(1)[0] cls._genesis: 'Address' = create_address() cls._fee_treasury: 'Address' = create_address() cls._accounts = cls.create_eoa_accounts(100) cls._tx_results: dict = {} def setUp(self): root_clear(self._score_root_path, self._state_db_root_path, self._iiss_db_root_path) self._block_height = -1 self._prev_block_hash = None config = IconConfig("", copy.deepcopy(default_icon_config)) config.load() config.update_conf( {ConfigKey.BUILTIN_SCORE_OWNER: str(self._admin.address)}) config.update_conf({ ConfigKey.SERVICE: { ConfigKey.SERVICE_AUDIT: False, ConfigKey.SERVICE_FEE: False, ConfigKey.SERVICE_DEPLOYER_WHITE_LIST: False, ConfigKey.SERVICE_SCORE_PACKAGE_VALIDATOR: False } }) config.update_conf({ ConfigKey.SCORE_ROOT_PATH: self._score_root_path, ConfigKey.STATE_DB_ROOT_PATH: self._state_db_root_path }) config.update_conf(self._make_init_config()) self._config: 'IconConfig' = config self.icon_service_engine = IconServiceEngine() self._mock_ipc() self.icon_service_engine.open(config) self._genesis_invoke() def get_block_height(self) -> int: return self._block_height def mock_calculate(self, _path, _block_height): context: 'IconScoreContext' = IconScoreContext( IconScoreContextType.QUERY) end_block_height_of_calc: int = context.storage.iiss.get_end_block_height_of_calc( context) calc_period: int = context.storage.iiss.get_calc_period(context) response = CalculateDoneNotification( 0, True, end_block_height_of_calc - calc_period, 0, b'mocked_response') self._calculate_done_callback(response) def _calculate_done_callback(self, response: 'CalculateDoneNotification'): pass @classmethod def _mock_ipc(cls, mock_calculate: callable = mock_calculate): RewardCalcProxy.open = Mock() RewardCalcProxy.start = Mock() RewardCalcProxy.stop = Mock() RewardCalcProxy.close = Mock() RewardCalcProxy.get_version = Mock() RewardCalcProxy.calculate = mock_calculate RewardCalcProxy.claim_iscore = Mock() RewardCalcProxy.query_iscore = Mock() RewardCalcProxy.commit_block = Mock() RewardCalcProxy.commit_claim = Mock() RewardCalcProxy.query_calculate_result = Mock( return_value=(RCCalculateResult.SUCCESS, 0, 0, bytes())) def tearDown(self): self.icon_service_engine.close() root_clear(self._score_root_path, self._state_db_root_path, self._iiss_db_root_path) def _make_init_config(self) -> dict: return {} def _genesis_invoke(self) -> tuple: tx_hash = create_tx_hash() timestamp_us = create_timestamp() request_params = { 'txHash': tx_hash, 'version': self._version, 'timestamp': timestamp_us } tx = { 'method': 'icx_sendTransaction', 'params': request_params, 'genesisData': { "accounts": [{ "name": "genesis", "address": self._genesis, "balance": 0 }, { "name": "fee_treasury", "address": self._fee_treasury, "balance": 0 }, { "name": "_admin", "address": self._admin.address, "balance": icx_to_loop(TOTAL_SUPPLY) }] }, } block_hash = create_block_hash() block = Block(self._block_height + 1, block_hash, timestamp_us, None, 0) invoke_response: tuple = self.icon_service_engine.invoke(block, [tx]) self.icon_service_engine.commit(block.height, block.hash, None) self._block_height += 1 self._prev_block_hash = block_hash return invoke_response def get_tx_results(self, hash_list: List[bytes]): tx_results: List['TransactionResult'] = [] for tx_hash in hash_list: tx_results.append(self._tx_results[tx_hash]) return tx_results @classmethod def get_hash_list_from_tx_list(cls, tx_list: list) -> List[bytes]: hash_list: list = [] for tx in tx_list: hash_list.append(tx['params']['txHash']) return hash_list def add_tx_result(self, tx_results: List['TransactionResult']): for tx_result in tx_results: self._tx_results[tx_result.tx_hash] = tx_result def make_and_req_block(self, tx_list: list, block_height: int = None, prev_block_generator: Optional['Address'] = None, prev_block_validators: Optional[List['Address']] = None, prev_block_votes: Optional[List[Tuple['Address', int]]] = None, block_hash: bytes = None) \ -> Tuple['Block', List[bytes]]: if block_height is None: block_height: int = self._block_height + 1 if block_hash is None: block_hash = create_block_hash() timestamp_us = create_timestamp() block = Block(block_height, block_hash, timestamp_us, self._prev_block_hash, 0) context = IconScoreContext(IconScoreContextType.DIRECT) is_block_editable = False self.icon_service_engine._set_revision_to_context(context) if context.is_decentralized(): is_block_editable = True tx_results, state_root_hash, added_transactions, main_prep_as_dict = \ self.icon_service_engine.invoke(block=block, tx_requests=tx_list, prev_block_generator=prev_block_generator, prev_block_validators=prev_block_validators, prev_block_votes=prev_block_votes, is_block_editable=is_block_editable) self.add_tx_result(tx_results) return block, self.get_hash_list_from_tx_list(tx_list) def debug_make_and_req_block( self, tx_list: list, prev_block_generator: Optional['Address'] = None, prev_block_validators: Optional[List['Address']] = None, prev_block_votes: Optional[List[Tuple['Address', int]]] = None, block: 'Block' = None) -> tuple: # Prevent a base transaction from being added to the original tx_list tx_list = copy.copy(tx_list) if block is None: block_height: int = self._block_height + 1 block_hash = create_block_hash() timestamp_us = create_timestamp() block = Block(block_height, block_hash, timestamp_us, self._prev_block_hash, 0) context = IconScoreContext(IconScoreContextType.DIRECT) is_block_editable = False self.icon_service_engine._set_revision_to_context(context) if context.is_decentralized(): is_block_editable = True tx_results, state_root_hash, added_transactions, main_prep_as_dict = \ self.icon_service_engine.invoke(block=block, tx_requests=tx_list, prev_block_generator=prev_block_generator, prev_block_validators=prev_block_validators, prev_block_votes=prev_block_votes, is_block_editable=is_block_editable) return block, tx_results, state_root_hash, added_transactions, main_prep_as_dict def _make_and_req_block_for_issue_test( self, tx_list: list, block_height: int = None, prev_block_generator: Optional['Address'] = None, prev_block_validators: Optional[List['Address']] = None, prev_block_votes: Optional[List[Tuple['Address', int]]] = None, is_block_editable=False, cumulative_fee: int = 0) -> Tuple['Block', List[bytes]]: if block_height is None: block_height: int = self._block_height + 1 block_hash = create_block_hash() timestamp_us = create_timestamp() block = Block(block_height, block_hash, timestamp_us, self._prev_block_hash, cumulative_fee) tx_results, _, added_transactions, main_prep_as_dict = \ self.icon_service_engine.invoke(block=block, tx_requests=tx_list, prev_block_generator=prev_block_generator, prev_block_validators=prev_block_validators, prev_block_votes=prev_block_votes, is_block_editable=is_block_editable) self.add_tx_result(tx_results) return block, self.get_hash_list_from_tx_list(tx_list) def _write_precommit_state(self, block: 'Block') -> None: self.icon_service_engine.commit(block.height, block.hash, None) self._block_height += 1 assert block.height == self._block_height self._prev_block_hash = block.hash def _remove_precommit_state(self, block: 'Block') -> None: """Revoke to commit the precommit data to db """ self.icon_service_engine.remove_precommit_state( block.height, block.hash) def rollback(self, block_height: int = -1, block_hash: Optional[bytes] = None): """Rollback the current state to the old one indicated by a given block :param block_height: the final block height after rollback :param block_hash: the final block hash after rollback """ self.icon_service_engine.rollback(block_height, block_hash) self._block_height = block_height self._prev_block_hash = block_hash def _query(self, request: dict, method: str = 'icx_call') -> Any: response = self.icon_service_engine.query(method, request) return response def inner_call(self, request: dict) -> Any: response = self.icon_service_engine.inner_call(request) return response def _create_invalid_block(self, block_height: int = None) -> 'Block': if block_height is None: block_height: int = self._block_height block_hash = create_block_hash() timestamp_us = create_timestamp() return Block(block_height, block_hash, timestamp_us, self._prev_block_hash, 0) @classmethod def _convert_address_from_address_type( cls, from_: Union['EOAAccount', 'Address', 'MalformedAddress', None] ) -> Union['Address', 'MalformedAddress', None]: if isinstance(from_, EOAAccount): return from_.address elif isinstance(from_, (Address, MalformedAddress)): return from_ else: return None # ====== API ===== # def process_confirm_block_tx( self, tx_list: list, expected_status: bool = True, prev_block_generator: Optional['Address'] = None, prev_block_validators: Optional[List['Address']] = None, prev_block_votes: Optional[List[Tuple['Address', int]]] = None, block_height: int = None) -> List['TransactionResult']: prev_block, hash_list = self.make_and_req_block( tx_list, block_height, prev_block_generator, prev_block_validators, prev_block_votes) self._write_precommit_state(prev_block) tx_results: List['TransactionResult'] = self.get_tx_results(hash_list) for tx_result in tx_results: self.assertEqual(int(expected_status), tx_result.status) return tx_results def process_confirm_block( self, tx_list: list, prev_block_generator: Optional['Address'] = None, prev_block_validators: Optional[List['Address']] = None, prev_block_votes: Optional[List[Tuple['Address', int]]] = None, block_height: int = None) -> List['TransactionResult']: prev_block, hash_list = self.make_and_req_block( tx_list, block_height, prev_block_generator, prev_block_validators, prev_block_votes) self._write_precommit_state(prev_block) tx_results: List['TransactionResult'] = self.get_tx_results(hash_list) return tx_results def create_deploy_score_tx( self, score_root: str, score_name: str, from_: Union['EOAAccount', 'Address', None], to_: Union['EOAAccount', 'Address'], deploy_params: dict = None, timestamp_us: int = None, data: bytes = None, is_sys: bool = False, pre_validation_enabled: bool = True, step_limit: int = DEFAULT_DEPLOY_STEP_LIMIT) -> dict: addr_from: Optional[ 'Address'] = self._convert_address_from_address_type(from_) addr_to: 'Address' = self._convert_address_from_address_type(to_) if deploy_params is None: deploy_params = {} score_path = get_score_path(score_root, score_name) if is_sys: deploy_data = { 'contentType': 'application/tbears', 'content': score_path, 'params': deploy_params } else: if data is None: mz = InMemoryZip() mz.zip_in_memory(score_path) data = f'0x{mz.data.hex()}' else: data = f'0x{bytes.hex(data)}' deploy_data = { 'contentType': 'application/zip', 'content': data, 'params': deploy_params } if timestamp_us is None: timestamp_us = create_timestamp() nonce = 0 request_params = { "version": self._version, "from": addr_from, "to": addr_to, "stepLimit": step_limit, "timestamp": timestamp_us, "nonce": nonce, "signature": self._signature, "dataType": "deploy", "data": deploy_data } method = 'icx_sendTransaction' # Insert txHash into request params request_params['txHash'] = create_tx_hash() tx = {'method': method, 'params': request_params} if pre_validation_enabled: self.icon_service_engine.validate_transaction(tx) return tx def create_score_call_tx(self, from_: Union['EOAAccount', 'Address', None], to_: 'Address', func_name: str, params: Optional[dict] = None, value: int = 0, pre_validation_enabled: bool = True, step_limit: int = DEFAULT_BIG_STEP_LIMIT) -> dict: from_: Optional['Address'] = self._convert_address_from_address_type( from_) if params is None: params: dict = {} timestamp_us = create_timestamp() nonce = 0 request_params = { "version": self._version, "from": from_, "to": to_, "value": value, "stepLimit": step_limit, "timestamp": timestamp_us, "nonce": nonce, "signature": self._signature, "dataType": "call", "data": { "method": func_name, "params": params } } method = 'icx_sendTransaction' # Insert txHash into request params request_params['txHash'] = create_tx_hash() tx = {'method': method, 'params': request_params} if pre_validation_enabled: self.icon_service_engine.validate_transaction(tx) return tx def create_transfer_icx_tx(self, from_: Union['EOAAccount', 'Address', None], to_: Union['EOAAccount', 'Address', 'MalformedAddress'], value: int, disable_pre_validate: bool = False, support_v2: bool = False, step_limit: int = DEFAULT_STEP_LIMIT) -> dict: addr_from: Optional[ 'Address'] = self._convert_address_from_address_type(from_) addr_to: Optional[ 'Address', 'MalformedAddress'] = self._convert_address_from_address_type(to_) timestamp_us = create_timestamp() nonce = 0 request_params = { "from": addr_from, "to": addr_to, "value": value, "stepLimit": step_limit, "timestamp": timestamp_us, "nonce": nonce, "signature": self._signature } if support_v2: request_params["fee"] = 10**16 else: request_params["version"] = self._version method = 'icx_sendTransaction' # Insert txHash into request params request_params['txHash'] = create_tx_hash() tx = {'method': method, 'params': request_params} if not disable_pre_validate: self.icon_service_engine.validate_transaction(tx) return tx def create_message_tx(self, from_: Union['EOAAccount', 'Address', None], to_: Union['EOAAccount', 'Address', 'MalformedAddress'], data: bytes = None, value: int = 0) -> dict: addr_from: Optional[ 'Address'] = self._convert_address_from_address_type(from_) addr_to: Optional[ 'Address', 'MalformedAddress'] = self._convert_address_from_address_type(to_) timestamp_us = create_timestamp() nonce = 0 request_params = { "version": self._version, "from": addr_from, "to": addr_to, "value": value, "stepLimit": DEFAULT_BIG_STEP_LIMIT, "timestamp": timestamp_us, "nonce": nonce, "signature": self._signature, "dataType": "message", "data": '0x' + data.hex(), } method = 'icx_sendTransaction' # Inserts txHash into request params request_params['txHash'] = create_tx_hash() tx = {'method': method, 'params': request_params} self.icon_service_engine.validate_transaction(tx) return tx def create_deposit_tx(self, from_: Union['EOAAccount', 'Address', None], to_: 'Address', action: str, params: dict, value: int = 0, pre_validation_enabled: bool = True, step_limit: int = DEFAULT_BIG_STEP_LIMIT) -> dict: addr_from: Optional[ 'Address'] = self._convert_address_from_address_type(from_) addr_to: 'Address' = self._convert_address_from_address_type(to_) timestamp_us = create_timestamp() nonce = 0 request_params = { "version": self._version, "from": addr_from, "to": addr_to, "value": value, "stepLimit": step_limit, "timestamp": timestamp_us, "nonce": nonce, "signature": self._signature, "dataType": "deposit", "data": { "action": action, } } for k, v in params.items(): request_params["data"][k] = v method = 'icx_sendTransaction' # Insert txHash into request params request_params['txHash'] = create_tx_hash() tx = {'method': method, 'params': request_params} if pre_validation_enabled: self.icon_service_engine.validate_transaction(tx) return tx def create_register_proposal_tx( self, from_: 'Address', title: str, description: str, type_: int, value: Union[str, int, 'Address'], step_limit: int = DEFAULT_BIG_STEP_LIMIT) -> dict: text = '{"address":"%s"}' % value json_data: bytes = text.encode("utf-8") method = "registerProposal" score_params = { "title": title, "description": description, "type": hex(type_), "value": bytes_to_hex(json_data) } return self.create_score_call_tx(from_=from_, to_=GOVERNANCE_SCORE_ADDRESS, func_name=method, params=score_params, step_limit=step_limit) def create_vote_proposal_tx(self, from_: 'Address', id_: bytes, vote: bool, step_limit=DEFAULT_BIG_STEP_LIMIT) -> dict: method = "voteProposal" score_params = {"id": bytes_to_hex(id_, "0x"), "vote": hex(vote)} return self.create_score_call_tx(from_=from_, to_=GOVERNANCE_SCORE_ADDRESS, func_name=method, params=score_params, step_limit=step_limit) @staticmethod def _convert_tx_for_estimating_step_from_origin_tx(tx: dict): tx = copy.deepcopy(tx) tx["method"] = "debug_estimateStep" del tx["params"]["nonce"] del tx["params"]["stepLimit"] del tx["params"]["timestamp"] del tx["params"]["txHash"] del tx["params"]["signature"] return tx # ===== wrapping API ===== # def estimate_step(self, tx: dict): converted_tx = self._convert_tx_for_estimating_step_from_origin_tx(tx) return self.icon_service_engine.estimate_step(request=converted_tx) def update_governance( self, version: str = "latest_version", expected_status: bool = True) -> List['TransactionResult']: tx = self.create_deploy_score_tx("sample_builtin", f"{version}/governance", self._admin, GOVERNANCE_SCORE_ADDRESS) return self.process_confirm_block_tx([tx], expected_status) def transfer_icx( self, from_: Union['EOAAccount', 'Address', None], to_: Union['EOAAccount', 'Address', 'MalformedAddress'], value: int, disable_pre_validate: bool = False, support_v2: bool = False, step_limit: int = DEFAULT_STEP_LIMIT, expected_status: bool = True) -> List['TransactionResult']: tx = self.create_transfer_icx_tx( from_=from_, to_=to_, value=value, disable_pre_validate=disable_pre_validate, support_v2=support_v2, step_limit=step_limit) return self.process_confirm_block_tx([tx], expected_status) def deploy_score(self, score_root: str, score_name: str, from_: Union['EOAAccount', 'Address', None], deploy_params: dict = None, step_limit: int = DEFAULT_DEPLOY_STEP_LIMIT, expected_status: bool = True, to_: Union['EOAAccount', 'Address'] = ZERO_SCORE_ADDRESS, data: bytes = None) -> List['TransactionResult']: tx = self.create_deploy_score_tx(score_root=score_root, score_name=score_name, from_=from_, to_=to_, deploy_params=deploy_params, step_limit=step_limit, data=data) return self.process_confirm_block_tx([tx], expected_status) def score_call(self, from_: Union['EOAAccount', 'Address', None], to_: 'Address', func_name: str, params: dict = None, value: int = 0, step_limit: int = DEFAULT_BIG_STEP_LIMIT, expected_status: bool = True) -> List['TransactionResult']: tx = self.create_score_call_tx(from_=from_, to_=to_, func_name=func_name, params=params, value=value, step_limit=step_limit) return self.process_confirm_block_tx([tx], expected_status) def set_revision( self, revision: int, expected_status: bool = True) -> List['TransactionResult']: return self.score_call(from_=self._admin, to_=GOVERNANCE_SCORE_ADDRESS, func_name="setRevision", params={ "code": hex(revision), "name": f"1.1.{revision}" }, expected_status=expected_status) def accept_score( self, tx_hash: Union[bytes, str], warning_message: str = None, expected_status: bool = True) -> List['TransactionResult']: if isinstance(tx_hash, bytes): tx_hash_str = f'0x{bytes.hex(tx_hash)}' else: tx_hash_str = tx_hash params: dict = {"txHash": tx_hash_str} if warning_message is not None: params["warning"] = warning_message return self.score_call(from_=self._admin, to_=GOVERNANCE_SCORE_ADDRESS, func_name="acceptScore", params=params, expected_status=expected_status) def reject_score( self, tx_hash: Union[bytes, str], reason: str = "reason", expected_status: bool = True) -> List['TransactionResult']: if isinstance(tx_hash, bytes): tx_hash_str = f'0x{bytes.hex(tx_hash)}' else: tx_hash_str = tx_hash return self.score_call(from_=self._admin, to_=GOVERNANCE_SCORE_ADDRESS, func_name="rejectScore", params={ "txHash": tx_hash_str, "reason": reason }, expected_status=expected_status) def deposit_icx(self, score_address: 'Address', amount: int, period: int, sender: Union['EOAAccount', 'Address', None] = None, expected_status: bool = True) -> List['TransactionResult']: if sender is None: sender = self._admin if FIXED_TERM: tx: dict = self.create_deposit_tx(from_=sender, to_=score_address, action="add", params={}, value=amount) else: tx: dict = self.create_deposit_tx(from_=sender, to_=score_address, action="add", params={"term": hex(period)}, value=amount) return self.process_confirm_block_tx([tx], expected_status) def withdraw_deposit( self, deposit_id: bytes, score_address: 'Address', sender: Union['EOAAccount', 'Address', None] = None, expected_status: bool = True) -> List['TransactionResult']: if sender is None: sender = self._admin tx: dict = self.create_deposit_tx( from_=sender, to_=score_address, action="withdraw", params={"id": f"0x{bytes.hex(deposit_id)}"}) return self.process_confirm_block_tx([tx], expected_status) def register_proposal(self, from_: 'Address', title: str, description: str, type_: int, value: Union[str, int, 'Address'], expected_status: bool = True) -> 'TransactionResult': tx: dict = self.create_register_proposal_tx(from_, title, description, type_, value) # 0: base transaction, 1: register proposal tx_results = self.process_confirm_block_tx([tx], expected_status) return tx_results[1] def vote_proposal( self, from_: 'Address', id_: bytes, vote: bool, expected_status: bool = True) -> List['TransactionResult']: tx: dict = self.create_vote_proposal_tx(from_, id_, vote) return self.process_confirm_block_tx([tx], expected_status) def get_balance(self, account: Union['EOAAccount', 'Address']) -> int: address: Optional['Address'] = self._convert_address_from_address_type( account) return self._query(request={"address": address}, method="icx_getBalance") def get_total_supply(self) -> int: return self._query(request={}, method="icx_getTotalSupply") def get_score_api(self, address: 'Address'): return self._query(request={"address": address}, method='icx_getScoreApi') def query_score(self, from_: Union['EOAAccount', 'Address', None], to_: 'Address', func_name: str, params: dict = None): query_request = { "version": self._version, "from": from_, "to": to_, "dataType": "call", "data": { "method": func_name, "params": {} if params is None else params } } return self._query(query_request) def get_score_status(self, to_: 'Address'): query_request = { "version": self._version, "from": self._admin, "to": GOVERNANCE_SCORE_ADDRESS, "dataType": "call", "data": { "method": "getScoreStatus", "params": { "address": str(to_) } } } return self._query(query_request) def get_step_price(self) -> int: query_request = { "version": self._version, "from": self._admin, "to": GOVERNANCE_SCORE_ADDRESS, "dataType": "call", "data": { "method": "getStepPrice", "params": {} } } return self._query(query_request) @classmethod def create_eoa_account(cls) -> 'EOAAccount': return EOAAccount(KeyWallet.create()) @classmethod def create_eoa_accounts(cls, count: int) -> List['EOAAccount']: return [cls.create_eoa_account() for _ in range(count)]