diff --git a/iconservice/iconscore/icon_container_db.py b/iconservice/iconscore/icon_container_db.py index 4b2be31c1..f226cd0c7 100644 --- a/iconservice/iconscore/icon_container_db.py +++ b/iconservice/iconscore/icon_container_db.py @@ -216,6 +216,9 @@ def __remove(self, key: K) -> None: raise InvalidContainerAccessException('DictDB depth mismatch') self._db.delete(get_encoded_key(key)) + def __iter__(self): + raise InvalidContainerAccessException("Not Supported iter function on DictDB") + class ArrayDB(object): """ diff --git a/tests/icon_score/test_icon_container_db.py b/tests/icon_score/test_icon_container_db.py index 7ddbdbb6f..c42afbe1a 100644 --- a/tests/icon_score/test_icon_container_db.py +++ b/tests/icon_score/test_icon_container_db.py @@ -339,3 +339,53 @@ def test_container_util(self): with self.assertRaises(InvalidParamsException): prefix: bytes = ContainerUtil.create_db_prefix(VarDB, 'vardb') + +""" +Dict DB infinity infinite case +implement __getitem__ function means can be iter +https://www.python.org/dev/peps/pep-0234 +https://docs.python.org/3/reference/datamodel.html#object.__getitem__ +""" + + +class TestOnlyGetItemObj: + """ + Override getitem function + If you don't consider this case, this logic will always have an infinite loop. + https://stackoverflow.com/questions/926574/why-does-defining-getitem-on-a-class-make-it-iterable-in-python + """ + def __init__(self, limit): + self._limit = limit + + def __getitem__(self, key): + if key > self._limit: + raise IndexError(key) + + +class TestIterObj(TestOnlyGetItemObj): + """ + Override the iter function. + This logic prevents the above infinite loop situation. + https://stackoverflow.com/questions/926574/why-does-defining-getitem-on-a-class-make-it-iterable-in-python + """ + def __iter__(self): + pass + + +class TestIsAvailableToIterator(unittest.TestCase): + def test_getitem(self): + # it is possible to use foreach by using getitem + limit = 100_000 + datas = TestOnlyGetItemObj(limit) + index = 0 + for index, e in enumerate(datas): + pass + self.assertEqual(index, limit) + + def test_iter(self): + # prevent infinite loop by using empty __iter function + limit = 100_000 + datas = TestIterObj(limit) + with self.assertRaises(TypeError) as e: + for e in datas: + pass diff --git a/tests/integrate_test/samples/sample_scores/sample_dict_db/package.json b/tests/integrate_test/samples/sample_scores/sample_dict_db/package.json new file mode 100644 index 000000000..6cc62a78a --- /dev/null +++ b/tests/integrate_test/samples/sample_scores/sample_dict_db/package.json @@ -0,0 +1,5 @@ +{ + "version": "0.0.1", + "main_file": "sample_dict_db", + "main_score": "IterableDictDB" +} \ No newline at end of file diff --git a/tests/integrate_test/samples/sample_scores/sample_dict_db/sample_dict_db.py b/tests/integrate_test/samples/sample_scores/sample_dict_db/sample_dict_db.py new file mode 100644 index 000000000..a304df4c5 --- /dev/null +++ b/tests/integrate_test/samples/sample_scores/sample_dict_db/sample_dict_db.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +""" +Example + +Problem with DictDB : cannot iterate +https://forum.icon.community/t/problem-with-dictdb-cannot-iterate/484 +""" + +from iconservice import * +TAG = 'IterableDictDB' + + +class IterableDictDB(IconScoreBase): + """ IterableDictDB SCORE Base implementation """ + + # ================================================ + # Initialization + # ================================================ + def __init__(self, db: IconScoreDatabase) -> None: + super().__init__(db) + self._dict = DictDB('DICT', db, value_type=int) + + def on_install(self) -> None: + super().on_install() + + def on_update(self) -> None: + super().on_update() + + @external + def create_item(self, key: str, value: int) -> None: + self._dict[key] = value + + @external(readonly=True) + def get_items(self) -> list: + items = [] + for item in self._dict: + items.append(item) + return items diff --git a/tests/integrate_test/test_integrate_array_db_patch.py b/tests/integrate_test/test_integrate_container_db_patch.py similarity index 72% rename from tests/integrate_test/test_integrate_array_db_patch.py rename to tests/integrate_test/test_integrate_container_db_patch.py index a6ddf2529..c958138e9 100644 --- a/tests/integrate_test/test_integrate_array_db_patch.py +++ b/tests/integrate_test/test_integrate_container_db_patch.py @@ -21,6 +21,7 @@ from iconservice.base.address import GOVERNANCE_SCORE_ADDRESS from iconservice.icon_constant import Revision +from iconservice.base.exception import InvalidContainerAccessException from tests.integrate_test.test_integrate_base import TestIntegrateBase if TYPE_CHECKING: @@ -112,3 +113,45 @@ def test_array_db_patch(self): ) self.assertEqual(len(response), 2) + + +class TestIntegrateDictDBPatch(TestIntegrateBase): + def test_dict_db_defective(self): + self.update_governance("0_0_4") + + expected_status = { + "code": Revision.TWO.value, + "name": "1.1.0" + } + + query_request = { + "version": self._version, + "from": self._accounts[0], + "to": GOVERNANCE_SCORE_ADDRESS, + "dataType": "call", + "data": { + "method": "getRevision", + "params": {} + } + } + response = self._query(query_request) + self.assertEqual(expected_status, response) + + tx_results: List['TransactionResult'] = self.deploy_score("sample_scores", + "sample_dict_db", + self._accounts[0]) + score_address: 'Address' = tx_results[0].score_address + + self.score_call(self._accounts[0], score_address, "create_item", params={"key": "a", "value": hex(1)}) + + with self.assertRaises(InvalidContainerAccessException) as e: + self._query( + { + 'to': score_address, + 'dataType': 'call', + 'data': { + 'method': 'get_items' + } + } + ) + self.assertEqual(e.exception.message, "Not Supported on DictDB")