diff --git a/iconservice/base/type_converter_templates.py b/iconservice/base/type_converter_templates.py index b8e4a8f5e..5941e76f9 100644 --- a/iconservice/base/type_converter_templates.py +++ b/iconservice/base/type_converter_templates.py @@ -38,6 +38,7 @@ class ParamType(IntEnum): ICX_GET_TOTAL_SUPPLY = 303 ICX_GET_SCORE_API = 304 ISE_GET_STATUS = 305 + DEBUG_GET_ACCOUNT = 306 WRITE_PRECOMMIT = 400 # REMOVE_PRECOMMIT = 500 @@ -161,6 +162,7 @@ class ConstantKeys: ICX_GET_TOTAL_SUPPLY = "icx_getTotalSupply" ICX_GET_SCORE_API = "icx_getScoreApi" ISE_GET_STATUS = "ise_getStatus" + DEBUG_GET_ACCOUNT = "debug_getAccount" DEPOSIT_TERM = "term" DEPOSIT_ID = "id" @@ -304,6 +306,11 @@ class ConstantKeys: ConstantKeys.FILTER: [ValueType.STRING] } +type_convert_templates[ParamType.DEBUG_GET_ACCOUNT] = { + ConstantKeys.ADDRESS: ValueType.ADDRESS, + ConstantKeys.FILTER: ValueType.INT +} + type_convert_templates[ParamType.QUERY] = { ConstantKeys.METHOD: ValueType.STRING, ConstantKeys.PARAMS: { @@ -313,7 +320,8 @@ class ConstantKeys: ConstantKeys.ICX_GET_BALANCE: type_convert_templates[ParamType.ICX_GET_BALANCE], ConstantKeys.ICX_GET_TOTAL_SUPPLY: type_convert_templates[ParamType.ICX_GET_TOTAL_SUPPLY], ConstantKeys.ICX_GET_SCORE_API: type_convert_templates[ParamType.ICX_GET_SCORE_API], - ConstantKeys.ISE_GET_STATUS: type_convert_templates[ParamType.ISE_GET_STATUS] + ConstantKeys.ISE_GET_STATUS: type_convert_templates[ParamType.ISE_GET_STATUS], + ConstantKeys.DEBUG_GET_ACCOUNT: type_convert_templates[ParamType.DEBUG_GET_ACCOUNT], } } } diff --git a/iconservice/icon_constant.py b/iconservice/icon_constant.py index ffd0d7c6e..c4e055d55 100644 --- a/iconservice/icon_constant.py +++ b/iconservice/icon_constant.py @@ -472,3 +472,4 @@ class RPCMethod: ICX_CALL = 'icx_call' ICX_SEND_TRANSACTION = 'icx_sendTransaction' DEBUG_ESTIMATE_STEP = "debug_estimateStep" + DEBUG_GET_ACCOUNT = "debug_getAccount" diff --git a/iconservice/icon_inner_service.py b/iconservice/icon_inner_service.py index 9d8be0dc8..d9f58139f 100644 --- a/iconservice/icon_inner_service.py +++ b/iconservice/icon_inner_service.py @@ -45,7 +45,8 @@ RPCMethod.ICX_GET_SCORE_API: THREAD_STATUS, RPCMethod.ISE_GET_STATUS: THREAD_STATUS, RPCMethod.ICX_CALL: THREAD_QUERY, - RPCMethod.DEBUG_ESTIMATE_STEP: THREAD_ESTIMATE + RPCMethod.DEBUG_ESTIMATE_STEP: THREAD_ESTIMATE, + RPCMethod.DEBUG_GET_ACCOUNT: THREAD_QUERY, } _TAG = "MQ" diff --git a/iconservice/icon_service_engine.py b/iconservice/icon_service_engine.py index ec9cb365b..ea0162101 100644 --- a/iconservice/icon_service_engine.py +++ b/iconservice/icon_service_engine.py @@ -20,7 +20,6 @@ from typing import TYPE_CHECKING, List, Optional, Tuple, Dict, Union, Any from iconcommons.logger import Logger - from iconservice.rollback import check_backup_exists from iconservice.rollback.backup_cleaner import BackupCleaner from iconservice.rollback.backup_manager import BackupManager @@ -64,7 +63,9 @@ from .icx import IcxEngine, IcxStorage from .icx.issue import IssueEngine, IssueStorage from .icx.issue.base_transaction_creator import BaseTransactionCreator -from .icx.unstake_patcher import UnstakePatcher, INVALID_EXPIRED_UNSTAKES_FILENAME +from .icx.storage import AccountPartFlag +from .icx.unstake_patcher import INVALID_EXPIRED_UNSTAKES_FILENAME +from .icx.unstake_patcher import UnstakePatcher from .iiss import check_decentralization_condition from .iiss.engine import Engine as IISSEngine from .iiss.reward_calc import RewardCalcStorage @@ -126,7 +127,8 @@ def __init__(self): RPCMethod.ISE_GET_STATUS: self._handle_ise_get_status, RPCMethod.ICX_CALL: self._handle_icx_call, RPCMethod.DEBUG_ESTIMATE_STEP: self._handle_estimate_step, - RPCMethod.ICX_SEND_TRANSACTION: self._handle_icx_send_transaction + RPCMethod.ICX_SEND_TRANSACTION: self._handle_icx_send_transaction, + RPCMethod.DEBUG_GET_ACCOUNT: self._handle_debug_get_account } self._precommit_data_manager = PrecommitDataManager() @@ -2127,3 +2129,26 @@ def _run_unstake_patcher(self, context: 'IconScoreContext'): ) Logger.info(tag=_TAG, msg="_run_unstake_patcher() end") + + @classmethod + def _handle_debug_get_account( + cls, + context: 'IconScoreContext', + params: dict + ) -> dict: + """ + Handles get raw data of account in StateDB + + :param context: context + :param params: parameters of account address and filter + :return: account raw data of stateDB + """ + + address: 'Address' = params["address"] + account_filter: 'AccountPartFlag' = AccountPartFlag(params["filter"]) + raw_data: dict = context.engine.icx.get_account_raw_data( + context=context, + address=address, + account_filter=account_filter + ) + return raw_data diff --git a/iconservice/icx/engine.py b/iconservice/icx/engine.py index 3f8140787..2937d96f1 100644 --- a/iconservice/icx/engine.py +++ b/iconservice/icx/engine.py @@ -17,12 +17,16 @@ from typing import TYPE_CHECKING from .icx_account import Account +from .storage import AccountPartFlag from ..base.ComponentBase import EngineBase from ..base.address import Address from ..base.exception import InvalidParamsException if TYPE_CHECKING: from ..iconscore.icon_score_context import IconScoreContext + from .coin_part import CoinPart + from .stake_part import StakePart + from .delegation_part import DelegationPart class Engine(EngineBase): @@ -97,3 +101,59 @@ def _transfer(self, context.storage.icx.put_account(context, to_account) return True + + @classmethod + def get_account_raw_data( + cls, + context: 'IconScoreContext', + address: 'Address', + account_filter: 'AccountPartFlag' + ) -> dict: + """Get the balance of address + + :param context: + :param address: account address + :param account_filter: + :return: raw data of account in stateDB + """ + + ret = {} + if AccountPartFlag.COIN in account_filter: + part: 'CoinPart' = context.storage.icx.get_part( + context=context, + flag=AccountPartFlag.COIN, + address=address + ) + ret["coin"] = { + "type": part.type.value, + "typeStr": str(part.type), + "flag": part.flags.value, + "flagStr": str(part.flags), + "balance": part.balance + } + if AccountPartFlag.STAKE in account_filter: + part: 'StakePart' = context.storage.icx.get_part( + context=context, + flag=AccountPartFlag.STAKE, + address=address + ) + ret["stake"] = { + "stake": part.stake, + "unstake": part.unstake, + "unstakeBlockHeight": part.unstake_block_height, + "unstakesInfo": part.unstakes_info, + } + if AccountPartFlag.DELEGATION in account_filter: + part: 'DelegationPart' = context.storage.icx.get_part( + context=context, + flag=AccountPartFlag.DELEGATION, + address=address + ) + ret["delegation"] = { + "totalDelegated": part.delegated_amount, + "delegations": [ + {"address": delegation[0], "value": delegation[1]} + for delegation in part.delegations + ], + } + return ret diff --git a/iconservice/icx/storage.py b/iconservice/icx/storage.py index 5d794e714..6fb435c00 100644 --- a/iconservice/icx/storage.py +++ b/iconservice/icx/storage.py @@ -310,9 +310,11 @@ def get_treasury_account(self, context: 'IconScoreContext') -> 'Account': """ return context.storage.icx.get_account(context, context.storage.icx.fee_treasury) - def _get_part(self, context: 'IconScoreContext', - part_class: Union[type(CoinPart), type(StakePart), type(DelegationPart)], - address: 'Address') -> Union['CoinPart', 'StakePart', 'DelegationPart']: + 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) @@ -391,13 +393,21 @@ def put_total_supply(self, value = value.to_bytes(DEFAULT_BYTE_SIZE, DATA_BYTE_ORDER) self._db.put(context, self._TOTAL_SUPPLY_KEY, value) - def get_coin_part(self, context: 'IconScoreContext', address: 'Address') -> 'CoinPart': - part: 'CoinPart' = self._get_part(context, CoinPart, address) - part.set_complete(True) - return part + def get_part( + self, + context: 'IconScoreContext', + flag: 'AccountPartFlag', + address: 'Address' + ) -> Union['CoinPart', 'StakePart', 'DelegationPart']: + + if AccountPartFlag.COIN == flag: + part_class = CoinPart + elif AccountPartFlag.STAKE == flag: + part_class = StakePart + else: + part_class = DelegationPart - def get_stake_part(self, context: 'IconScoreContext', address: 'Address') -> 'StakePart': - part: 'StakePart' = self._get_part(context, StakePart, address) + part: Union['CoinPart', 'StakePart', 'DelegationPart'] = self._get_part(context, part_class, address) part.set_complete(True) return part diff --git a/iconservice/icx/unstake_patcher.py b/iconservice/icx/unstake_patcher.py index 2aea98021..bac53189d 100644 --- a/iconservice/icx/unstake_patcher.py +++ b/iconservice/icx/unstake_patcher.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, List, Dict, Any from iconcommons.logger import Logger - +from .storage import AccountPartFlag from ..base.address import Address from ..icx.coin_part import CoinPartFlag @@ -126,8 +126,8 @@ def run(self, context: 'IconScoreContext'): for target in self._targets: address = target.address - coin_part = storage.get_coin_part(context, address) - stake_part = storage.get_stake_part(context, address) + coin_part = storage.get_part(context, AccountPartFlag.COIN, address) + stake_part = storage.get_part(context, AccountPartFlag.STAKE, address) result: Result = self._check_removable(coin_part, stake_part, target) if result == Result.FALSE: diff --git a/tests/integrate_test/iiss/decentralized/test_debug_get_acount.py b/tests/integrate_test/iiss/decentralized/test_debug_get_acount.py new file mode 100644 index 000000000..7a18d1374 --- /dev/null +++ b/tests/integrate_test/iiss/decentralized/test_debug_get_acount.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018 ICON Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""IconScoreEngine testcase +""" +from unittest.mock import Mock + +from iconservice.base.address import SYSTEM_SCORE_ADDRESS +from iconservice.icon_constant import Revision, ICX_IN_LOOP +from iconservice.icx.storage import AccountPartFlag +from iconservice.iiss import IISSMethod +from iconservice.iiss.reward_calc.ipc.reward_calc_proxy import RewardCalcProxy +from tests.integrate_test.iiss.test_iiss_base import TestIISSBase + + +class TestIISS(TestIISSBase): + def test_debug_get_account_coin(self): + self.update_governance() + + # set Revision REV_IISS + self.set_revision(Revision.IISS.value) + + expected_ret = { + 'coin': { + 'type': 0, + 'typeStr': 'GENERAL', + 'flag': 0, + 'flagStr': 'CoinPartFlag.NONE', + 'balance': 0 + } + } + ret: dict = self.debug_get_account( + self._accounts[0], + account_filter=AccountPartFlag.COIN.value + ) + self.assertEqual(expected_ret, ret) + + def test_debug_get_account_stake(self): + self.update_governance() + + # set Revision REV_IISS + self.set_revision(Revision.IISS.value) + + expected_ret = { + 'stake': { + 'stake': 0, + 'unstake': 0, + 'unstakeBlockHeight': 0, + 'unstakesInfo': [] + } + } + ret: dict = self.debug_get_account( + self._accounts[0], + account_filter=AccountPartFlag.STAKE.value + ) + self.assertEqual(expected_ret, ret) + + def test_debug_get_account_delegation(self): + self.update_governance() + + # set Revision REV_IISS + self.set_revision(Revision.IISS.value) + + expected_ret = { + 'delegation': { + 'delegations': [], + 'totalDelegated': 0 + } + } + ret: dict = self.debug_get_account( + self._accounts[0], + account_filter=AccountPartFlag.DELEGATION.value + ) + self.assertEqual(expected_ret, ret) + + def test_debug_get_account_all(self): + self.update_governance() + + # set Revision REV_IISS + self.set_revision(Revision.IISS.value) + + expected_ret = { + 'coin': { + 'type': 0, + 'typeStr': 'GENERAL', + 'flag': 0, + 'flagStr': 'CoinPartFlag.NONE', + 'balance': 0 + }, + 'stake': { + 'stake': 0, + 'unstake': 0, + 'unstakeBlockHeight': 0, + 'unstakesInfo': [] + }, + 'delegation': { + 'delegations': [], + 'totalDelegated': 0 + } + } + all_filter: AccountPartFlag = \ + AccountPartFlag.COIN | \ + AccountPartFlag.STAKE | \ + AccountPartFlag.DELEGATION + ret: dict = self.debug_get_account( + self._accounts[0], + account_filter=all_filter.value + ) + self.assertEqual(expected_ret, ret) diff --git a/tests/integrate_test/test_integrate_base.py b/tests/integrate_test/test_integrate_base.py index 71bdee574..375ca3dd3 100644 --- a/tests/integrate_test/test_integrate_base.py +++ b/tests/integrate_test/test_integrate_base.py @@ -29,7 +29,12 @@ from iconservice.base.block import Block from iconservice.fee.engine import FIXED_TERM from iconservice.icon_config import default_icon_config -from iconservice.icon_constant import ConfigKey, IconScoreContextType, RCCalculateResult, Revision +from iconservice.icon_constant import ( + ConfigKey, + IconScoreContextType, + RCCalculateResult, + RPCMethod +) from iconservice.icon_service_engine import IconServiceEngine from iconservice.iconscore.icon_score_context import IconScoreContext from iconservice.iiss.reward_calc.ipc.reward_calc_proxy import RewardCalcProxy, CalculateDoneNotification @@ -1005,6 +1010,20 @@ def create_eoa_account(cls) -> 'EOAAccount': def create_eoa_accounts(cls, count: int) -> List['EOAAccount']: return [cls.create_eoa_account() for _ in range(count)] + def debug_get_account( + self, + account: Union['EOAAccount', 'Address'], + account_filter: int + ) -> dict: + address: Optional['Address'] = self._convert_address_from_address_type(account) + return self._query( + request={ + "address": address, + "filter": account_filter + }, + method=RPCMethod.DEBUG_GET_ACCOUNT + ) + class EOAAccount: def __init__(self, wallet: 'KeyWallet', balance: int = 0): diff --git a/tests/unit_test/base/test_type_converter.py b/tests/unit_test/base/test_type_converter.py index d64611e70..ca9f93761 100644 --- a/tests/unit_test/base/test_type_converter.py +++ b/tests/unit_test/base/test_type_converter.py @@ -970,3 +970,25 @@ def test_wrong_block_convert(): TypeConverter.convert(request, ParamType.BLOCK) assert "TypeConvert Exception int value :1, type: " == e.value.message + + +def test_query_convert_debug_get_account(): + method = "debug_getAccount" + addr1 = create_address() + account_filter = 7 + + request = { + ConstantKeys.METHOD: method, + ConstantKeys.PARAMS: { + ConstantKeys.ADDRESS: str(addr1), + ConstantKeys.FILTER: hex(account_filter) + } + } + + ret_params = TypeConverter.convert(request, ParamType.QUERY) + + assert method == ret_params[ConstantKeys.METHOD] + + params_params = ret_params[ConstantKeys.PARAMS] + assert addr1 == params_params[ConstantKeys.ADDRESS] + assert account_filter == params_params[ConstantKeys.FILTER] \ No newline at end of file