From 5d9c7305feb798294c97d3178ad4cfe0aafd9729 Mon Sep 17 00:00:00 2001 From: Erik van den Brink Date: Mon, 1 Feb 2021 14:27:58 +0100 Subject: [PATCH] https://github.com/neo-project/neo/pull/2119 utterly broken after this but not fixing until all preview + preview 5 changes are in --- neo3/contracts/__init__.py | 3 +- neo3/contracts/applicationengine.py | 10 +- neo3/contracts/interop/__init__.py | 1 - neo3/contracts/interop/contract.py | 191 +++---------------- neo3/contracts/interop/native.py | 18 -- neo3/contracts/interop/storage.py | 21 +- neo3/contracts/native/__init__.py | 4 +- neo3/contracts/native/designate.py | 15 +- neo3/contracts/native/management.py | 243 ++++++++++++++++++++++++ neo3/contracts/native/nativecontract.py | 113 ++++++----- neo3/contracts/native/oracle.py | 16 +- neo3/contracts/nef.py | 2 +- neo3/storage/__init__.py | 2 - neo3/storage/base.py | 101 +--------- neo3/storage/cache.py | 166 +--------------- neo3/storage/implementations/leveldb.py | 80 -------- neo3/storage/implementations/memory.py | 85 --------- neo3/storage/snapshot.py | 21 -- neo3/storage/storagekey.py | 46 ++--- 19 files changed, 384 insertions(+), 754 deletions(-) delete mode 100644 neo3/contracts/interop/native.py create mode 100644 neo3/contracts/native/management.py diff --git a/neo3/contracts/__init__.py b/neo3/contracts/__init__.py index b49168aa..0d7ccb59 100644 --- a/neo3/contracts/__init__.py +++ b/neo3/contracts/__init__.py @@ -23,7 +23,8 @@ GasToken, OracleContract, DesignateContract, - DesignateRole) + DesignateRole, + ManagementContract) from .checkreturn import ReturnTypeConvention from .applicationengine import ApplicationEngine diff --git a/neo3/contracts/applicationengine.py b/neo3/contracts/applicationengine.py index 5579af0d..9f8f3f60 100644 --- a/neo3/contracts/applicationengine.py +++ b/neo3/contracts/applicationengine.py @@ -87,7 +87,9 @@ def checkwitness(self, hash_: types.UInt160) -> bool: contracts.native.CallFlags(self.current_context.call_flags): raise ValueError("Context requires callflags ALLOW_STATES") - contract = self.snapshot.contracts.get(self.calling_scripthash) + contract = contracts.ManagementContract().get_contract(self.snapshot, self.calling_scripthash) + if contract is None: + return False group_keys = set(map(lambda g: g.public_key, contract.manifest.groups)) if any(group_keys.intersection(signer.allowed_groups)): return True @@ -377,3 +379,9 @@ def load_contract(self, if init is not None: self.load_context(context.clone(init.offset), False) return context + + def call_native(self, name: str) -> None: + contract = contracts.ManagementContract().get_contract_by_name(name) + if contract is None or contract.active_block_index > self.snapshot.persisting_block.index: + raise ValueError + contract.invoke(self) diff --git a/neo3/contracts/interop/__init__.py b/neo3/contracts/interop/__init__.py index e7de0095..052cdbf8 100644 --- a/neo3/contracts/interop/__init__.py +++ b/neo3/contracts/interop/__init__.py @@ -11,6 +11,5 @@ from .crypto import __name__ from .json import __name__ from .enumerator import IIterator, IEnumerator, StorageIterator -from .native import __name__ from .runtime import __name__ from .storage import _storage_put_internal, MAX_STORAGE_VALUE_SIZE, MAX_STORAGE_KEY_SIZE diff --git a/neo3/contracts/interop/contract.py b/neo3/contracts/interop/contract.py index b16af685..964d6afb 100644 --- a/neo3/contracts/interop/contract.py +++ b/neo3/contracts/interop/contract.py @@ -6,172 +6,6 @@ from neo3.contracts.interop import register -@register("System.Contract.Create", 0, contracts.native.CallFlags.WRITE_STATES, False, [bytes, bytes]) -def contract_create(engine: contracts.ApplicationEngine, nef_file: bytes, manifest: bytes) -> None: - if not isinstance(engine.script_container, payloads.Transaction): - raise ValueError("Cannot create contract without a Transaction script container") - - nef_len = len(nef_file) - manifest_len = len(manifest) - if (nef_len == 0 - or nef_len > engine.MAX_CONTRACT_LENGTH - or manifest_len == 0 - or manifest_len > contracts.ContractManifest.MAX_LENGTH): - raise ValueError("Invalid NEF or manifest length") - - engine.add_gas(engine.STORAGE_PRICE * (nef_len + manifest_len)) - - nef = contracts.NEF.deserialize_from_bytes(nef_file) - sb = vm.ScriptBuilder() - sb.emit(vm.OpCode.ABORT) - sb.emit_push(engine.script_container.sender.to_array()) - sb.emit_push(nef.script) - hash_ = to_script_hash(sb.to_array()) - - contract = engine.snapshot.contracts.try_get(hash_) - if contract is not None: - raise ValueError("Contract already exists") - - new_id = engine.snapshot.contract_id + 1 - engine.snapshot.contract_id = new_id - - contract = storage.ContractState( - new_id, - nef.script, - contracts.ContractManifest.from_json(json.loads(manifest.decode())), - 0, - hash_ - ) - - if not contract.manifest.is_valid(hash_): - raise ValueError("Error: invalid manifest") - - engine.snapshot.contracts.put(contract) - - engine.push(engine._native_to_stackitem(contract, storage.ContractState)) - method_descriptor = contract.manifest.abi.get_method("_deploy") - if method_descriptor is not None: - contract_call_internal_ex(engine, - contract, - method_descriptor, - vm.ArrayStackItem(engine.reference_counter, vm.BooleanStackItem(False)), - contracts.native.CallFlags.ALL, - contracts.ReturnTypeConvention.ENSURE_IS_EMPTY - ) - - -@register("System.Contract.Update", 0, contracts.native.CallFlags.WRITE_STATES, False, [bytes, bytes]) -def contract_update(engine: contracts.ApplicationEngine, nef_file: bytes, manifest: bytes) -> None: - nef_len = len(nef_file) - manifest_len = len(manifest) - - engine.add_gas(engine.STORAGE_PRICE * (nef_len + manifest_len)) - - contract = engine.snapshot.contracts.try_get(engine.current_scripthash, read_only=False) - if contract is None: - raise ValueError("Can't find contract to update") - - if nef_len == 0: - raise ValueError(f"Invalid NEF length: {nef_len}") - - nef = contracts.NEF.deserialize_from_bytes(nef_file) - # update contract - contract.script = nef.script - - if manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH: - raise ValueError(f"Invalid manifest length: {manifest_len}") - - contract.manifest = contracts.ContractManifest.from_json(json.loads(manifest.decode())) - if not contract.manifest.is_valid(contract.hash_): - raise ValueError("Error: manifest does not match with script") - - contract.update_counter += 1 - - if len(nef_file) != 0: - method_descriptor = contract.manifest.abi.get_method("_deploy") - if method_descriptor is not None: - contract_call_internal_ex(engine, - contract, - method_descriptor, - vm.ArrayStackItem(engine.reference_counter, vm.BooleanStackItem(True)), - contracts.native.CallFlags.ALL, - contracts.ReturnTypeConvention.ENSURE_IS_EMPTY - ) - - -@register("System.Contract.Destroy", 1000000, contracts.native.CallFlags.WRITE_STATES, False) -def contract_destroy(engine: contracts.ApplicationEngine) -> None: - hash_ = engine.current_scripthash - contract = engine.snapshot.contracts.try_get(hash_) - - if contract is None: - return - - engine.snapshot.contracts.delete(hash_) - - for key, _ in engine.snapshot.storages.find(contract.script_hash(), b''): - engine.snapshot.storages.delete(key) - - -@register("contract_call_internal", 0, contracts.native.CallFlags.ALL, False, []) -def contract_call_internal(engine: contracts.ApplicationEngine, - contract_hash: types.UInt160, - method: str, - args: vm.ArrayStackItem, - flags: contracts.native.CallFlags, - convention: contracts.ReturnTypeConvention) -> None: - if method.startswith('_'): - raise ValueError("[System.Contract.Call] Method not allowed to start with _") - - target_contract = engine.snapshot.contracts.try_get(contract_hash, read_only=True) - if target_contract is None: - raise ValueError("[System.Contract.Call] Can't find target contract") - - method_descriptor = target_contract.manifest.abi.get_method(method) - if method_descriptor is None: - raise ValueError(f"[System.Contract.Call] Method '{method}' does not exist on target contract") - - current_contract = engine.snapshot.contracts.try_get(engine.current_scripthash, read_only=True) - if current_contract and not current_contract.can_call(target_contract, method): - raise ValueError(f"[System.Contract.Call] Not allowed to call target method '{method}' according to manifest") - - contract_call_internal_ex(engine, target_contract, method_descriptor, args, flags, convention) - - -def contract_call_internal_ex(engine: contracts.ApplicationEngine, - contract: storage.ContractState, - contract_method_descriptor: contracts.ContractMethodDescriptor, - args: vm.ArrayStackItem, - flags: contracts.native.CallFlags, - convention: contracts.ReturnTypeConvention) -> None: - counter = engine._invocation_counter.get(contract.hash, 0) - engine._invocation_counter.update({contract.hash: counter + 1}) - - engine._get_invocation_state(engine.current_context).convention = convention - - state = engine.current_context - calling_flags = state.call_flags - - arg_len = len(args) - expected_len = len(contract_method_descriptor.parameters) - if arg_len != expected_len: - raise ValueError( - f"[System.Contract.Call] Invalid number of contract arguments. Expected {expected_len} actual {arg_len}") # noqa - - context_new = engine.load_contract(contract, contract_method_descriptor.name, flags & calling_flags) - if context_new is None: - raise ValueError - context_new.calling_script = state.script - - if contracts.NativeContract.is_native(contract.hash): - context_new.evaluation_stack.push(args) - context_new.evaluation_stack.push(vm.ByteStringStackItem(contract_method_descriptor.name.encode('utf-8'))) - else: - for item in reversed(args): - context_new.evaluation_stack.push(item) - context_new.ip = contract_method_descriptor.offset - - @register("System.Contract.Call", 1000000, contracts.native.CallFlags.ALLOW_CALL, False, [types.UInt160, str, vm.ArrayStackItem]) def contract_call(engine: contracts.ApplicationEngine, @@ -192,12 +26,15 @@ def contract_callex(engine: contracts.ApplicationEngine, # and will thrown an exception while converting the arguments for the function # if ((callFlags & ~CallFlags.All) != 0) # throw new ArgumentOutOfRangeException(nameof(callFlags)); - contract_call_internal(engine, contract_hash, method, args, flags, contracts.ReturnTypeConvention.ENSURE_NOT_EMPTY) + # TODO: fix + # contract_call_internal(engine, + # contract_hash, method, args, flags, contracts.ReturnTypeConvention.ENSURE_NOT_EMPTY) + pass @register("System.Contract.IsStandard", 30000, contracts.native.CallFlags.READ_STATES, True, [types.UInt160]) def contract_is_standard(engine: contracts.ApplicationEngine, hash_: types.UInt160) -> bool: - contract = engine.snapshot.contracts.try_get(hash_) + contract = contracts.ManagementContract().get_contract(engine.snapshot, hash_) if contract: return (contracts.Contract.is_signature_contract(contract.script) or contracts.Contract.is_multisig_contract(contract.script)) @@ -220,3 +57,21 @@ def get_callflags(engine: contracts.ApplicationEngine) -> contracts.native.CallF def contract_create_standard_account(engine: contracts.ApplicationEngine, public_key: cryptography.ECPoint) -> types.UInt160: return to_script_hash(contracts.Contract.create_signature_redeemscript(public_key)) + + +@register("System.Contract.NativeOnPersist", 0, contracts.CallFlags.WRITE_STATES, False, []) +def native_on_persist(engine: contracts.ApplicationEngine) -> None: + if engine.trigger != contracts.TriggerType.ON_PERSIST: + raise SystemError() + for contract in contracts.NativeContract._contracts.values(): + if contract.active_block_index <= engine.snapshot.persisting_block.index: + contract.on_persist(engine) + + +@register("System.Contract.NativePostPersist", 0, contracts.CallFlags.WRITE_STATES, False, []) +def native_post_persist(engine: contracts.ApplicationEngine) -> None: + if engine.trigger != contracts.TriggerType.POST_PERSIST: + raise SystemError() + for contract in contracts.NativeContract._contracts.values(): + if contract.active_block_index <= engine.snapshot.persisting_block.index: + contract.post_persist(engine) diff --git a/neo3/contracts/interop/native.py b/neo3/contracts/interop/native.py deleted file mode 100644 index fc29d3b1..00000000 --- a/neo3/contracts/interop/native.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import annotations -from neo3 import contracts, storage -from neo3.contracts.interop import register - - -@register("Neo.Native.Deploy", 0, contracts.native.CallFlags.WRITE_STATES, False, []) -def deploy_native(engine: contracts.ApplicationEngine) -> None: - if engine.snapshot.persisting_block.index != 0: - raise ValueError("Can only deploy native contracts in the genenis block") - - for nc in contracts.NativeContract()._contracts.values(): - engine.snapshot.contracts.put(storage.ContractState(nc.id, nc.script, nc.manifest, 0, nc.hash)) - nc._initialize(engine) - - -@register("Neo.Native.Call", 0, contracts.native.CallFlags.ALLOW_CALL, False, [str]) -def call_native(engine: contracts.ApplicationEngine, contract_name: str) -> None: - contracts.NativeContract.get_contract(contract_name).invoke(engine) diff --git a/neo3/contracts/interop/storage.py b/neo3/contracts/interop/storage.py index 8c00ae02..5394857f 100644 --- a/neo3/contracts/interop/storage.py +++ b/neo3/contracts/interop/storage.py @@ -11,27 +11,32 @@ @register("System.Storage.GetContext", 400, contracts.native.CallFlags.READ_STATES, False, []) def get_context(engine: contracts.ApplicationEngine) -> storage.StorageContext: - return storage.StorageContext(engine.current_scripthash, False) + contract = contracts.ManagementContract().get_contract(engine.snapshot, engine.current_scripthash) + if contract is None: + raise ValueError("Contract not deployed") + return storage.StorageContext(contract.id, False) @register("System.Storage.GetReadOnlyContext", 400, contracts.native.CallFlags.READ_STATES, False, []) def get_read_only_context(engine: contracts.ApplicationEngine) -> storage.StorageContext: - contract = engine.snapshot.contracts.try_get(engine.current_scripthash, read_only=True) - return storage.StorageContext(contract.script_hash(), True) + contract = contracts.ManagementContract().get_contract(engine.snapshot, engine.current_scripthash) + if contract is None: + raise ValueError("Contract not deployed") + return storage.StorageContext(contract.id, True) @register("System.Storage.AsReadOnly", 400, contracts.native.CallFlags.READ_STATES, False, [storage.StorageContext]) def context_as_read_only(engine: contracts.ApplicationEngine, context: storage.StorageContext) -> storage.StorageContext: if not context.is_read_only: - context = storage.StorageContext(context.script_hash, True) + context = storage.StorageContext(context.id, True) return context @register("System.Storage.Get", 1000000, contracts.native.CallFlags.READ_STATES, False, [storage.StorageContext, bytes]) def storage_get(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes) -> Optional[bytes]: - storage_key = storage.StorageKey(context.script_hash, key) + storage_key = storage.StorageKey(context.id, key) item = engine.snapshot.storages.try_get(storage_key, read_only=True) if item is not None: return item.value @@ -41,7 +46,7 @@ def storage_get(engine: contracts.ApplicationEngine, context: storage.StorageCon @register("System.Storage.Find", 1000000, contracts.native.CallFlags.READ_STATES, False, [storage.StorageContext, bytes]) def storage_find(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes) -> IIterator: - it = StorageIterator(engine.snapshot.storages.find(context.script_hash, key)) + it = StorageIterator(engine.snapshot.storages.find(context.id, key)) return it @@ -57,7 +62,7 @@ def _storage_put_internal(engine: contracts.ApplicationEngine, if context.is_read_only: raise ValueError("Cannot persist to read-only storage context") - storage_key = storage.StorageKey(context.script_hash, key) + storage_key = storage.StorageKey(context.id, key) item = engine.snapshot.storages.try_get(storage_key, read_only=False) is_constant = storage.StorageFlags.CONSTANT in flags @@ -104,7 +109,7 @@ def storage_put_ex(engine: contracts.ApplicationEngine, def storage_delete(engine: contracts.ApplicationEngine, context: storage.StorageContext, key: bytes) -> None: if context.is_read_only: raise ValueError("Cannot delete from read-only storage context") - storage_key = storage.StorageKey(context.script_hash, key) + storage_key = storage.StorageKey(context.id, key) item = engine.snapshot.storages.try_get(storage_key) if item and item.is_constant: raise ValueError("Cannot delete a storage item that is marked constant") diff --git a/neo3/contracts/native/__init__.py b/neo3/contracts/native/__init__.py index 660a4929..0fb01222 100644 --- a/neo3/contracts/native/__init__.py +++ b/neo3/contracts/native/__init__.py @@ -1,11 +1,13 @@ from .nativecontract import (CallFlags, NativeContract, PolicyContract, NeoToken, GasToken) from .oracle import OracleContract from .designate import DesignateRole, DesignateContract +from .management import ManagementContract __all__ = ['NativeContract', 'CallFlags', 'PolicyContract', 'NeoToken', 'GasToken', - 'OracleContract' + 'OracleContract', + 'ManagementContract' ] diff --git a/neo3/contracts/native/designate.py b/neo3/contracts/native/designate.py index a81ef209..61044cb2 100644 --- a/neo3/contracts/native/designate.py +++ b/neo3/contracts/native/designate.py @@ -16,13 +16,14 @@ class DesignateContract(NativeContract): _id = -5 def init(self): + super(DesignateContract, self).init() self._register_contract_method(self.get_designated_by_role, "getDesignatedByRole", 1000000, return_type=List[cryptography.ECPoint], add_engine=False, add_snapshot=True, - safe_method=True) + call_flags=contracts.native.CallFlags.READ_STATES) self._register_contract_method(self.designate_as_role, "designateAsRole", @@ -30,7 +31,7 @@ def init(self): return_type=None, add_engine=True, add_snapshot=False, - safe_method=False) + call_flags=contracts.native.CallFlags.WRITE_STATES) def get_designated_by_role(self, snapshot: storage.Snapshot, @@ -39,11 +40,9 @@ def get_designated_by_role(self, if snapshot.block_height + 1 < index: raise ValueError("[DesignateContract] Designate list index out of range") - key = storage.StorageKey(self.hash, - role.to_bytes(1, 'little') + vm.BigInteger(index).to_array() - ).to_array() - boundary = storage.StorageKey(self.hash, role.to_bytes(1, 'little')) .to_array() - for _, storage_item in snapshot.storages.find_range(self.hash, key, boundary, "reverse"): + key = self.create_key(role.to_bytes(1, 'little') + vm.BigInteger(index).to_array()).to_array() + boundary = self.create_key(role.to_bytes(1, 'little')) .to_array() + for _, storage_item in snapshot.storages.find_range(key, boundary, "reverse"): with serialization.BinaryReader(storage_item.value) as reader: return reader.read_serializable_list(cryptography.ECPoint) else: @@ -67,7 +66,7 @@ def designate_as_role(self, nodes.sort() index = engine.snapshot.persisting_block.index + 1 - storage_key = storage.StorageKey(self.hash, role.to_bytes(1, 'little') + vm.BigInteger(index).to_array()) + storage_key = self.create_key(role.to_bytes(1, 'little') + vm.BigInteger(index).to_array()) with serialization.BinaryWriter() as writer: writer.write_serializable_list(nodes) storage_item = storage.StorageItem(writer.to_array()) diff --git a/neo3/contracts/native/management.py b/neo3/contracts/native/management.py new file mode 100644 index 00000000..6859b4e8 --- /dev/null +++ b/neo3/contracts/native/management.py @@ -0,0 +1,243 @@ +from __future__ import annotations +import json +from . import NativeContract +from typing import Optional +from neo3 import storage, contracts, vm +from neo3.core import to_script_hash, types +from neo3.network import payloads +from neo3.contracts.interop import register + + +@register("contract_call_internal", 0, contracts.native.CallFlags.ALL, False, []) +def contract_call_internal(engine: contracts.ApplicationEngine, + contract_hash: types.UInt160, + method: str, + args: vm.ArrayStackItem, + flags: contracts.native.CallFlags, + convention: contracts.ReturnTypeConvention) -> None: + if method.startswith('_'): + raise ValueError("[System.Contract.Call] Method not allowed to start with _") + + target_contract = ManagementContract().get_contract(engine.snapshot, contract_hash) + if target_contract is None: + raise ValueError("[System.Contract.Call] Can't find target contract") + + method_descriptor = target_contract.manifest.abi.get_method(method) + if method_descriptor is None: + raise ValueError(f"[System.Contract.Call] Method '{method}' does not exist on target contract") + + current_contract = ManagementContract().get_contract(engine.snapshot, engine.current_scripthash) + if current_contract and not current_contract.can_call(target_contract, method): + raise ValueError( + f"[System.Contract.Call] Not allowed to call target method '{method}' according to manifest") + + contract_call_internal_ex(engine, target_contract, method_descriptor, args, flags, convention) + + +def contract_call_internal_ex(engine: contracts.ApplicationEngine, + contract: storage.ContractState, + contract_method_descriptor: contracts.ContractMethodDescriptor, + args: vm.ArrayStackItem, + flags: contracts.native.CallFlags, + convention: contracts.ReturnTypeConvention) -> None: + counter = engine._invocation_counter.get(contract.hash, 0) + engine._invocation_counter.update({contract.hash: counter + 1}) + + engine._get_invocation_state(engine.current_context).convention = convention + + state = engine.current_context + calling_flags = state.call_flags + + arg_len = len(args) + expected_len = len(contract_method_descriptor.parameters) + if arg_len != expected_len: + raise ValueError( + f"[System.Contract.Call] Invalid number of contract arguments. Expected {expected_len} actual {arg_len}") # noqa + + context_new = engine.load_contract(contract, contract_method_descriptor.name, flags & calling_flags) + if context_new is None: + raise ValueError + context_new.calling_script = state.script + + if contracts.NativeContract.is_native(contract.hash): + context_new.evaluation_stack.push(args) + context_new.evaluation_stack.push(vm.ByteStringStackItem(contract_method_descriptor.name.encode('utf-8'))) + else: + for item in reversed(args): + context_new.evaluation_stack.push(item) + context_new.ip = contract_method_descriptor.offset + + +class ManagementContract(NativeContract): + _service_name = "Neo Contract Management" + _id = 0 + + _PREFIX_NEXT_AVAILABLE_ID = b'\x0F' + _PREFIX_CONTRACT = b'\x08' + + def init(self): + super(ManagementContract, self).init() + + self._register_contract_method(self.get_contract, + 1000000, + "getContract", + add_engine=False, + add_snapshot=True, + return_type=storage.ContractState, + call_flags=contracts.CallFlags.READ_STATES) + self._register_contract_method(self.contract_create, + 0, + "deploy", + add_engine=True, + add_snapshot=False, + return_type=None, + parameter_names=["nef_file", "manifest"], + parameter_types=[bytes, bytes], + call_flags=contracts.native.CallFlags.WRITE_STATES) + self._register_contract_method(self.contract_update, + 0, + "update", + add_engine=True, + add_snapshot=False, + return_type=None, + parameter_names=["nef_file", "manifest"], + parameter_types=[bytes, bytes], + call_flags=contracts.native.CallFlags.WRITE_STATES) + self._register_contract_method(self.contract_destroy, + 1000000, + "destroy", + add_engine=True, + add_snapshot=False, + return_type=None, + call_flags=contracts.native.CallFlags.WRITE_STATES) + + def get_next_available_id(self, snapshot: storage.Snapshot) -> int: + key = self.create_key(self._PREFIX_NEXT_AVAILABLE_ID) + item = snapshot.storages.try_get(key, read_only=False) + if item is None: + value = vm.BigInteger(1) + item = storage.StorageItem(value.to_array()) + else: + value = vm.BigInteger(item.value) + 1 + item.value = value.to_array() + snapshot.storages.update(key, item) + return int(value) + + def on_persist(self, engine: contracts.ApplicationEngine) -> None: + for contract in self._contracts.values(): + if contract.active_block_index != engine.snapshot.persisting_block.index: + continue + storage_key = self.create_key(self._PREFIX_CONTRACT + contract.hash.to_array()) + storage_item = storage.StorageItem( + storage.ContractState(contract.id, contract.script, contract.manifest, 0, contract.hash).to_array() + ) + engine.snapshot.storages.put(storage_key, storage_item) + contract._initialize(engine) + + def get_contract(self, snapshot: storage.Snapshot, hash_: types.UInt160) -> Optional[storage.ContractState]: + storage_key = self.create_key(self._PREFIX_CONTRACT + hash_.to_array()) + storage_item = snapshot.storages.try_get(storage_key, read_only=True) + if storage_item is None: + return None + return storage.ContractState.deserialize_from_bytes(storage_item.value) + + def contract_create(self, engine: contracts.ApplicationEngine, nef_file: bytes, manifest: bytes) -> None: + if not isinstance(engine.script_container, payloads.Transaction): + raise ValueError("Cannot create contract without a Transaction script container") + + nef_len = len(nef_file) + manifest_len = len(manifest) + if (nef_len == 0 + or nef_len > engine.MAX_CONTRACT_LENGTH + or manifest_len == 0 + or manifest_len > contracts.ContractManifest.MAX_LENGTH): + raise ValueError("Invalid NEF or manifest length") + + engine.add_gas(engine.STORAGE_PRICE * (nef_len + manifest_len)) + + nef = contracts.NEF.deserialize_from_bytes(nef_file) + sb = vm.ScriptBuilder() + sb.emit(vm.OpCode.ABORT) + sb.emit_push(engine.script_container.sender.to_array()) + sb.emit_push(nef.script) + hash_ = to_script_hash(sb.to_array()) + + key = self.create_key(self._PREFIX_CONTRACT + hash_.to_array()) + contract = engine.snapshot.storages.try_get(key) + if contract is not None: + raise ValueError("Contract already exists") + + contract = storage.ContractState( + self.get_next_available_id(engine.snapshot), + nef.script, + contracts.ContractManifest.from_json(json.loads(manifest.decode())), + 0, + hash_ + ) + + if not contract.manifest.is_valid(hash_): + raise ValueError("Error: invalid manifest") + + engine.snapshot.storages.put(key, storage.StorageItem(contract.to_array())) + + engine.push(engine._native_to_stackitem(contract, storage.ContractState)) + method_descriptor = contract.manifest.abi.get_method("_deploy") + if method_descriptor is not None: + contract_call_internal_ex(engine, + contract, + method_descriptor, + vm.ArrayStackItem(engine.reference_counter, vm.BooleanStackItem(False)), + contracts.native.CallFlags.ALL, + contracts.ReturnTypeConvention.ENSURE_IS_EMPTY + ) + + def contract_update(self, engine: contracts.ApplicationEngine, nef_file: bytes, manifest: bytes) -> None: + nef_len = len(nef_file) + manifest_len = len(manifest) + + engine.add_gas(engine.STORAGE_PRICE * (nef_len + manifest_len)) + + key = self.create_key(self._PREFIX_CONTRACT + engine.current_scripthash.to_array()) + contract = engine.snapshot.storages.try_get(key, read_only=False) + if contract is None: + raise ValueError("Can't find contract to update") + + if nef_len == 0: + raise ValueError(f"Invalid NEF length: {nef_len}") + + nef = contracts.NEF.deserialize_from_bytes(nef_file) + # update contract + contract.script = nef.script + + if manifest_len == 0 or manifest_len > contracts.ContractManifest.MAX_LENGTH: + raise ValueError(f"Invalid manifest length: {manifest_len}") + + contract.manifest = contracts.ContractManifest.from_json(json.loads(manifest.decode())) + if not contract.manifest.is_valid(contract.hash_): + raise ValueError("Error: manifest does not match with script") + + contract.update_counter += 1 + + if len(nef_file) != 0: + method_descriptor = contract.manifest.abi.get_method("_deploy") + if method_descriptor is not None: + contract_call_internal_ex(engine, + contract, + method_descriptor, + vm.ArrayStackItem(engine.reference_counter, vm.BooleanStackItem(True)), + contracts.native.CallFlags.ALL, + contracts.ReturnTypeConvention.ENSURE_IS_EMPTY + ) + + def contract_destroy(self, engine: contracts.ApplicationEngine) -> None: + hash_ = engine.current_scripthash + key = self.create_key(self._PREFIX_CONTRACT + hash_.to_array()) + contract = engine.snapshot.storages.try_get(key) + + if contract is None: + return + + engine.snapshot.storages.delete(hash_) + + for key, _ in engine.snapshot.storages.find(contract.id.to_bytes(4, 'little', signed=True), b''): + engine.snapshot.storages.delete(key) diff --git a/neo3/contracts/native/nativecontract.py b/neo3/contracts/native/nativecontract.py index 54f1eb0e..8a24d05c 100644 --- a/neo3/contracts/native/nativecontract.py +++ b/neo3/contracts/native/nativecontract.py @@ -54,6 +54,8 @@ class NativeContract(convenience._Singleton): #: A dictionary for accessing a native contract by its hash _contract_hashes: Dict[types.UInt160, NativeContract] = {} + active_block_index = 0 + def init(self): self._methods: Dict[str, _ContractMethodMetadata] = {} self._neo = NeoToken() @@ -61,9 +63,9 @@ def init(self): self._policy = PolicyContract() sb = vm.ScriptBuilder() sb.emit_push(self._service_name) - sb.emit_syscall(192440171) # "Neo.Native.Call" + sb.emit_syscall(1736177434) # "System.Contract.CallNative" self._script: bytes = sb.to_array() - sender = to_script_hash(b'\x11') # OpCode.PUSH1 + sender = types.UInt160.zero() # OpCode.PUSH1 sb = vm.ScriptBuilder() sb.emit(vm.OpCode.ABORT) sb.emit_push(sender.to_array()) @@ -93,7 +95,7 @@ def init(self): call_flags=contracts.CallFlags.WRITE_STATES) @classmethod - def get_contract(cls, name: str) -> NativeContract: + def get_contract_by_name(cls, name: str) -> NativeContract: """ Get the contract instance by its service name Args: @@ -116,7 +118,7 @@ def _register_contract_method(self, parameter_names: List[str] = None, add_engine: bool = False, add_snapshot: bool = False, - call_flags: contracts.CallFlags = contracts.CallFlags.NONE + call_flags: contracts.native.CallFlags = contracts.native.CallFlags.NONE ) -> None: """ Registers a native contract method into the manifest @@ -274,17 +276,18 @@ def on_persist(self, engine: contracts.ApplicationEngine) -> None: Should not be called manually. """ - if engine.trigger != contracts.TriggerType.ON_PERSIST: - raise SystemError("Invalid operation") + pass def post_persist(self, engine: contracts.ApplicationEngine): - if engine.trigger != contracts.TriggerType.POST_PERSIST: - raise SystemError("Invalid operation") + pass def _check_committee(self, engine: contracts.ApplicationEngine) -> bool: addr = NeoToken().get_committee_address() return engine.checkwitness(addr) + def create_key(self, prefix: bytes) -> storage.StorageKey: + return storage.StorageKey(self._id, prefix) + class PolicyContract(NativeContract): _id: int = -3 @@ -393,7 +396,7 @@ def get_max_block_size(self, snapshot: storage.Snapshot) -> int: int: maximum number of bytes. """ data = snapshot.storages.try_get( - storage.StorageKey(self.hash, self._PREFIX_MAX_BLOCK_SIZE), + self.create_key(self._PREFIX_MAX_BLOCK_SIZE), read_only=True ) if data is None: @@ -408,7 +411,7 @@ def get_max_transactions_per_block(self, snapshot: storage.Snapshot) -> int: int: maximum number of transaction. """ data = snapshot.storages.try_get( - storage.StorageKey(self.hash, self._PREFIX_MAX_TRANSACTIONS_PER_BLOCK), + self.create_key(self._PREFIX_MAX_TRANSACTIONS_PER_BLOCK), read_only=True ) if data is None: @@ -423,7 +426,7 @@ def get_max_block_system_fee(self, snapshot: storage.Snapshot) -> int: int: maximum system fee. """ data = snapshot.storages.try_get( - storage.StorageKey(self.hash, self._PREFIX_MAX_BLOCK_SYSTEM_FEE), + self.create_key(self._PREFIX_MAX_BLOCK_SYSTEM_FEE), read_only=True ) if data is None: @@ -439,7 +442,7 @@ def get_fee_per_byte(self, snapshot: storage.Snapshot) -> int: int: maximum fee. """ data = snapshot.storages.try_get( - storage.StorageKey(self.hash, self._PREFIX_FEE_PER_BYTE), + self.create_key(self._PREFIX_FEE_PER_BYTE), read_only=True ) if data is None: @@ -454,7 +457,7 @@ def is_blocked(self, snapshot: storage.Snapshot, account: types.UInt160) -> bool """ si = snapshot.storages.try_get( - storage.StorageKey(self.hash, self._PREFIX_BLOCKED_ACCOUNT + account.to_array()), + self.create_key(self._PREFIX_BLOCKED_ACCOUNT + account.to_array()), read_only=True ) if si is None: @@ -472,7 +475,7 @@ def _set_max_block_size(self, engine: contracts.ApplicationEngine, value: int) - if not self._check_committee(engine): return False - storage_key = storage.StorageKey(self.hash, self._PREFIX_MAX_BLOCK_SIZE) + storage_key = self.create_key(self._PREFIX_MAX_BLOCK_SIZE) storage_item = engine.snapshot.storages.try_get( storage_key, read_only=False @@ -493,7 +496,7 @@ def _set_max_transactions_per_block(self, engine: contracts.ApplicationEngine, v if not self._check_committee(engine): return False - storage_key = storage.StorageKey(self.hash, self._PREFIX_MAX_TRANSACTIONS_PER_BLOCK) + storage_key = self.create_key(self._PREFIX_MAX_TRANSACTIONS_PER_BLOCK) storage_item = engine.snapshot.storages.try_get( storage_key, read_only=False @@ -516,7 +519,7 @@ def _set_max_block_system_fee(self, engine: contracts.ApplicationEngine, value: if not self._check_committee(engine): return False - storage_key = storage.StorageKey(self.hash, self._PREFIX_MAX_BLOCK_SYSTEM_FEE) + storage_key = self.create_key(self._PREFIX_MAX_BLOCK_SYSTEM_FEE) storage_item = engine.snapshot.storages.try_get( storage_key, read_only=False @@ -538,7 +541,7 @@ def _set_fee_per_byte(self, engine: contracts.ApplicationEngine, value: int) -> if not self._check_committee(engine): return False - storage_key = storage.StorageKey(self.hash, self._PREFIX_FEE_PER_BYTE) + storage_key = self.create_key(self._PREFIX_FEE_PER_BYTE) storage_item = engine.snapshot.storages.try_get( storage_key, read_only=False @@ -556,8 +559,7 @@ def _block_account(self, engine: contracts.ApplicationEngine, account: types.UIn """ if not self._check_committee(engine): return False - storage_key = storage.StorageKey(self.hash, - self._PREFIX_BLOCKED_ACCOUNT + account.to_array()) + storage_key = self.create_key(self._PREFIX_BLOCKED_ACCOUNT + account.to_array()) storage_item = engine.snapshot.storages.try_get(storage_key, read_only=False) if storage_item is None: storage_item = storage.StorageItem(b'\x01') @@ -573,8 +575,7 @@ def _unblock_account(self, engine: contracts.ApplicationEngine, account: types.U """ if not self._check_committee(engine): return False - storage_key = storage.StorageKey(self.hash, - self._PREFIX_BLOCKED_ACCOUNT + account.to_array()) + storage_key = self.create_key(self._PREFIX_BLOCKED_ACCOUNT + account.to_array()) storage_item = engine.snapshot.storages.try_get(storage_key, read_only=False) if storage_item is None: return False @@ -665,7 +666,7 @@ def mint(self, if amount == vm.BigInteger.zero(): return - storage_key = storage.StorageKey(self.hash, self._PREFIX_ACCOUNT + account.to_array()) + storage_key = self.create_key(self._PREFIX_ACCOUNT + account.to_array()) storage_item = engine.snapshot.storages.try_get(storage_key, read_only=False) if storage_item is None: @@ -676,7 +677,7 @@ def mint(self, self.on_balance_changing(engine, account, state, amount) state.balance += amount - storage_key = storage.StorageKey(self.hash, self._PREFIX_TOTAL_SUPPLY) + storage_key = self.create_key(self._PREFIX_TOTAL_SUPPLY) storage_item = engine.snapshot.storages.try_get(storage_key, read_only=False) if storage_item is None: storage_item = storage.StorageItem(amount.to_array()) @@ -702,7 +703,7 @@ def burn(self, engine: contracts.ApplicationEngine, account: types.UInt160, amou if amount == vm.BigInteger.zero(): return - storage_key = storage.StorageKey(self.hash, self._PREFIX_ACCOUNT + account.to_array()) + storage_key = self.create_key(self._PREFIX_ACCOUNT + account.to_array()) storage_item = engine.snapshot.storages.get(storage_key, read_only=False) state = self._state.from_storage(storage_item) @@ -716,7 +717,7 @@ def burn(self, engine: contracts.ApplicationEngine, account: types.UInt160, amou state.balance -= amount engine.snapshot.storages.update(storage_key, storage_item) - storage_key = storage.StorageKey(self.hash, self._PREFIX_TOTAL_SUPPLY) + storage_key = self.create_key(self._PREFIX_TOTAL_SUPPLY) storage_item = engine.snapshot.storages.get(storage_key, read_only=False) old_value = vm.BigInteger(storage_item.value) new_value = old_value - amount @@ -728,7 +729,7 @@ def burn(self, engine: contracts.ApplicationEngine, account: types.UInt160, amou def total_supply(self, snapshot: storage.Snapshot) -> vm.BigInteger: """ Get the total deployed tokens. """ - storage_key = storage.StorageKey(self.hash, self._PREFIX_TOTAL_SUPPLY) + storage_key = self.create_key(self._PREFIX_TOTAL_SUPPLY) storage_item = snapshot.storages.try_get(storage_key) if storage_item is None: return vm.BigInteger.zero() @@ -748,7 +749,7 @@ def balance_of(self, snapshot: storage.Snapshot, account: types.UInt160) -> vm.B Note: The returned value is still in internal format. Divide the results by the contract's `decimals` """ - storage_key = storage.StorageKey(self.hash, self._PREFIX_ACCOUNT + account.to_array()) + storage_key = self.create_key(self._PREFIX_ACCOUNT + account.to_array()) storage_item = snapshot.storages.try_get(storage_key) if storage_item is None: return vm.BigInteger.zero() @@ -779,7 +780,7 @@ def _post_transfer(self, # wallet or smart contract if not call_on_payment \ or account_to == types.UInt160.zero() \ - or engine.snapshot.contracts.try_get(account_to) is None: + or contracts.ManagementContract().get_contract(engine.snapshot, account_to) is None: return if account_from == types.UInt160.zero(): @@ -812,7 +813,7 @@ def transfer(self, if account_from != engine.calling_scripthash and not engine.checkwitness(account_from): return False - storage_key_from = storage.StorageKey(self.hash, self._PREFIX_ACCOUNT + account_from.to_array()) + storage_key_from = self.create_key(self._PREFIX_ACCOUNT + account_from.to_array()) storage_item_from = engine.snapshot.storages.try_get(storage_key_from, read_only=False) if storage_item_from is None: @@ -834,7 +835,7 @@ def transfer(self, else: state_from.balance -= amount - storage_key_to = storage.StorageKey(self.hash, self._PREFIX_ACCOUNT + account_to.to_array()) + storage_key_to = self.create_key(self._PREFIX_ACCOUNT + account_to.to_array()) storage_item_to = engine.snapshot.storages.try_get(storage_key_to, read_only=False) if storage_item_to is None: storage_item_to = storage.StorageItem(b'') @@ -966,7 +967,7 @@ class _CommitteeState(serialization.ISerializable): def __init__(self, snapshot: storage.Snapshot, validators: Dict[cryptography.ECPoint, vm.BigInteger]): self._snapshot = snapshot self._validators = validators - self._storage_key = storage.StorageKey(NeoToken().hash, NeoToken()._PREFIX_COMMITTEE) + self._storage_key = storage.StorageKey(NeoToken().id, NeoToken()._PREFIX_COMMITTEE) with serialization.BinaryWriter() as writer: self.serialize(writer) @@ -1053,7 +1054,7 @@ def _serializable_init(cls): class GasBonusState(serialization.ISerializable, Sequence): def __init__(self, initial_record: _GasRecord = None): - self._storage_key = storage.StorageKey(NeoToken().hash, NeoToken()._PREFIX_GAS_PER_BLOCK) + self._storage_key = storage.StorageKey(NeoToken().id, NeoToken()._PREFIX_GAS_PER_BLOCK) self._records: List[_GasRecord] = [initial_record] if initial_record else [] self._storage_item = storage.StorageItem(b'') self._iter = iter(self._records) @@ -1081,7 +1082,7 @@ def __setitem__(self, key, record: _GasRecord) -> None: def from_snapshot(cls, snapshot: storage.Snapshot): record = cls() record._storage_item = snapshot.storages.get( - storage.StorageKey(NeoToken().hash, NeoToken()._PREFIX_GAS_PER_BLOCK)) + storage.StorageKey(NeoToken().id, NeoToken()._PREFIX_GAS_PER_BLOCK)) with serialization.BinaryReader(record._storage_item.value) as reader: record.deserialize(reader) return record @@ -1132,11 +1133,8 @@ def _calculate_bonus(self, if vote.is_zero(): return neo_holder_reward - border = storage.StorageKey(self.hash, - self._PREFIX_VOTER_REWARD_PER_COMMITTEE + vote.to_array() - ).to_array() - key_start = storage.StorageKey( - self.hash, + border = self.create_key(self._PREFIX_VOTER_REWARD_PER_COMMITTEE + vote.to_array()).to_array() + key_start = self.create_key( self._PREFIX_VOTER_REWARD_PER_COMMITTEE + vote.to_array() + vm.BigInteger(start).to_array() ).to_array() @@ -1146,8 +1144,7 @@ def _calculate_bonus(self, else: start_reward_per_neo = vm.BigInteger.zero() - key_end = storage.StorageKey( - self.hash, + key_end = self.create_key( self._PREFIX_VOTER_REWARD_PER_COMMITTEE + vote.to_array() + vm.BigInteger(end).to_array() ).to_array() @@ -1186,11 +1183,10 @@ def _check_candidate(self, public_key: cryptography.ECPoint, candidate: _CandidateState) -> None: if not candidate.registered and candidate.votes == 0: - storage_key = storage.StorageKey(self.hash, - self._PREFIX_VOTER_REWARD_PER_COMMITTEE + public_key.to_array()) + storage_key = self.create_key(self._PREFIX_VOTER_REWARD_PER_COMMITTEE + public_key.to_array()) for k, v in snapshot.storages.find(storage_key): snapshot.storages.delete(k) - storage_key_candidate = storage.StorageKey(self.hash, self._PREFIX_CANDIDATE + public_key.to_array()) + storage_key_candidate = self.create_key(self._PREFIX_CANDIDATE + public_key.to_array()) snapshot.storages.delete(storage_key_candidate) def init(self): @@ -1279,13 +1275,13 @@ def _initialize(self, engine: contracts.ApplicationEngine) -> None: dict.fromkeys(settings.standby_validators, vm.BigInteger(0)) ) engine.snapshot.storages.put( - storage.StorageKey(self.hash, self._PREFIX_VOTERS_COUNT), + self.create_key(self._PREFIX_VOTERS_COUNT), storage.StorageItem(b'\x00') ) gas_bonus_state = GasBonusState(_GasRecord(0, GasToken().factor * 5)) engine.snapshot.storages.put( - storage.StorageKey(NeoToken().hash, NeoToken()._PREFIX_GAS_PER_BLOCK), + self.create_key(NeoToken()._PREFIX_GAS_PER_BLOCK), storage.StorageItem(gas_bonus_state.to_array()) ) self.mint(engine, @@ -1309,12 +1305,12 @@ def on_balance_changing(self, engine: contracts.ApplicationEngine, if state.vote_to.is_zero(): return - sk_voters_count = storage.StorageKey(self.hash, self._PREFIX_VOTERS_COUNT) + sk_voters_count = self.create_key(self._PREFIX_VOTERS_COUNT) si_voters_count = engine.snapshot.storages.get(sk_voters_count, read_only=False) new_value = vm.BigInteger(si_voters_count.value) + amount si_voters_count.value = new_value.to_array() - sk_candidate = storage.StorageKey(self.hash, self._PREFIX_CANDIDATE + state.vote_to.to_array()) + sk_candidate = self.create_key(self._PREFIX_CANDIDATE + state.vote_to.to_array()) si_candidate = engine.snapshot.storages.get(sk_candidate, read_only=False) candidate_state = _CandidateState.from_storage(si_candidate) candidate_state.votes += amount @@ -1349,13 +1345,11 @@ def post_persist(self, engine: contracts.ApplicationEngine): member_votes = self._committee_state[member] if member_votes > 0: voter_sum_reward_per_neo = factor * voter_reward_of_each_committee / member_votes - voter_reward_key = storage.StorageKey( - self.hash, + voter_reward_key = self.create_key( (self._PREFIX_VOTER_REWARD_PER_COMMITTEE + member.to_array() + vm.BigInteger(engine.snapshot.persisting_block.index + 1).to_array()) ) - border = storage.StorageKey( - self.hash, + border = self.create_key( self._PREFIX_VOTER_REWARD_PER_COMMITTEE + member.to_array() ).to_array() result = engine.snapshot.storages.find_range(self.hash, voter_reward_key.to_array(), border) @@ -1379,7 +1373,7 @@ def unclaimed_gas(self, snapshot: storage.Snapshot, account: types.UInt160, end: end: ending block height to calculate bonus up to. You should use mostlikly use the current chain height. """ storage_item = snapshot.storages.try_get( - storage.StorageKey(self.hash, self._PREFIX_ACCOUNT + account.to_array()) + self.create_key(self._PREFIX_ACCOUNT + account.to_array()) ) if storage_item is None: return vm.BigInteger.zero() @@ -1403,7 +1397,7 @@ def register_candidate(self, if not engine.checkwitness(script_hash): return False - storage_key = storage.StorageKey(self.hash, self._PREFIX_CANDIDATE + public_key.to_array()) + storage_key = self.create_key(self._PREFIX_CANDIDATE + public_key.to_array()) storage_item = engine.snapshot.storages.try_get(storage_key, read_only=False) if storage_item is None: state = _CandidateState() @@ -1433,7 +1427,7 @@ def unregister_candidate(self, if not engine.checkwitness(script_hash): return False - storage_key = storage.StorageKey(self.hash, self._PREFIX_CANDIDATE + public_key.to_array()) + storage_key = self.create_key(self._PREFIX_CANDIDATE + public_key.to_array()) storage_item = engine.snapshot.storages.try_get(storage_key, read_only=False) if storage_item is None: return True @@ -1463,13 +1457,13 @@ def vote(self, if not engine.checkwitness(account): return False - storage_key_account = storage.StorageKey(self.hash, self._PREFIX_ACCOUNT + account.to_array()) + storage_key_account = self.create_key(self._PREFIX_ACCOUNT + account.to_array()) storage_item = engine.snapshot.storages.try_get(storage_key_account, read_only=False) if storage_item is None: return False account_state = self._state.from_storage(storage_item) - storage_key_candidate = storage.StorageKey(self.hash, self._PREFIX_CANDIDATE + vote_to.to_array()) + storage_key_candidate = self.create_key(self._PREFIX_CANDIDATE + vote_to.to_array()) storage_item_candidate = engine.snapshot.storages.try_get(storage_key_candidate, read_only=False) if storage_key_candidate is None: return False @@ -1479,7 +1473,7 @@ def vote(self, return False if account_state.vote_to.is_zero(): - sk_voters_count = storage.StorageKey(self.hash, self._PREFIX_VOTERS_COUNT) + sk_voters_count = self.create_key(self._PREFIX_VOTERS_COUNT) si_voters_count = engine.snapshot.storages.get(sk_voters_count, read_only=False) old_value = vm.BigInteger(si_voters_count.value) @@ -1487,8 +1481,7 @@ def vote(self, si_voters_count.value = new_value.to_array() if not account_state.vote_to.is_zero(): - sk_validator = storage.StorageKey(self.hash, - self._PREFIX_CANDIDATE + account_state.vote_to.to_array()) + sk_validator = self.create_key(self._PREFIX_CANDIDATE + account_state.vote_to.to_array()) si_validator = engine.snapshot.storages.get(sk_validator, read_only=False) validator_state = _CandidateState.from_storage(si_validator) validator_state.votes -= account_state.balance @@ -1544,7 +1537,7 @@ def get_committee_address(self) -> types.UInt160: ) def _compute_committee_members(self, snapshot: storage.Snapshot) -> Dict[cryptography.ECPoint, vm.BigInteger]: - storage_key = storage.StorageKey(self.hash, self._PREFIX_VOTERS_COUNT) + storage_key = self.create_key(self._PREFIX_VOTERS_COUNT) storage_item = snapshot.storages.get(storage_key, read_only=True) voters_count = int(vm.BigInteger(storage_item.value)) voter_turnout = voters_count / float(self.total_amount) diff --git a/neo3/contracts/native/oracle.py b/neo3/contracts/native/oracle.py index b89a00aa..7fae0da3 100644 --- a/neo3/contracts/native/oracle.py +++ b/neo3/contracts/native/oracle.py @@ -113,7 +113,7 @@ def init(self): def _initialize(self, engine: contracts.ApplicationEngine) -> None: engine.snapshot.storages.put( - storage.StorageKey(self.hash, self._PREFIX_REQUEST_ID), + self.create_key(self._PREFIX_REQUEST_ID), storage.StorageItem(b'\x00' * 8) # uint64 ) @@ -150,7 +150,7 @@ def finish(self, engine: contracts.ApplicationEngine) -> None: def get_request(self, snapshot: storage.Snapshot, id: int) -> Optional[OracleRequest]: id_bytes = id.to_bytes(8, 'little', signed=False) - storage_key = storage.StorageKey(self.hash, self._PREFIX_REQUEST + id_bytes) + storage_key = self.create_key(self._PREFIX_REQUEST + id_bytes) storage_item = snapshot.storages.try_get(storage_key) if storage_item is None: return None @@ -186,15 +186,15 @@ def _request(self, engine.add_gas(gas_for_response) self._gas.mint(engine, self.hash, vm.BigInteger(gas_for_response), False) - sk_item_id = storage.StorageKey(self.hash, self._PREFIX_REQUEST_ID) + sk_item_id = self.create_key(self._PREFIX_REQUEST_ID) si_item_id = engine.snapshot.storages.get(sk_item_id, read_only=False) item_id = int.from_bytes(si_item_id.value, 'little', signed=False) si_item_id.value = item_id.to_bytes(8, 'little', signed=False) - if engine.snapshot.contracts.try_get(engine.calling_scripthash) is None: + if contracts.ManagementContract().get_contract(engine.snapshot, engine.calling_scripthash) is None: raise ValueError - sk_request = storage.StorageKey(self.hash, self._PREFIX_REQUEST + si_item_id.value) + sk_request = self.create_key(self._PREFIX_REQUEST + si_item_id.value) oracle_request = OracleRequest(self._get_original_txid(engine), gas_for_response, url, @@ -204,7 +204,7 @@ def _request(self, contracts.BinarySerializer.serialize(user_data, self._MAX_USER_DATA_LEN)) engine.snapshot.storages.put(sk_request, storage.StorageItem(oracle_request.to_array())) - sk_id_list = storage.StorageKey(self.hash, self._PREFIX_ID_LIST + self._get_url_hash(url)) + sk_id_list = self.create_key(self._PREFIX_ID_LIST + self._get_url_hash(url)) si_id_list = engine.snapshot.storages.try_get(sk_id_list, read_only=False) if si_id_list is None: si_id_list = storage.StorageItem(b'\x00') @@ -251,7 +251,7 @@ def post_persist(self, engine: contracts.ApplicationEngine) -> None: continue # remove request from storage - sk_request = storage.StorageKey(self.hash, self._PREFIX_REQUEST + response.id.to_bytes(8, 'little')) + sk_request = self.create_key(self._PREFIX_REQUEST + response.id.to_bytes(8, 'little')) si_request = engine.snapshot.storages.try_get(sk_request) if si_request is None: continue @@ -259,7 +259,7 @@ def post_persist(self, engine: contracts.ApplicationEngine) -> None: engine.snapshot.storages.delete(sk_request) # remove id from id list - sk_id_list = storage.StorageKey(self.hash, self._PREFIX_ID_LIST + self._get_url_hash(request.url)) + sk_id_list = self.create_key(self._PREFIX_ID_LIST + self._get_url_hash(request.url)) si_id_list = engine.snapshot.storages.try_get(sk_id_list, read_only=False) if si_id_list is None: si_id_list = storage.StorageItem(b'\x00') diff --git a/neo3/contracts/nef.py b/neo3/contracts/nef.py index af788ff4..bc79dbf5 100644 --- a/neo3/contracts/nef.py +++ b/neo3/contracts/nef.py @@ -75,7 +75,7 @@ def deserialize(self, reader: serialization.BinaryReader) -> None: raise ValueError("Deserialization error - Incorrect magic") self.compiler = reader.read_bytes(32).decode('utf-8') self.version = reader.read_bytes(32).decode('utf-8') - self.script = reader.read_var_bytes() + self.script = reader.read_var_bytes(max=512 * 1024) if len(self.script) == 0: raise ValueError("Deserialization error - Script can't be empty") diff --git a/neo3/storage/__init__.py b/neo3/storage/__init__.py index e299d13f..07fd6195 100644 --- a/neo3/storage/__init__.py +++ b/neo3/storage/__init__.py @@ -6,8 +6,6 @@ CloneTXCache, CachedBlockAccess, CloneBlockCache, - CachedContractAccess, - CloneContractCache, CachedStorageAccess, CloneStorageCache, AttributeCache) diff --git a/neo3/storage/base.py b/neo3/storage/base.py index 4a608512..5451e391 100644 --- a/neo3/storage/base.py +++ b/neo3/storage/base.py @@ -30,18 +30,6 @@ def _internal_bestblockheight_put(self, height: int): def _internal_bestblockheight_update(self, height: int): """ Update the existing best stored block height. """ - @abc.abstractmethod - def _internal_contractid_get(self): - """ Get the latest known contract id. """ - - @abc.abstractmethod - def _internal_contractid_put(self, new_id: int): - """ Persist a new latest known contract id. """ - - @abc.abstractmethod - def _internal_contractid_update(self, new_id: int): - """ Update the latest known contract id. """ - @abc.abstractmethod def _internal_block_put(self, block: payloads.Block) -> None: """ Persist a block to the real backend. """ @@ -80,36 +68,6 @@ def _internal_block_get_by_height(self, height: int) -> payloads.Block: def _internal_block_all(self) -> Iterator[payloads.Block]: """ Return all blocks stored in the real backend (readonly). """ - @abc.abstractmethod - def _internal_contract_put(self, contract: storage.ContractState) -> None: - """ Persist a contract in the real backend. """ - - @abc.abstractmethod - def _internal_contract_update(self, contract: storage.ContractState) -> None: - """ Update a contract in the real backend. """ - - @abc.abstractmethod - def _internal_contract_delete(self, script_hash: types.UInt160) -> None: - """ Delete a contract from the real backend. """ - - @abc.abstractmethod - def _internal_contract_get(self, script_hash: types.UInt160) -> storage.ContractState: - """ - Get a contract from the real backend. - - Must raise KeyError if not found. Return value must be read only. - """ - - def _internal_contract_try_get(self, script_hash: types.UInt160) -> Optional[storage.ContractState]: - try: - return self._internal_contract_get(script_hash) - except KeyError: - return None - - @abc.abstractmethod - def _internal_contract_all(self) -> Iterator[storage.ContractState]: - """ Return all contracts stored in the real backend (readonly). """ - @abc.abstractmethod def _internal_storage_put(self, key: storage.StorageKey, value: storage.StorageItem) -> None: """ Persist a storage key/value pair in the real backend. """ @@ -191,10 +149,6 @@ def __init__(self, db: IDBImplementation): def blocks(self): return RawBlockAccess(self._db) - @property - def contracts(self): - return RawContractAccess(self._db) - @property def storages(self): return RawStorageAccess(self._db) @@ -288,57 +242,6 @@ def all(self) -> Iterator[payloads.Block]: yield block -class RawContractAccess: - def __init__(self, db: IDBImplementation): - self._db = db - - def put(self, contract: storage.ContractState) -> None: - """ - Store a contract. - - Args: - contract: contract state instance. - """ - self._db._internal_contract_put(contract) - - def get(self, script_hash: types.UInt160) -> storage.ContractState: - """ - Retrieve a contract. - - Args: - script_hash: contract script hash. - - Raises: - KeyError: if the item is not found. - """ - return self._db._internal_contract_get(script_hash) - - def try_get(self, script_hash: types.UInt160) -> Optional[storage.ContractState]: - """ - Try to retrieve a contract. - - Args: - script_hash: contract script hash. - """ - return self._db._internal_contract_try_get(script_hash) - - def delete(self, script_hash: types.UInt160) -> None: - """ - Remove a transaction. - - Args: - script_hash: contract script hash. - """ - self._db._internal_contract_delete(script_hash) - - def all(self) -> Iterator[storage.ContractState]: - """ - Retrieve all stored contracts. - """ - for contract in self._db._internal_contract_all(): - yield contract - - class RawStorageAccess: def __init__(self, db: IDBImplementation): self._db = db @@ -461,6 +364,6 @@ def all(self) -> Iterator[payloads.Transaction]: class StorageContext: - def __init__(self, script_hash: types.UInt160, is_read_only: bool): - self.script_hash = script_hash + def __init__(self, contract_id: int, is_read_only: bool): + self.id = contract_id self.is_read_only = is_read_only diff --git a/neo3/storage/cache.py b/neo3/storage/cache.py index 443113c8..fcdf5390 100644 --- a/neo3/storage/cache.py +++ b/neo3/storage/cache.py @@ -272,98 +272,6 @@ def all(self) -> Iterator[payloads.Block]: return None -# It is currently unclear why these do not persist when they're class attributes to CachedContractAccess -# Keeping them here for the time being -_gas_token_contract_state = None -_neo_token_contract_state = None - - -class CachedContractAccess(CachedAccess): - # TODO: update to new values - _gas_token_script_hash = types.UInt160.from_string("668e0c1f9d7b70a99dd9e06eadd4c784d641afbc") - _neo_token_script_hash = types.UInt160.from_string("de5f57d430d3dece511cf975a8d37848cb9e0525") - - def __init__(self, db): - super(CachedContractAccess, self).__init__(db) - self._internal_get = self._db._internal_contract_get - self._internal_try_get = self._db._internal_contract_try_get - self._internal_all = self._db._internal_contract_all - - def put(self, contract: storage.ContractState) -> None: - """ - Store a contract. - - Args: - contract: contract state instance. - - Raises: - ValueError: if a duplicate item is found. - """ - super(CachedContractAccess, self)._put(contract.hash, contract) - - def get(self, hash_: types.UInt160, read_only=False) -> storage.ContractState: - """ - Retrieve a contract. - - Args: - hash_: contract hash. - read_only: set to True to safeguard against return value modifications being persisted when committing. - - Raises: - KeyError: if the item is not found. - """ - global _gas_token_contract_state, _neo_token_contract_state - if hash_ == self._gas_token_script_hash: - if _gas_token_contract_state is None: - _gas_token_contract_state = super(CachedContractAccess, self)._get(hash_, read_only) - return _gas_token_contract_state - elif hash_ == self._neo_token_script_hash: - if _neo_token_contract_state is None: - _neo_token_contract_state = super(CachedContractAccess, self)._get(hash_, read_only) - return _neo_token_contract_state - return super(CachedContractAccess, self)._get(hash_, read_only) - - def try_get(self, hash_: types.UInt160, read_only=False) -> Optional[storage.ContractState]: - """ - Try to retrieve a contract. - - Args: - hash_: contract hash. - read_only: set to True to safeguard against return value modifications being persisted when committing. - """ - try: - return self.get(hash_, read_only) - except KeyError: - return None - - def delete(self, hash_: types.UInt160) -> None: - """ - Remove a transaction. - - Args: - hash_: contract hash. - """ - super(CachedContractAccess, self)._delete(hash_) - - def all(self) -> Iterator[storage.ContractState]: - """ - Retrieve all contracts (readonly) - """ - contracts = [] - for contract in self._internal_all(): # type: storage.ContractState - if contract.hash not in self._dictionary: - contracts.append(contract) - - for k, v in self._dictionary.items(): - if v.state != TrackState.DELETED: - contracts.append(deepcopy(v.item)) - - contracts.sort(key=lambda contract: contract.hash.to_array()) - for contract in contracts: - yield contract - return None - - class CachedStorageAccess(CachedAccess): def __init__(self, db): super(CachedStorageAccess, self).__init__(db) @@ -464,33 +372,21 @@ def all(self, contract_script_hash: types.UInt160 = None) -> Iterator[Tuple[stor yield pair return None - def find(self, contract_script_hash: types.UInt160, key_prefix: bytes) -> Iterator[Tuple[storage.StorageKey, - storage.StorageItem]]: + def find(self, key_prefix: bytes) -> Iterator[Tuple[storage.StorageKey, storage.StorageItem]]: """ Retrieve all storage key/value pairs (readonly). Args: - contract_script_hash: script hash of smart contract to search storage of. key_prefix: the prefix part of the storage.StorageKey.key to look for. """ - pairs = [] - for k, v in self._internal_find(contract_script_hash, key_prefix): - if k not in self._dictionary: - pairs.append((k, v)) - - for k, v in self._dictionary.items(): - if v.state != TrackState.DELETED and k.key.startswith(key_prefix): - pairs.append((deepcopy(v.key), deepcopy(v.item))) - - pairs.sort(key=lambda keypair: keypair[0].to_array()) - for pair in pairs: - yield pair - return None + for key, value in self.seek(key_prefix): + if key.to_array().startswith(key_prefix): + yield key, value + else: + raise StopIteration - def seek(self, contract_script_hash: types.UInt160, - key_prefix: bytes, - direction="forward" + def seek(self, key_prefix: bytes, direction="forward" ) -> Iterator[Tuple[storage.StorageKey, storage.StorageItem]]: comperator = storage.NEOByteCompare(direction) @@ -501,7 +397,7 @@ def seek(self, contract_script_hash: types.UInt160, results.append((deepcopy(key), deepcopy(value.item))) cached_keys = self._dictionary.keys() - for key, value in self._internal_seek(contract_script_hash, key_prefix, "forward"): + for key, value in self._internal_seek(key_prefix, "forward"): if key not in cached_keys: results.append((key, value)) results.sort(key=cmp_to_key(partial(storage.NEOSeekSort, comperator.compare))) # type: ignore @@ -511,13 +407,12 @@ def seek(self, contract_script_hash: types.UInt160, return None def find_range(self, - contract_script_hash: types.UInt160, start: bytes, end: bytes, direction: str = "forward" ) -> Iterator[Tuple[storage.StorageKey, storage.StorageItem]]: comperator = storage.NEOByteCompare(direction) - for key, value in self.seek(contract_script_hash, start, direction): + for key, value in self.seek(start, direction): if comperator.compare(key.to_array(), end) < 0: yield key, value else: @@ -654,49 +549,6 @@ def commit(self) -> None: self._changeset.clear() -class CloneContractCache(CachedContractAccess): - def __init__(self, db, inner_cache: CachedContractAccess): - super(CloneContractCache, self).__init__(db) - self.inner_cache = inner_cache - self._internal_get = self._inner_cache_get - self._internal_try_get = self._inner_cache_try_get - self._internal_all = self._inner_cache_all - - def _inner_cache_try_get(self, hash, read_only=True): - try: - return self._inner_cache_get(hash, read_only) - except KeyError: - return None - - def _inner_cache_get(self, hash, read_only=True): - return self.inner_cache.get(hash, read_only) - - def _inner_cache_all(self): - return self.inner_cache.all() - - def commit(self) -> None: - """ - Persist changes to the parent snapshot. - """ - keys_to_delete: List[types.UInt160] = [] - for trackable in self.get_changeset(): # trackable.item: storage.ContractState - if trackable.state == TrackState.ADDED: - self.inner_cache.put(trackable.item) - trackable.state = storage.TrackState.NONE - elif trackable.state == TrackState.CHANGED: - item = self.inner_cache.try_get(trackable.item.script_hash(), read_only=False) - if item: - item.from_replica(trackable.item) - trackable.state = storage.TrackState.NONE - elif trackable.state == TrackState.DELETED: - self.inner_cache.delete(trackable.item.script_hash()) - keys_to_delete.append(trackable.key) - for key in keys_to_delete: - with suppress(KeyError): - self._dictionary.pop(key) - self._changeset.clear() - - class CloneStorageCache(CachedStorageAccess): def __init__(self, db, inner_cache: CachedStorageAccess): super(CloneStorageCache, self).__init__(db) diff --git a/neo3/storage/implementations/leveldb.py b/neo3/storage/implementations/leveldb.py index bf4975ac..d5b175ae 100644 --- a/neo3/storage/implementations/leveldb.py +++ b/neo3/storage/implementations/leveldb.py @@ -17,7 +17,6 @@ class DBPrefixes: BLOCKS = b'\x01' BLOCKS_HEIGHT_MAP = b'\x02' BLOCKS_BEST_HEIGHT = b'\x03' - CONTRACTS = b'\x04' STORAGES = b'\x05' TRANSACTIONS = b'\x06' @@ -127,44 +126,6 @@ def _internal_block_all(self): for block in res: yield deepcopy(block) - def _internal_contract_put(self, contract: storage.ContractState, batch=None): - if batch: - db = batch - else: - db = self._real_db - - db.put(DBPrefixes.CONTRACTS + contract.hash.to_array(), contract.to_array()) - - def _internal_contract_update(self, contract: storage.ContractState, batch=None): - self._internal_contract_put(contract, batch) - - def _internal_contract_delete(self, script_hash: types.UInt160, batch=None): - if batch: - db = batch - else: - db = self._real_db - - db.delete(DBPrefixes.CONTRACTS + script_hash.to_array()) - - def _internal_contract_get(self, script_hash: types.UInt160): - contract_bytes = self._real_db.get(DBPrefixes.CONTRACTS + script_hash.to_array()) - if contract_bytes is None: - raise KeyError - - return storage.ContractState.deserialize_from_bytes(contract_bytes) - - def _internal_contract_all(self): - res = [] - with self._real_db.iterator(prefix=DBPrefixes.CONTRACTS, include_key=False, include_value=True) as it: - for value in it: - # strip off prefix - v = storage.ContractState.deserialize_from_bytes(value) - res.append(v) - - # yielding outside of iterator to make sure the LevelDB iterator is closed and not leaking resources - for contract in res: - yield deepcopy(contract) - def _internal_storage_put(self, key: storage.StorageKey, value: storage.StorageItem, batch=None): if batch: db = batch @@ -282,11 +243,9 @@ def __init__(self, db: LevelDB): self._batch = db._real_db.write_batch() self._block_cache = LevelDBCachedBlockAccess(db, self._batch) - self._contract_cache = LevelDBCachedContractAccess(db, self._batch) self._storage_cache = LevelDBCachedStorageAccess(db, self._batch) self._tx_cache = LevelDBCachedTXAccess(db, self._batch) self._block_height_cache = LevelDBBestBlockHeightAttribute(db, self._batch) - self._contract_id_cache = LevelDBContractIDAttribute(db, self._batch) def commit(self) -> None: super(LevelDBSnapshot, self).commit() @@ -312,19 +271,6 @@ def _update_internal(self, value): self._db._internal_bestblockheight_update(value, self._batch) -class LevelDBContractIDAttribute(storage.AttributeCache): - def __init__(self, db, batch): - super(LevelDBContractIDAttribute, self).__init__() - self._db = db - self._batch = batch - - def _get_internal(self): - return self._db._internal_contractid_get() - - def _update_internal(self, value): - self._db._internal_contractid_update(value, self._batch) - - class LevelDBCachedBlockAccess(storage.CachedBlockAccess): def __init__(self, db, batch): super(LevelDBCachedBlockAccess, self).__init__(db) @@ -351,32 +297,6 @@ def create_snapshot(self): return storage.CloneBlockCache(self._db, self) -class LevelDBCachedContractAccess(storage.CachedContractAccess): - def __init__(self, db, batch): - super(LevelDBCachedContractAccess, self).__init__(db) - self._batch = batch - - def commit(self) -> None: - keys_to_delete: List[types.UInt160] = [] - for trackable in self.get_changeset(): # trackable.item: storage.ContractState - if trackable.state == storage.TrackState.ADDED: - self._db._internal_contract_put(trackable.item, self._batch) - trackable.state = storage.TrackState.NONE - elif trackable.state == storage.TrackState.CHANGED: - self._db._internal_contract_update(trackable.item, self._batch) - trackable.state = storage.TrackState.NONE - elif trackable.state == storage.TrackState.DELETED: - self._db._internal_contract_delete(trackable.item.script_hash(), self._batch) - keys_to_delete.append(trackable.key) - for key in keys_to_delete: - with suppress(KeyError): - self._dictionary.pop(key) - self._changeset.clear() - - def create_snapshot(self): - return storage.CloneContractCache(self._db, self) - - class LevelDBCachedStorageAccess(storage.CachedStorageAccess): def __init__(self, db, batch): super(LevelDBCachedStorageAccess, self).__init__(db) diff --git a/neo3/storage/implementations/memory.py b/neo3/storage/implementations/memory.py index 803a70cf..c05586c4 100644 --- a/neo3/storage/implementations/memory.py +++ b/neo3/storage/implementations/memory.py @@ -11,8 +11,6 @@ class MemoryDB(storage.IDBImplementation): BLOCK = 'blocks' BLOCK_HEIGHT_MAP = 'blocksmap' BLOCK_BEST_HEIGHT = 'blockheight' - CONTRACT = 'contracts' - CONTRACT_ID = 'contract_id' STORAGE = 'storages' TX = 'transactions' @@ -20,7 +18,6 @@ def __init__(self, options: dict = None): self.db: Dict[str, dict] = { self.BLOCK: {}, self.BLOCK_HEIGHT_MAP: {}, - self.CONTRACT: {}, self.STORAGE: {}, self.TX: {} } @@ -44,20 +41,6 @@ def _internal_bestblockheight_put(self, height: int, batch=None): def _internal_bestblockheight_update(self, height: int, batch=None): self._internal_bestblockheight_put(height, batch) - def _internal_contractid_get(self): - if self._contract_id == -1: - raise KeyError - return self._contract_id - - def _internal_contractid_put(self, new_id: int, batch=None): - if batch: - batch.put(self.CONTRACT_ID, new_id, new_id) - else: - self._contract_id = new_id - - def _internal_contractid_update(self, new_id: int, batch=None): - self._internal_contractid_update(new_id, batch) - def _internal_block_put(self, block: payloads.Block, batch: WriteBatch = None) -> None: if batch: batch.put(self.BLOCK, block.hash(), block) @@ -100,33 +83,6 @@ def _internal_block_all(self) -> Iterator[payloads.Block]: for block in self.db[self.BLOCK].values(): yield deepcopy(block) - def _internal_contract_put(self, contract: storage.ContractState, batch: WriteBatch = None) -> None: - if batch: - batch.put(self.CONTRACT, contract.hash, contract) - else: - self.db[self.CONTRACT][contract.hash] = contract - - def _internal_contract_update(self, contract: storage.ContractState, batch: WriteBatch = None) -> None: - self._internal_contract_put(contract, batch) - - def _internal_contract_delete(self, script_hash: types.UInt160, batch: WriteBatch = None) -> None: - if batch: - batch.delete(self.CONTRACT, script_hash) - else: - with suppress(KeyError): - self.db[self.CONTRACT].pop(script_hash) - - def _internal_contract_get(self, script_hash: types.UInt160) -> storage.ContractState: - value = self.db[self.CONTRACT].get(script_hash, None) - if value is None: - raise KeyError - - return deepcopy(value) - - def _internal_contract_all(self) -> Iterator[storage.ContractState]: - for contract in self.db[self.CONTRACT].values(): - yield deepcopy(contract) - def _internal_storage_put(self, key: storage.StorageKey, value: storage.StorageItem, batch: WriteBatch = None) -> None: @@ -237,11 +193,9 @@ def __init__(self, db: MemoryDB): self._batch = WriteBatch() self._block_cache = MemoryDBCachedBlockAccess(db, self._batch) - self._contract_cache = MemoryDBCachedContractAccess(db, self._batch) self._storage_cache = MemoryDBCachedStorageAccess(db, self._batch) self._tx_cache = MemoryDBCachedTXAccess(db, self._batch) self._block_height_cache = MemoryBestBlockHeightAttribute(db, self._batch) - self._contract_id_cache = MemoryBestBlockHeightAttribute(db, self._batch) def commit(self) -> None: super(MemorySnapshot, self).commit() @@ -268,19 +222,6 @@ def _update_internal(self, value): self._db._internal_bestblockheight_update(value, self._batch) -class MemoryContractIDAttribute(storage.AttributeCache): - def __init__(self, db, batch): - super(MemoryContractIDAttribute, self).__init__() - self._db = db - self._batch = batch - - def _get_internal(self): - return self._db._internal_contractid_get() - - def _update_internal(self, value): - self._db._internal_contractid_update(value, self._batch) - - class MemoryDBCachedBlockAccess(storage.CachedBlockAccess): def __init__(self, db, batch): super(MemoryDBCachedBlockAccess, self).__init__(db) @@ -307,32 +248,6 @@ def create_snapshot(self): return storage.CloneBlockCache(self._db, self) -class MemoryDBCachedContractAccess(storage.CachedContractAccess): - def __init__(self, db, batch): - super(MemoryDBCachedContractAccess, self).__init__(db) - self._batch = batch - - def commit(self) -> None: - keys_to_delete: List[types.UInt160] = [] - for trackable in self.get_changeset(): # trackable.item: storage.ContractState - if trackable.state == storage.TrackState.ADDED: - self._db._internal_contract_put(trackable.item, self._batch) - trackable.state = storage.TrackState.NONE - elif trackable.state == storage.TrackState.CHANGED: - self._db._internal_contract_update(trackable.item, self._batch) - trackable.state = storage.TrackState.NONE - elif trackable.state == storage.TrackState.DELETED: - self._db._internal_contract_delete(trackable.item.script_hash(), self._batch) - keys_to_delete.append(trackable.key) - for key in keys_to_delete: - with suppress(KeyError): - self._dictionary.pop(key) - self._changeset.clear() - - def create_snapshot(self): - return storage.CloneContractCache(self._db, self) - - class MemoryDBCachedStorageAccess(storage.CachedStorageAccess): def __init__(self, db, batch): super(MemoryDBCachedStorageAccess, self).__init__(db) diff --git a/neo3/storage/snapshot.py b/neo3/storage/snapshot.py index f35d12fd..14a55389 100644 --- a/neo3/storage/snapshot.py +++ b/neo3/storage/snapshot.py @@ -9,21 +9,15 @@ class Snapshot: def __init__(self): self._block_cache: storage.CachedBlockAccess = None - self._contract_cache: storage.CachedContractAccess = None self._storage_cache: storage.CachedStorageAccess = None self._tx_cache: storage.CachedTXAccess = None self._block_height_cache: storage.AttributeCache = None - self._contract_id_cache: storage.AttributeCache = None self.persisting_block: payloads.Block = None @property def blocks(self): return self._block_cache - @property - def contracts(self): - return self._contract_cache - @property def storages(self): return self._storage_cache @@ -43,17 +37,6 @@ def block_height(self) -> int: def block_height(self, value) -> None: self._block_height_cache.put(value) - @property - def contract_id(self) -> int: - try: - return self._contract_id_cache.get() - except KeyError: - return -1 - - @contract_id.setter - def contract_id(self, value) -> None: - self._contract_id_cache.put(value) - def commit(self): """ @@ -61,11 +44,9 @@ def commit(self): """ self._block_cache.commit() - self._contract_cache.commit() self._storage_cache.commit() self._tx_cache.commit() self._block_height_cache.commit() - self._contract_id_cache.commit() def clone(self) -> CloneSnapshot: return CloneSnapshot(self) @@ -84,11 +65,9 @@ def __init__(self, snapshot: Snapshot): super(CloneSnapshot, self).__init__() self._snapshot = snapshot self._block_cache = snapshot.blocks.create_snapshot() - self._contract_cache = snapshot.contracts.create_snapshot() self._storage_cache = snapshot.storages.create_snapshot() self._tx_cache = snapshot.transactions.create_snapshot() self._block_height_cache = snapshot._block_height_cache.create_snapshot() - self._contract_id_cache = snapshot._contract_id_cache.create_snapshot() self.persisting_block = snapshot.persisting_block def commit(self): diff --git a/neo3/storage/storagekey.py b/neo3/storage/storagekey.py index 529cdb66..c40740ae 100644 --- a/neo3/storage/storagekey.py +++ b/neo3/storage/storagekey.py @@ -3,56 +3,32 @@ class StorageKey(serialization.ISerializable): - def __init__(self, contract: types.UInt160, key: bytes): - self.contract = contract + def __init__(self, id_: int, key: bytes): + self.id = id_ self.key = key def __len__(self): - # TODO: see if there is a cleaner way of doing this - with serialization.BinaryWriter() as bw: - bw.write_bytes_with_grouping(self.key, 16) - key_len = len(bw._stream.getvalue()) - return s.uint160 + key_len + return s.uint32 + len(self.key) def __eq__(self, other): if not isinstance(other, type(self)): return False - return self.contract == other.contract and self.key == other.key + return self.id == other.id and self.key == other.key def __hash__(self): - return hash(self.contract) + mmh3.hash(self.key, seed=0, signed=False) + return hash(self.id) + mmh3.hash(self.key, seed=0, signed=False) def __repr__(self): - return f"<{self.__class__.__name__} at {hex(id(self))}> [{self.contract}] {self.key}" + return f"<{self.__class__.__name__} at {hex(id(self))}> [{self.id}] {self.key}" def serialize(self, writer: serialization.BinaryWriter) -> None: - writer.write_serializable(self.contract) - writer.write_bytes_with_grouping(self.key, group_size=16) + writer.write_int32(self.id) + writer.write_var_bytes(self.key) def deserialize(self, reader: serialization.BinaryReader) -> None: - self.contract = reader.read_serializable(types.UInt160) - self.key = reader.read_bytes_with_grouping(group_size=16) + self.id = reader.read_int32() + self.key = reader.read_var_bytes() @classmethod def _serializable_init(cls): - return cls(types.UInt160.zero(), b'') - - # TODO: Remove when we can conclude with certainty it is no longer needed. - # To be validated on neo-preview2 compatible release - # @staticmethod - # def create_grouped_prefix(key_prefix: bytes, group_size) -> bytes: - # if group_size >= 255: - # raise ValueError("group_size must be < 254") - # - # output = b'' - # index = 0 - # remainder = len(key_prefix) - # while remainder >= group_size: - # output += key_prefix[index:index + group_size] - # output += bytes([group_size]) - # index += group_size - # remainder -= group_size - # - # if remainder > 0: - # output += key_prefix[index:] - # return output + return cls(0, b'')