class PendingTxDbProxy: def __init__(self, db: IconScoreDatabase): self._last_pending_tx_id_db = VarDB(LAST_TX_ID, db, int) self._pending_tx_list = DictDB(PENDING_TX, db, str, 1) self._pending_tx_id_list_db = VarDB(PENDING_TX_ID_LIST, db, str) def add(self, account_id, token_type, contract_addr, to, amount, dids) -> str: """ :param account_id: :param token_type: :param contract_addr: :param to: :param amount: :param dids: :return: """ # get need data last_tx_id = self._last_pending_tx_id_db.get() + 1 pending_tx_id_list_as_str = self._pending_tx_id_list_db.get() if pending_tx_id_list_as_str == "": pending_tx_id_list_as_str = json_dumps([]) pending_tx_id_list = json_loads(pending_tx_id_list_as_str) # create pending tx pending_tx = { "id": last_tx_id, "tokenType": token_type, "contractAddr": contract_addr, "from": account_id, "to": to, "amount": amount, "dids": dids } # update db pending_tx_as_str = json_dumps(pending_tx) self._pending_tx_list[last_tx_id] = pending_tx_as_str self._last_pending_tx_id_db.set(last_tx_id) pending_tx_id_list.append(last_tx_id) self._pending_tx_id_list_db.set(json_dumps(pending_tx_id_list)) return pending_tx_as_str def get(self, tx_id: int) -> dict: if self._pending_tx_list[tx_id] != "": return json_loads(self._pending_tx_list[tx_id]) return {} def remove(self, tx_id: int): del self._pending_tx_list[tx_id] pending_tx_id_list = json_loads( self._pending_tx_id_list_db.get()) # type: List pending_tx_id_list.remove(tx_id) self._pending_tx_id_list_db.set(json_dumps(pending_tx_id_list)) def update(self, pending_tx: dict): self._pending_tx_list[pending_tx["id"]] = json_dumps(pending_tx)
def _var_db_perfomance(self, range_cnt: int, _create_db_func: callable): self._setup(range_cnt, _create_db_func) var_db = VarDB(VAR_DB, self.db, value_type=Address) var_db.set(0) start = time.time() # LOGIC for i in range(range_cnt): a = var_db.get() print(f"_var_db_perfomance [{_create_db_func.__name__} {range_cnt} :", time.time() - start) self._tear_down()
def test_pending_transaction(self): """ GIVEN account with did_list not empty WHEN call transfer with transferable info THEN a transaction will be pending """ # GIVEN self.__setup_processing_pending_tx() # THEN def PendingMock(_self, pending_tx: str): print(pending_tx) pending_tx_as_dict = json.loads(pending_tx) self.assertEqual(1, pending_tx_as_dict["id"]) self.assertEqual(0, pending_tx_as_dict["from"]) self.assertEqual("hx" + "1" * 40, pending_tx_as_dict["to"]) self.assertEqual("icx", pending_tx_as_dict["tokenType"]) self.assertEqual("", pending_tx_as_dict["contractAddr"]) self.assertEqual(500, pending_tx_as_dict["amount"]) self.assertEqual(["kakao", "chainId"], pending_tx_as_dict["dids"]) SmartWallet.Pending = PendingMock # WHEN self.smart_wallet.transfer(account_id=0, token_type="icx", contract_addr="", to="hx" + "1" * 40, amount=500) # THEN pending_tx_db = DictDB(PENDING_TX, self.smart_wallet.db, str, 1) pending_tx_as_dict = json.loads(pending_tx_db[1]) self.assertEqual(1, pending_tx_as_dict["id"]) self.assertEqual(0, pending_tx_as_dict["from"]) self.assertEqual("hx" + "1" * 40, pending_tx_as_dict["to"]) self.assertEqual("icx", pending_tx_as_dict["tokenType"]) self.assertEqual("", pending_tx_as_dict["contractAddr"]) self.assertEqual(500, pending_tx_as_dict["amount"]) self.assertEqual(["kakao", "chainId"], pending_tx_as_dict["dids"]) pending_tx_list = json.loads( VarDB(PENDING_TX_ID_LIST, self.smart_wallet.db, str).get()) self.assertEqual(1, len(pending_tx_list)) self.assertEqual(1, pending_tx_list[0]) last_tx_id = VarDB(LAST_TX_ID, self.smart_wallet.db, int).get() self.assertEqual(1, last_tx_id)
class SampleScore(IconScoreBase): def __init__(self, db: 'IconScoreDatabase') -> None: super().__init__(db) self._db_field = VarDB("field", db, value_type=int) def on_install(self) -> None: pass def on_update(self) -> None: pass def get_owner(self, score_address: Optional['Address']) -> Optional['Address']: return None @eventlog(indexed=2) def SampleEvent(self, i_data: bytes, address: Address, data: bytes, text: str): pass @external def transfer(self): self.icx.transfer(self.msg.sender, 10) @external def set_db(self, size: int): data = b'1' * size self._db_field.set(data) @external def get_db(self): get = self._db_field.get() @external(readonly=True) def query_db(self) -> bytes: get = self._db_field.get() return get @external(readonly=True) def hash_readonly(self, data: bytes) -> bytes: return sha3_256(data) @external def hash_writable(self, data: bytes) -> bytes: return sha3_256(data)
def __init__(self, key, db: IconScoreDatabase, value_type: type, with_flush: bool = False) -> None: self.__base_db = db self.__prefix = str(key) self.__value_type = value_type if not (issubclass(value_type, IterableDictDB) or issubclass(value_type, PropertiesDB)): self.__db = DictDB(self._sub_prefix("_dict"), db, value_type) # TODO using AutoExpand for __db_keys self.__db_keys = VarDB(self._sub_prefix("_keys"), db, value_type=str) self.__with_flush = with_flush self.__cache = {} # cache of db self.__keys = [] self.__is_loaded_keys = False self.__add = {} self.__remove = {} self.__update = {}
def _create_new_db(self, range_cnt: int): self.db = self._create_plyvel_db(range_cnt) self._context = IconScoreContext(IconScoreContextType.DIRECT) self._context.current_address = self.db.address self._context.revision = REVISION ContextContainer._push_context(self._context) ## LOGIC var_db = VarDB(VAR_DB, self.db, value_type=int) array_db = ArrayDB(ARRAY_DB, self.db, value_type=Address) dict_db1 = DictDB(DICT_DB1, self.db, value_type=Address) dict_db2 = DictDB(DICT_DB2, self.db, value_type=int) index: int = 0 for index in range(range_cnt): addr: 'Address' = create_address() array_db.put(addr) dict_db1[index] = addr dict_db2[addr] = index var_db.set(index) ContextContainer._pop_context()
def test_approval(self): """ GIVEN account with two dids, and pending one tx from that account, WHEN call approval twice THEN first approval remove dids in pending tx, second remove pending tx with TransferSuccess event """ # GIVEN self.__setup_processing_pending_tx() SmartWallet.Pending = lambda _self, pending_tx: None self.smart_wallet.transfer(account_id=0, token_type="icx", contract_addr="", to="hx" + "1" * 40, amount=500) # first WHEN def FirstApprovalMock(_self, tx_id, did): self.assertEqual(1, tx_id) self.assertEqual("kakao", did) SmartWallet.Approval = FirstApprovalMock self.smart_wallet.approval(1, "kakao", "true") # first THEN pending_tx_db = DictDB(PENDING_TX, self.smart_wallet.db, str, 1) pending_tx_as_dict = json.loads(pending_tx_db[1]) self.assertEqual(["chainId"], pending_tx_as_dict["dids"]) # second WHEN def SecondApprovalMock(_self, tx_id, did): self.assertEqual(1, tx_id) self.assertEqual("chainId", did) SmartWallet.Approval = SecondApprovalMock SmartWallet.TransferSuccess = lambda _self, account_id, token_type, contract_addr, to, amount: None self.smart_wallet.approval(1, "chainId", "true") # second THEN self.assertEqual("", pending_tx_db[1]) self.assertEqual( json.dumps([]), VarDB(PENDING_TX_ID_LIST, self.smart_wallet.db, str).get())
def __init__(self, db: 'IconScoreDatabase') -> None: super().__init__(db) self._db_field = VarDB("field", db, value_type=int)
def __init__(self, db: IconScoreDatabase): self._last_pending_tx_id_db = VarDB(LAST_TX_ID, db, int) self._pending_tx_list = DictDB(PENDING_TX, db, str, 1) self._pending_tx_id_list_db = VarDB(PENDING_TX_ID_LIST, db, str)
class IterableDictDB(ABC): def __init__(self, key, db: IconScoreDatabase, value_type: type, with_flush: bool = False) -> None: self.__base_db = db self.__prefix = str(key) self.__value_type = value_type if not (issubclass(value_type, IterableDictDB) or issubclass(value_type, PropertiesDB)): self.__db = DictDB(self._sub_prefix("_dict"), db, value_type) # TODO using AutoExpand for __db_keys self.__db_keys = VarDB(self._sub_prefix("_keys"), db, value_type=str) self.__with_flush = with_flush self.__cache = {} # cache of db self.__keys = [] self.__is_loaded_keys = False self.__add = {} self.__remove = {} self.__update = {} @property def base_db(self) -> IconScoreDatabase: return self.__base_db @abstractmethod def raw_to_object(self, raw): pass @abstractmethod def object_to_raw(self, obj): pass @property def _base_db(self) -> IconScoreDatabase: return self.__base_db @property def _prefix(self) -> str: return self.__prefix def _sub_prefix(self, prefix: str) -> str: if self.__prefix is None: raise Exception("not initialized") if prefix is None: raise Exception("invalid prefix") return '|'.join([self.__prefix, prefix]) def flush(self): if self.__with_flush: if issubclass(self.__value_type, IterableDictDB) or issubclass( self.__value_type, PropertiesDB): for key in self.__add: v = self.__add[key] v.flush() for key in self.__update: v = self.__update[key] v.flush() for key in self.__remove: v = self.__remove[key] v.flush() else: for key in self.__add: self.__db[key] = self.object_to_raw(self.__add[key]) for key in self.__update: self.__db[key] = self.object_to_raw(self.__update[key]) for key in self.__remove: self.__db.remove(key) if len(self.__add) + len(self.__remove) > 0: self._flush_keys() def _flush_keys(self) -> None: serialized = ",".join(self.__keys) self.__db_keys.set(serialized) def _load_keys(self) -> list: if not self.__is_loaded_keys: serialized = self.__db_keys.get() if len(serialized) > 1: self.__keys = serialized.split(",") self.__is_loaded_keys = True return self.__keys def _add_key(self, key: str) -> None: # TODO [TBD] encode with escape for delimiter(',') if "," in key: raise Exception('key could not contains comma') self._load_keys() self.__keys.append(key) if not self.__with_flush: self._flush_keys() def _remove_key(self, key: str) -> int: self._load_keys() index = self._index_key(key) self.__keys.pop(index) if not self.__with_flush: self._flush_keys() return index def _index_key(self, key: str) -> int: self._load_keys() for i in range(len(self.__keys)): if self.__keys[i] == key: return i return -1 def keys(self) -> list: self._load_keys() return self.__keys def at(self, index: int): keys = self.keys() if len(keys) > index: return self._get(keys[index]) else: raise Exception("out of range") def _cache(self, key: str): if key in self.__cache: return self.__cache[key] else: if self._contains(key): if issubclass(self.__value_type, IterableDictDB): raw = self.raw_to_object(key) elif issubclass(self.__value_type, PropertiesDB): raw = self.raw_to_object(key) else: raw = self.__db[key] self.__cache[key] = raw return raw else: return None def _set(self, key: str, value) -> None: if key in self.__add: self.__add[key] = value elif key in self.__update: self.__update[key] = value elif key in self.__remove: self.__remove.pop(key) self.__update[key] = value self._add_key(key) else: raw = self._cache(key) if raw is None: self.__add[key] = value self._add_key(key) else: self.__update[key] = value if not self.__with_flush: if not (issubclass(self.__value_type, IterableDictDB) or issubclass(self.__value_type, PropertiesDB)): self.__db[key] = self.object_to_raw(value) def _get(self, key: str): if key in self.__add: return self.__add[key] elif key in self.__update: return self.__update[key] elif key in self.__remove: return None else: raw = self._cache(key) if raw is None: return None else: if not (issubclass(self.__value_type, IterableDictDB) or issubclass(self.__value_type, PropertiesDB)): return self.raw_to_object(raw) else: return raw def remove(self, key) -> None: key = str(key) if key in self.__add: self.__add.pop(key) return elif key in self.__remove: return elif key in self.__update: self.__update.pop(key) v = self._cache(key) if v is None: return self._remove_key(key) self.__remove[key] = v self.__cache.pop(key) if isinstance(v, IterableDictDB) or isinstance(v, PropertiesDB): v.remove_all() else: if not self.__with_flush: self.__db.remove(key) def remove_all(self): for k in self.keys(): self.remove(k) def _contains(self, key: str) -> bool: index = self._index_key(key) if index < 0: return False else: return True def __setitem__(self, key, value) -> None: key = str(key) if value is None: self.remove(key) else: self._set(key, value) def __getitem__(self, key): key = str(key) return self._get(key) def __delitem__(self, key): key = str(key) return self.remove(key) def __contains__(self, key): key = str(key) return self._contains(key) def __iter__(self): return self.keys().__iter__() def __len__(self): return self.keys().__len__() def to_dict(self) -> dict: d = {} for k in self.keys(): v = self._get(k) d[k] = v return d def __repr__(self): return self.dump() def dump(self, depth: int = 0): t1 = '\t' * (depth * 2) s = f'{t1}{self.__prefix}[\n' t = '\t' * (depth * 2 + 1) for k in self.keys(): el = self._get(k) v = '' if isinstance(el, IterableDictDB): v += el.dump(depth + 1) s += f'{t}[key:{k}, value:\n{v}{t}]\n' elif isinstance(el, PropertiesDB): v += el.dump(depth + 1) s += f'{t}[key:{k}, value:\n{v}{t}]\n' else: s += f'{t}[key:{k}, value:{v}]\n' s += f'{t1}]\n' return s