Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

IS-1029: Array Optimization hotfix #424

Merged
merged 2 commits into from
Mar 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions iconservice/database/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
from typing import TYPE_CHECKING, Optional, Tuple, Iterable

import plyvel

from iconcommons.logger import Logger

from .batch import TransactionBatchValue
from ..base.exception import DatabaseException, InvalidParamsException, AccessDeniedException
from ..icon_constant import ICON_DB_LOG_TAG
Expand Down Expand Up @@ -384,6 +384,14 @@ def __init__(self,
self._context_db = context_db
self._observer: Optional[DatabaseObserver] = None

self._prefix_hash_key: bytes = self._make_prefix_hash_key()

def _make_prefix_hash_key(self) -> bytes:
data = [self.address.to_bytes()]
if self._prefix is not None:
data.append(self._prefix)
return b'|'.join(data)

def get(self, key: bytes) -> bytes:
"""
Gets the value for the specified key
Expand Down Expand Up @@ -428,7 +436,7 @@ def get_sub_db(self, prefix: bytes) -> 'IconScoreSubDatabase':
'prefix is None in IconScoreDatabase.get_sub_db()')

if self._prefix is not None:
prefix = b'|'.join([self._prefix, prefix])
prefix = b'|'.join((self._prefix, prefix))

return IconScoreSubDatabase(self.address, self, prefix)

Expand Down Expand Up @@ -460,12 +468,8 @@ def _hash_key(self, key: bytes) -> bytes:
:params key: key passed by SCORE
:return: key bytes
"""
data = [self.address.to_bytes()]
if self._prefix is not None:
data.append(self._prefix)
data.append(key)

return b'|'.join(data)
return b'|'.join((self._prefix_hash_key, key))

def _validate_ownership(self):
"""Prevent a SCORE from accessing the database of another SCORE
Expand All @@ -490,6 +494,14 @@ def __init__(self, address: 'Address', score_db: 'IconScoreDatabase', prefix: by
self._prefix = prefix
self._score_db = score_db

self._prefix_hash_key: bytes = self._make_prefix_hash_key()

def _make_prefix_hash_key(self) -> bytes:
data = []
if self._prefix is not None:
data.append(self._prefix)
return b'|'.join(data)

def get(self, key: bytes) -> bytes:
"""
Gets the value for the specified key
Expand Down Expand Up @@ -521,7 +533,7 @@ def get_sub_db(self, prefix: bytes) -> 'IconScoreSubDatabase':
raise InvalidParamsException("Invalid prefix")

if self._prefix is not None:
prefix = b'|'.join([self._prefix, prefix])
prefix = b'|'.join((self._prefix, prefix))

return IconScoreSubDatabase(self.address, self._score_db, prefix)

Expand All @@ -544,9 +556,5 @@ def _hash_key(self, key: bytes) -> bytes:
:params key: key passed by SCORE
:return: key bytes
"""
data = []
if self._prefix is not None:
data.append(self._prefix)
data.append(key)

return b'|'.join(data)
return b'|'.join((self._prefix_hash_key, key))
96 changes: 48 additions & 48 deletions iconservice/iconscore/icon_container_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@

from typing import TypeVar, Optional, Any, Union, TYPE_CHECKING

from .icon_score_context import ContextContainer
from iconservice.icon_constant import IconScoreContextType, Revision
from iconservice.iconscore.icon_score_context import ContextContainer
from ..base.address import Address
from ..base.exception import InvalidParamsException, InvalidContainerAccessException
from ..icon_constant import Revision, IconScoreContextType
from ..utils import int_to_bytes, bytes_to_int

if TYPE_CHECKING:
Expand All @@ -39,27 +39,27 @@ def get_encoded_key(key: V) -> bytes:

class ContainerUtil(object):

@staticmethod
def create_db_prefix(cls, var_key: K) -> bytes:
@classmethod
def create_db_prefix(cls, container_cls: type, var_key: K) -> bytes:
"""Create a prefix used
as a parameter of IconScoreDatabase.get_sub_db()

:param cls: ArrayDB, DictDB, VarDB
:param container_cls: ArrayDB, DictDB, VarDB
:param var_key:
:return:
"""
if cls == ArrayDB:
if container_cls == ArrayDB:
container_id = ARRAY_DB_ID
elif cls == DictDB:
elif container_cls == DictDB:
container_id = DICT_DB_ID
else:
raise InvalidParamsException(f'Unsupported container class: {cls}')
raise InvalidParamsException(f'Unsupported container class: {container_cls}')

encoded_key: bytes = get_encoded_key(var_key)
return b'|'.join([container_id, encoded_key])

@staticmethod
def encode_key(key: K) -> bytes:
@classmethod
def encode_key(cls, key: K) -> bytes:
"""Create a key passed to IconScoreDatabase

:param key:
Expand All @@ -80,8 +80,8 @@ def encode_key(key: K) -> bytes:
raise InvalidParamsException(f'Unsupported key type: {type(key)}')
return bytes_key

@staticmethod
def encode_value(value: V) -> bytes:
@classmethod
def encode_value(cls, value: V) -> bytes:
if isinstance(value, int):
byte_value = int_to_bytes(value)
elif isinstance(value, str):
Expand All @@ -96,8 +96,8 @@ def encode_value(value: V) -> bytes:
raise InvalidParamsException(f'Unsupported value type: {type(value)}')
return byte_value

@staticmethod
def decode_object(value: bytes, value_type: type) -> Optional[Union[K, V]]:
@classmethod
def decode_object(cls, value: bytes, value_type: type) -> Optional[Union[K, V]]:
if value is None:
return get_default_value(value_type)

Expand All @@ -114,45 +114,45 @@ def decode_object(value: bytes, value_type: type) -> Optional[Union[K, V]]:
obj_value = value
return obj_value

@staticmethod
def remove_prefix_from_iters(iter_items: iter) -> iter:
return ((ContainerUtil.__remove_prefix_from_key(key), value) for key, value in iter_items)
@classmethod
def remove_prefix_from_iters(cls, iter_items: iter) -> iter:
return ((cls.__remove_prefix_from_key(key), value) for key, value in iter_items)

@staticmethod
def __remove_prefix_from_key(key_from_bytes: bytes) -> bytes:
@classmethod
def __remove_prefix_from_key(cls, key_from_bytes: bytes) -> bytes:
return key_from_bytes[:-1]

@staticmethod
def put_to_db(db: 'IconScoreDatabase', db_key: str, container: iter) -> None:
sub_db = db.get_sub_db(ContainerUtil.encode_key(db_key))
@classmethod
def put_to_db(cls, db: 'IconScoreDatabase', db_key: str, container: iter) -> None:
sub_db = db.get_sub_db(cls.encode_key(db_key))
if isinstance(container, dict):
ContainerUtil.__put_to_db_internal(sub_db, container.items())
cls.__put_to_db_internal(sub_db, container.items())
elif isinstance(container, (list, set, tuple)):
ContainerUtil.__put_to_db_internal(sub_db, enumerate(container))
cls.__put_to_db_internal(sub_db, enumerate(container))

@staticmethod
def get_from_db(db: 'IconScoreDatabase', db_key: str, *args, value_type: type) -> Optional[K]:
sub_db = db.get_sub_db(ContainerUtil.encode_key(db_key))
@classmethod
def get_from_db(cls, db: 'IconScoreDatabase', db_key: str, *args, value_type: type) -> Optional[K]:
sub_db = db.get_sub_db(cls.encode_key(db_key))
*args, last_arg = args
for arg in args:
sub_db = sub_db.get_sub_db(ContainerUtil.encode_key(arg))
sub_db = sub_db.get_sub_db(cls.encode_key(arg))

byte_key = sub_db.get(ContainerUtil.encode_key(last_arg))
byte_key = sub_db.get(cls.encode_key(last_arg))
if byte_key is None:
return get_default_value(value_type)
return ContainerUtil.decode_object(byte_key, value_type)
return cls.decode_object(byte_key, value_type)

@staticmethod
def __put_to_db_internal(db: Union['IconScoreDatabase', 'IconScoreSubDatabase'], iters: iter) -> None:
@classmethod
def __put_to_db_internal(cls, db: Union['IconScoreDatabase', 'IconScoreSubDatabase'], iters: iter) -> None:
for key, value in iters:
sub_db = db.get_sub_db(ContainerUtil.encode_key(key))
sub_db = db.get_sub_db(cls.encode_key(key))
if isinstance(value, dict):
ContainerUtil.__put_to_db_internal(sub_db, value.items())
cls.__put_to_db_internal(sub_db, value.items())
elif isinstance(value, (list, set, tuple)):
ContainerUtil.__put_to_db_internal(sub_db, enumerate(value))
cls.__put_to_db_internal(sub_db, enumerate(value))
else:
db_key = ContainerUtil.encode_key(key)
db_value = ContainerUtil.encode_value(value)
db_key = cls.encode_key(key)
db_value = cls.encode_value(value)
db.put(db_key, db_value)


Expand Down Expand Up @@ -217,7 +217,7 @@ def __remove(self, key: K) -> None:
self._db.delete(get_encoded_key(key))

def __iter__(self):
raise InvalidContainerAccessException("Not Supported iter function on DictDB")
raise InvalidContainerAccessException("Iteration not supported in DictDB")


class ArrayDB(object):
Expand Down Expand Up @@ -279,12 +279,12 @@ def __get_size(self) -> int:
return self.__get_size_from_db()

def __get_size_from_db(self) -> int:
return ContainerUtil.decode_object(self._db.get(ArrayDB.__SIZE_BYTE_KEY), int)
return ContainerUtil.decode_object(self._db.get(self.__SIZE_BYTE_KEY), int)

def __set_size(self, size: int) -> None:
self.__legacy_size = size
byte_value = ContainerUtil.encode_value(size)
self._db.put(ArrayDB.__SIZE_BYTE_KEY, byte_value)
self._db.put(self.__SIZE_BYTE_KEY, byte_value)

def __put(self, index: int, value: V) -> None:
byte_value = ContainerUtil.encode_value(value)
Expand Down Expand Up @@ -312,22 +312,22 @@ def __setitem__(self, index: int, value: V) -> None:
raise InvalidParamsException('ArrayDB out of index')

def __getitem__(self, index: int) -> V:
return ArrayDB._get(self._db, self.__get_size(), index, self.__value_type)
return self._get(self._db, self.__get_size(), index, self.__value_type)

def __contains__(self, item: V):
for e in self:
if e == item:
return True
return False

@staticmethod
def __is_defective_revision():
@classmethod
def __is_defective_revision(cls):
context = ContextContainer._get_context()
revision = context.revision
return context.type == IconScoreContextType.INVOKE and revision < Revision.THREE.value

@staticmethod
def _get(db: Union['IconScoreDatabase', 'IconScoreSubDatabase'], size: int, index: int, value_type: type) -> V:
@classmethod
def _get(cls, db: Union['IconScoreDatabase', 'IconScoreSubDatabase'], size: int, index: int, value_type: type) -> V:
if not isinstance(index, int):
raise InvalidParamsException('Invalid index type: not an integer')

Expand All @@ -341,10 +341,10 @@ def _get(db: Union['IconScoreDatabase', 'IconScoreSubDatabase'], size: int, inde

raise InvalidParamsException('ArrayDB out of index')

@staticmethod
def _get_generator(db: Union['IconScoreDatabase', 'IconScoreSubDatabase'], size: int, value_type: type):
@classmethod
def _get_generator(cls, db: Union['IconScoreDatabase', 'IconScoreSubDatabase'], size: int, value_type: type):
for index in range(size):
yield ArrayDB._get(db, size, index, value_type)
yield cls._get(db, size, index, value_type)


class VarDB(object):
Expand Down
2 changes: 1 addition & 1 deletion tests/integrate_test/test_integrate_container_db_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,4 @@ def test_dict_db_defective(self):
}
}
)
self.assertEqual(e.exception.message, "Not Supported on DictDB")
self.assertEqual(e.exception.message, "Iteration not supported in DictDB")
Loading