def _invoke(self, context: "IconScoreContext", to: "Address", icon_score_address: "Address", data: dict) -> None: assert icon_score_address is not None assert icon_score_address != SYSTEM_SCORE_ADDRESS assert icon_score_address.is_contract if icon_score_address in (None, SYSTEM_SCORE_ADDRESS): raise InvalidParamsException( f'Invalid SCORE address: {icon_score_address}') try: deploy_type: 'DeployType' = \ DeployType.INSTALL if to == SYSTEM_SCORE_ADDRESS else DeployType.UPDATE context.storage.deploy.put_deploy_info_and_tx_params( context, icon_score_address, deploy_type, context.tx.origin, context.tx.hash, data) if not self._is_audit_needed(context, icon_score_address): self.deploy(context, context.tx.hash) except BaseException as e: Logger.warning('Failed to write deploy info and tx params', ICON_DEPLOY_LOG_TAG) raise e
def invoke(self, context: 'IconScoreContext', to: 'Address', icon_score_address: 'Address', data: dict) -> None: """Handle data contained in icx_sendTransaction message :param context: :param to: :param icon_score_address: cx0000000000000000000000000000000000000000 on install otherwise score address to update :param data: SCORE deploy data """ assert icon_score_address is not None assert icon_score_address != ZERO_SCORE_ADDRESS assert icon_score_address.is_contract if icon_score_address in (None, ZERO_SCORE_ADDRESS): raise InvalidParamsException( f'Invalid SCORE address: {icon_score_address}') try: IconScoreContextUtil.validate_deployer(context, context.tx.origin) deploy_type: 'DeployType' = \ DeployType.INSTALL if to == ZERO_SCORE_ADDRESS else DeployType.UPDATE context.storage.deploy.put_deploy_info_and_tx_params( context, icon_score_address, deploy_type, context.tx.origin, context.tx.hash, data) if not self._is_audit_needed(context, icon_score_address): self.deploy(context, context.tx.hash) except BaseException as e: Logger.warning('Failed to write deploy info and tx params', ICON_DEPLOY_LOG_TAG) raise e
def create_delegation_info(address: 'Address', value: int) -> 'DelegationInfo': info = DelegationInfo() info.address: 'Address' = address info.value: int = value Logger.debug(f"create_delegation_info: {str(info.address)}", "iiss") return info
def run( self, context: 'IconScoreContext', prep: 'PRep', on_penalty_imposed: Callable[ ['IconScoreContext', 'Address', 'PenaltyReason'], None] ) -> 'PenaltyReason': reason: 'PenaltyReason' = PenaltyReason.NONE if self._check_block_validation_penalty(prep): Logger.info( f"PenaltyImposer statistics({PenaltyReason.BLOCK_VALIDATION}): " f"prep_total_blocks: {prep.total_blocks} " f"prep_block_validation_proportion: {prep.block_validation_proportion}" ) reason = PenaltyReason.BLOCK_VALIDATION if self._check_low_productivity_penalty(prep): Logger.info( f"PenaltyImposer statistics({PenaltyReason.LOW_PRODUCTIVITY}): " f"prep_total_blocks: {prep.total_blocks} " f"prep_unvalidated_sequence_blocks: {prep.unvalidated_sequence_blocks}" ) reason = PenaltyReason.LOW_PRODUCTIVITY if on_penalty_imposed and reason != PenaltyReason.NONE: on_penalty_imposed(context, prep.address, reason) return reason
async def _on_send(self, writer: 'StreamWriter'): Logger.info(tag=_TAG, msg="_on_send() start") while self._running: try: request: 'Request' = await self._queue.get() self._queue.task_done() if request.msg_type == MessageType.NONE: # Stopping IPCServer break data: bytes = request.to_bytes() Logger.debug(tag=_TAG, msg=f"on_send(): data({data.hex()}") Logger.info(tag=_TAG, msg=f"Sending Data : {request}") writer.write(data) await writer.drain() except asyncio.CancelledError: pass except BaseException as e: Logger.warning(tag=_TAG, msg=str(e)) writer.close() Logger.info(tag=_TAG, msg="_on_send() end")
def _on_deploy(self, context: 'IconScoreContext', tx_params: 'IconScoreDeployTXParams') -> None: """ Decompress a SCORE zip file and write them to file system Create a SCORE instance from SCORE class Call a SCORE initialization function (on_install or on_update) :param tx_params: use deploy_data, score_address, tx_hash, deploy_type from IconScoreDeployTxParams :return: """ data = tx_params.deploy_data score_address = tx_params.score_address params: dict = data.get('params', {}) deploy_info: 'IconScoreDeployInfo' = context.storage.deploy.get_deploy_info( context, tx_params.score_address) next_tx_hash: bytes = deploy_info.next_tx_hash self._write_score_to_filesystem(context, score_address, next_tx_hash, data) backup_msg = context.msg backup_tx = context.tx new_tx_score_mapper: dict = {} try: IconScoreContextUtil.validate_score_package( context, score_address, next_tx_hash) score_info: 'IconScoreInfo' =\ self._create_score_info(context, score_address, next_tx_hash) if context.revision >= Revision.STRICT_SCORE_DECORATOR_CHECK.value: check_score_flag(score_info.score_class) # score_info.get_score() returns a cached or created score instance # according to context.revision. score: 'IconScoreBase' = score_info.get_score(context.revision) # check_on_deploy will be done by normalize_signature() # since Revision.THREE if context.revision < Revision.THREE.value: ScoreApiGenerator.check_on_deploy(context, score) # owner is set in IconScoreBase.__init__() context.msg = Message(sender=score.owner) context.tx = None self._initialize_score(context, tx_params.deploy_type, score, params) new_tx_score_mapper[score_address] = score_info except BaseException as e: Logger.warning(f'Failed to deploy a SCORE: {score_address}', ICON_DEPLOY_LOG_TAG) raise e finally: context.msg = backup_msg context.tx = backup_tx self._update_new_score_mapper(context.new_icon_score_mapper, new_tx_score_mapper)
def get_total_elected_prep_delegated_snapshot(self) -> int: """ total_elected_prep_delegated_snapshot = the delegated amount which the elected P-Reps received at the beginning of this term - the delegated amount which unregistered P-Reps received in this term This function is only intended for state backward compatibility and not used any more after revision is set to 7. """ unreg_preps: Set['Address'] = set() db = self._db.get_sub_db(TxData.PREFIX) for k, v in db.iterator(): data: 'TxData' = TxData.from_bytes(v) if data.type == TxType.PREP_UNREGISTER: unreg_preps.add(data.address) db = self._db.get_sub_db(PRepsData.PREFIX) preps: Optional[List['DelegationInfo']] = None for k, v in db.iterator(): data: 'PRepsData' = PRepsData.from_bytes(k, v) preps = data.prep_list break ret = 0 if preps: for info in preps: if info.address not in unreg_preps: ret += info.value Logger.info( tag=IISS_LOG_TAG, msg=f"get_total_elected_prep_delegated_snapshot load: {ret}") return ret
def _on_accepted(self, reader: 'StreamReader', writer: 'StreamWriter'): Logger.info(tag=_TAG, msg=f"on_accepted() start: {reader} {writer}") self._tasks.append(asyncio.ensure_future(self._on_send(writer))) self._tasks.append(asyncio.ensure_future(self._on_recv(reader))) Logger.info(tag=_TAG, msg="on_accepted() end")
def scan_rc_db(cls, rc_data_path: str) -> Tuple[str, str, str]: """Scan directories that are managed by RewardCalcStorage :param rc_data_path: the parent directory of rc_dbs :return: current_rc_db_exists(bool), standby_rc_db_path, iiss_rc_db_path """ current_rc_db_path: str = "" standby_rc_db_path: str = "" iiss_rc_db_path: str = "" with os.scandir(rc_data_path) as it: for entry in it: if entry.is_dir(): if entry.name == cls.CURRENT_IISS_DB_NAME: current_rc_db_path: str = os.path.join( rc_data_path, cls.CURRENT_IISS_DB_NAME) elif entry.name.startswith( cls.STANDBY_IISS_DB_NAME_PREFIX): standby_rc_db_path: str = os.path.join( rc_data_path, entry.name) elif entry.name.startswith(cls.IISS_RC_DB_NAME_PREFIX): iiss_rc_db_path: str = os.path.join( rc_data_path, entry.name) Logger.info(tag=WAL_LOG_TAG, msg=f"current_rc_db={current_rc_db_path}, " f"standby_rc_db={standby_rc_db_path}, " f"iiss_rc_db={iiss_rc_db_path}") return current_rc_db_path, standby_rc_db_path, iiss_rc_db_path
def _supplement_db(self, context: 'IconScoreContext', revision: int): # Supplement db which is made by previous icon service version (as there is no version, revision and header) if revision < Revision.IISS.value: return rc_version, _ = self.get_version_and_revision() if rc_version == -1: self._put_version_and_revision(revision) # On the first change point. # We have to put Header for RC if self._db.get(Header.PREFIX) is None: rc_version, rc_revision = self.get_version_and_revision() end_block_height: int = context.storage.iiss.get_end_block_height_of_calc( context) calc_period: int = context.storage.iiss.get_calc_period(context) prev_end_calc_block_height: int = end_block_height - calc_period # if this point is new calc start point ... # we have to set block height in header data. if prev_end_calc_block_height == context.block.height: end_block_height: int = context.block.height header: 'Header' = DataCreator.create_header( rc_version, end_block_height, rc_revision) self.put_data_directly(header) Logger.debug( tag=IISS_LOG_TAG, msg=f"No header data. Put Header to db on open: {str(header)}")
def create_delegation_info(cls, address: 'Address', value: int) -> 'DelegationInfo': info = DelegationInfo() info.address = address info.value = value Logger.debug(f"create_delegation_info: {info.address}", cls.TAG) return info
def close(self): Logger.info(tag=_TAG, msg="close() start") self._loop = None self._unpacker = None Logger.info(tag=_TAG, msg="close() end")
def rollback(self, context: 'IconScoreContext', block_height: int, block_hash: bytes): Logger.info(tag=ROLLBACK_LOG_TAG, msg=f"rollback() start: block_height={block_height} block_hash={bytes_to_hex(block_hash)}") self._load_special_address(context, self._GENESIS_DB_KEY) self._load_special_address(context, self._TREASURY_DB_KEY) self.load_last_block_info(context) Logger.info(tag=ROLLBACK_LOG_TAG, msg="rollback() end")
def _rename_db(old_db_path: str, new_db_path: str): if os.path.exists(old_db_path) and not os.path.exists(new_db_path): os.rename(old_db_path, new_db_path) Logger.info(tag=IISS_LOG_TAG, msg=f"Rename db: {old_db_path} -> {new_db_path}") else: raise DatabaseException( "Cannot create IISS DB because of invalid path. Check both IISS " "current DB path and IISS DB path")
def _get_part(self, context: 'IconScoreContext', part_class: Union[type(CoinPart), type(StakePart), type(DelegationPart)], address: 'Address') -> Union['CoinPart', 'StakePart', 'DelegationPart']: key: bytes = part_class.make_key(address) value: bytes = self._db.get(context, key) if value is None and part_class is CoinPart: Logger.info(tag="PV", msg=f"No CoinPart: {address} {context.block}") return part_class.from_bytes(value) if value else part_class()
def start(self): Logger.info(tag=_TAG, msg="start() start") if self._running: return self._running = True co = asyncio.start_unix_server(self._on_accepted, self._path) asyncio.ensure_future(co) Logger.info(tag=_TAG, msg="start() end")
def _handle_icx_issue_formula_for_prep(self, irep: int, rrep: int, total_delegation: int) -> int: calculated_irep: int = self.calculate_irep_per_block_contributor(irep) beta_1: int = calculated_irep * self._prep_count beta_2: int = calculated_irep * self._sub_prep_count temp_rrep = IssueFormula.calculate_temporary_reward_prep(rrep) beta_3: int = temp_rrep * total_delegation // (IISS_ANNUAL_BLOCK * IISS_MAX_REWARD_RATE) Logger.debug("Calculated issue amount about this block. " f"irep: {irep} rrep: {temp_rrep} total_delegation: {total_delegation} " f"beta1: {beta_1} beta2: {beta_2} beta3: {beta_3}", IISS_LOG_TAG) return beta_1 + beta_2 + beta_3
def start(self): Logger.info(tag=_TAG, msg="start() start") if self._running: return self._running = True co = asyncio.start_unix_server(self._on_accepted, self._path) self._loop.run_until_complete(co) Logger.info(tag=_TAG, msg="start() end")
def __init__(self, reset_time: int, threshold: int, ban_time: int): Logger.info( f"DoSGuard config: reset_time={reset_time}, threshold={threshold}, ban_time={ban_time}" ) self._statistics: dict = {c.value: {} for c in Category} self._ban_expired: dict = {c.value: {} for c in Category} self._reset_time: int = reset_time self._threshold: int = threshold self._ban_time: int = ban_time self._last_reset_time: int = now()
def stop(self): Logger.info(tag=_TAG, msg="stop() start") if not self._running: return self._running = False for t in self._tasks: t.cancel() Logger.info(tag=_TAG, msg="stop() end")
def open(self, loop, message_queue: 'MessageQueue', path: str): Logger.info(tag=_TAG, msg="open() start") assert loop assert message_queue assert isinstance(path, str) self._loop = loop self._queue = message_queue self._path = path Logger.info(tag=_TAG, msg="open() end")
def _remove_score_dir(cls, address: 'Address', converted_tx_hash: Optional[str] = None): if cls.icon_score_loader is None: return score_root_path = cls.icon_score_loader.score_root_path if converted_tx_hash is None: target_path = os.path.join(score_root_path, bytes.hex(address.to_bytes())) else: target_path = os.path.join(score_root_path, bytes.hex(address.to_bytes()), converted_tx_hash) try: rmtree(target_path) except Exception as e: Logger.warning(e)
def _issue(context: 'IconScoreContext', to: 'Address', amount: int): if amount > 0: to_account: 'Account' = context.storage.icx.get_account( context, to) to_account.deposit(amount) current_total_supply = context.storage.icx.get_total_supply( context) context.storage.icx.put_account(context, to_account) context.storage.icx.put_total_supply(context, current_total_supply + amount) Logger.info( f"Issue icx. amount: {amount} " f"Total supply: {current_total_supply + amount} " f"Treasury: {to_account.balance}", ICX_LOG_TAG)
def _set_corrected_issue_data(self, context: 'IconScoreContext', issue_amount: int): regulator_variable: 'RegulatorVariable' = context.storage.issue.get_regulator_variable( context) prev_block_cumulative_fee: int = context.storage.icx.last_block.cumulative_fee end_block_height_of_calc: int = context.storage.iiss.get_end_block_height_of_calc( context) # Update current calculated period total issued icx current_calc_period_total_issued_icx: int = regulator_variable.current_calc_period_issued_icx current_calc_period_total_issued_icx += issue_amount if end_block_height_of_calc == context.block.height: prev_calc_period_issued_iscore, _, _ = context.storage.rc.get_calc_response_from_rc( ) assert prev_calc_period_issued_iscore >= 0 # In case of the first term of decentralization. # Do not regulate on the first term of decentralization # as Icon service has not issued ICX on the last period of 'pre-vote' # (On pre-vote, icon-foundation provided ICX instead of issuing it) if regulator_variable.prev_calc_period_issued_icx == -1: regulator_variable.prev_calc_period_issued_icx, prev_calc_period_issued_iscore = 0, 0 covered_icx_by_fee, covered_icx_by_remain, remain_over_issued_iscore, corrected_icx_issue_amount = \ self._correct_issue_amount_on_calc_period(regulator_variable.prev_calc_period_issued_icx, prev_calc_period_issued_iscore, regulator_variable.over_issued_iscore, issue_amount, prev_block_cumulative_fee) regulator_variable.prev_calc_period_issued_icx = current_calc_period_total_issued_icx regulator_variable.current_calc_period_issued_icx = 0 else: covered_icx_by_fee, covered_icx_by_remain, remain_over_issued_iscore, corrected_icx_issue_amount = \ self._correct_issue_amount(regulator_variable.over_issued_iscore, issue_amount, prev_block_cumulative_fee) regulator_variable.current_calc_period_issued_icx = current_calc_period_total_issued_icx regulator_variable.over_issued_iscore = remain_over_issued_iscore self._regulator_variable = regulator_variable self._covered_icx_by_fee = covered_icx_by_fee self._covered_icx_by_remain = covered_icx_by_remain self._corrected_icx_issue_amount = corrected_icx_issue_amount Logger.info( f"Regulate BH: {context.block.height} " f"Covered by fee: {self._covered_icx_by_fee} " f"Covered by remain: {self._covered_icx_by_remain} " f"Corrected issue amount {self._corrected_icx_issue_amount}" f"Regulator variable: {self._regulator_variable}", IISS_LOG_TAG)
def index(self, address: 'Address') -> int: """Returns the index of a given address in active_prep_list :return: zero-based index """ prep: 'PRep' = self._prep_dict.get(address) if prep is None: Logger.info(tag="PREP", msg=f"P-Rep not found: {address}") return -1 if prep.status == PRepStatus.ACTIVE: return self._active_prep_list.index(prep) return -1
def create_prep_data(block_height: int, total_delegation: int, preps: List['PRep']) -> 'PRepsData': converted_preps: List['DelegationInfo'] = [] for prep in preps: Logger.debug(f"create_prep_data: {str(prep.address)}", "iiss") info = DataCreator.create_delegation_info(prep.address, prep.delegated) converted_preps.append(info) data = PRepsData() data.block_height: int = block_height data.total_delegation: int = total_delegation data.prep_list: List['DelegationInfo'] = converted_preps return data
def commit(self, block: 'Block'): node = self._precommit_data_mapper.get(block.hash) if node is None: Logger.warning( tag=_TAG, msg= f"No precommit data: height={block.height} hash={bytes_to_hex(block.hash)}" ) return if not node.parent.is_root() and self._root == node.parent: raise InternalServiceErrorException(f"Parent should be a root") self._remove_sibling_precommit_data(block) del self._precommit_data_mapper[self._root.block.hash] self._set_root(node)
def open(self, context: IconScoreContext, path: str): revision: int = context.revision if not os.path.exists(path): raise DatabaseException(f"Invalid IISS DB path: {path}") self._path = path self._db = self.create_current_db(path) self._db_iiss_tx_index = self._load_last_transaction_index() Logger.info( tag=IISS_LOG_TAG, msg=f"last_transaction_index on open={self._db_iiss_tx_index}") # todo: check side effect of WAL self._supplement_db(context, revision)
def _on_deploy_for_builtin(self, context: 'IconScoreContext', score_address: 'Address', src_score_path: str) -> None: """Install an icon score for builtin """ score_root_path = context.icon_score_mapper.score_root_path target_path = path.join(score_root_path, score_address.to_bytes().hex()) makedirs(target_path, exist_ok=True) deploy_info = self._icon_score_deploy_storage.get_deploy_info( context, score_address) if deploy_info is None: next_tx_hash = None else: next_tx_hash = deploy_info.next_tx_hash if next_tx_hash is None: next_tx_hash = bytes(DEFAULT_BYTE_SIZE) converted_tx_hash: str = f'0x{bytes.hex(next_tx_hash)}' target_path = path.join(target_path, converted_tx_hash) try: copytree(src_score_path, target_path) except FileExistsError: pass try: score = context.icon_score_mapper.load_score( score_address, next_tx_hash) if score is None: raise InvalidParamsException( f'score is None : {score_address}') self._initialize_score(on_deploy=score.on_install, params={}) except BaseException as e: Logger.warning( f'load wait icon score fail!! address: {score_address}', ICON_DEPLOY_LOG_TAG) Logger.warning('revert to add wait icon score', ICON_DEPLOY_LOG_TAG) raise e context.icon_score_mapper.put_score_info(score_address, score, next_tx_hash)
def _set_corrected_issue_data(self, context: 'IconScoreContext', issue_amount: int): regulator_variable: 'RegulatorVariable' = context.storage.issue.get_regulator_variable( context) prev_block_cumulative_fee: int = context.storage.icx.last_block.cumulative_fee end_block_height_of_calc: int = context.storage.iiss.get_end_block_height_of_calc( context) # Update current calculated period total issued icx current_calc_period_total_issued_icx: int = regulator_variable.current_calc_period_issued_icx current_calc_period_total_issued_icx += issue_amount if end_block_height_of_calc == context.block.height: prev_calc_period_issued_iscore, _, _ = context.storage.rc.get_calc_response_from_rc( ) assert prev_calc_period_issued_iscore >= 0 if regulator_variable.prev_calc_period_issued_icx == -1: regulator_variable.prev_calc_period_issued_icx, prev_calc_period_issued_iscore = 0, 0 covered_icx_by_fee, covered_icx_by_remain, remain_over_issued_iscore, corrected_icx_issue_amount = \ self._correct_issue_amount_on_calc_period(regulator_variable.prev_calc_period_issued_icx, prev_calc_period_issued_iscore, regulator_variable.over_issued_iscore, issue_amount, prev_block_cumulative_fee) regulator_variable.prev_calc_period_issued_icx = current_calc_period_total_issued_icx regulator_variable.current_calc_period_issued_icx = 0 else: covered_icx_by_fee, covered_icx_by_remain, remain_over_issued_iscore, corrected_icx_issue_amount = \ self._correct_issue_amount(regulator_variable.over_issued_iscore, issue_amount, prev_block_cumulative_fee) regulator_variable.current_calc_period_issued_icx = current_calc_period_total_issued_icx regulator_variable.over_issued_iscore = remain_over_issued_iscore self._regulator_variable = regulator_variable self._covered_icx_by_fee = covered_icx_by_fee self._covered_icx_by_remain = covered_icx_by_remain self._corrected_icx_issue_amount = corrected_icx_issue_amount Logger.info( f"Regulate BH: {context.block.height} " f"Covered by fee: {self._covered_icx_by_fee} " f"Covered by remain: {self._covered_icx_by_remain} " f"Corrected issue amount {self._corrected_icx_issue_amount}" f"Regulator variable: {self._regulator_variable}", IISS_LOG_TAG)